cloving 0.1.10 → 0.1.12
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 +1 -1
- package/dist/cloving_gpt/adapters/gemini.js +1 -1
- package/dist/cloving_gpt/adapters/mistral.js +1 -1
- package/dist/cloving_gpt/adapters/openai.js +1 -1
- package/dist/cloving_gpt/index.js +1 -1
- package/dist/commands/generate/code.js +85 -64
- package/dist/commands/generate/review.js +7 -1
- package/dist/commands/generate/shell.js +66 -52
- package/dist/commands/generate/unit_tests.js +123 -22
- package/dist/commands/project/build.js +7 -1
- package/dist/index.js +1 -1
- package/dist/package.json +6 -1
- package/dist/utils/config_utils.js +2 -1
- package/dist/utils/string_utils.js +23 -1
- package/package.json +6 -1
|
@@ -24,7 +24,7 @@ class MistralAdapter {
|
|
|
24
24
|
model: this.model.replace(':', '-'),
|
|
25
25
|
messages: [{ role: "user", content: request.prompt }],
|
|
26
26
|
max_tokens: request.maxTokens,
|
|
27
|
-
temperature: request.temperature || 0.
|
|
27
|
+
temperature: request.temperature || 0.2,
|
|
28
28
|
stream: false
|
|
29
29
|
};
|
|
30
30
|
}
|
|
@@ -24,7 +24,7 @@ class OpenAIAdapter {
|
|
|
24
24
|
model: this.model.replace(':', '-'),
|
|
25
25
|
messages: [{ role: "user", content: request.prompt }],
|
|
26
26
|
max_tokens: request.maxTokens,
|
|
27
|
-
temperature: request.temperature || 0.
|
|
27
|
+
temperature: request.temperature || 0.2
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
extractResponse(data) {
|
|
@@ -16,7 +16,7 @@ const generateCodePrompt = (prompt, files, contextFiles, previousCode) => {
|
|
|
16
16
|
const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
|
|
17
17
|
const specialFiles = Object.keys(specialFileContents).map((file) => `### Contents of ${file}\n\n${JSON.stringify(specialFileContents[file], null, 2)}\n\n`).join('\n');
|
|
18
18
|
const contextFileContents = Object.keys(contextFiles).map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`).join('\n');
|
|
19
|
-
let promptText =
|
|
19
|
+
let promptText = `### Description of App
|
|
20
20
|
|
|
21
21
|
${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
|
|
22
22
|
|
|
@@ -87,7 +87,7 @@ const generateCodePrompt = (prompt: string | undefined, files: string[], context
|
|
|
87
87
|
|
|
88
88
|
### Request
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Generate code that does the following: ${prompt}
|
|
91
91
|
|
|
92
92
|
Do not use any data from the example response structure, only use the structure. Please generate the code and include filenames with paths to the code files mentioned and do not be lazy and ask me to keep the existing code or show things like previous code remains unchanged, always include existing code in the response.`;
|
|
93
93
|
return promptText;
|
|
@@ -107,7 +107,13 @@ const displayGeneratedCode = (rawCodeCommand) => {
|
|
|
107
107
|
const lines = code.split('\n');
|
|
108
108
|
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
109
109
|
console.log(lines[0]);
|
|
110
|
-
|
|
110
|
+
try {
|
|
111
|
+
console.log((0, cli_highlight_1.default)(lines.slice(1, -1).join('\n'), { language }));
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
// don't highlight if it fails
|
|
115
|
+
console.log(lines.slice(1, -1).join('\n'));
|
|
116
|
+
}
|
|
111
117
|
console.log(lines.slice(-1)[0]);
|
|
112
118
|
}
|
|
113
119
|
else {
|
|
@@ -115,28 +121,8 @@ const displayGeneratedCode = (rawCodeCommand) => {
|
|
|
115
121
|
}
|
|
116
122
|
});
|
|
117
123
|
};
|
|
118
|
-
const extractFilesAndContent = (rawCodeCommand) => {
|
|
119
|
-
const files = [];
|
|
120
|
-
const fileContents = {};
|
|
121
|
-
const matches = rawCodeCommand.match(/(\*{2})([^\*]+)(\*{2})/g);
|
|
122
|
-
if (!matches)
|
|
123
|
-
return [files, fileContents];
|
|
124
|
-
for (const match of matches) {
|
|
125
|
-
const fileName = match.replace(/\*{2}/g, '').trim();
|
|
126
|
-
const regex = new RegExp(`\\*\\*${fileName}\\*\\*\\n\\n\\\`{3}([\\s\\S]+?)\\\`{3}`, 'g');
|
|
127
|
-
const contentMatch = regex.exec(rawCodeCommand);
|
|
128
|
-
if (contentMatch) {
|
|
129
|
-
files.push(fileName);
|
|
130
|
-
let content = contentMatch[1];
|
|
131
|
-
// Remove the first word after the opening triple backticks
|
|
132
|
-
content = content.split('\n').map((line, idx) => idx === 0 ? line.replace(/^\w+\s*/, '') : line).join('\n');
|
|
133
|
-
fileContents[fileName] = content;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return [files, fileContents];
|
|
137
|
-
};
|
|
138
124
|
const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contextFiles) => {
|
|
139
|
-
const [files, fileContents] = extractFilesAndContent(rawCodeCommand);
|
|
125
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
140
126
|
const { action } = await inquirer_1.default.prompt([
|
|
141
127
|
{
|
|
142
128
|
type: 'list',
|
|
@@ -145,7 +131,8 @@ const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contex
|
|
|
145
131
|
choices: [
|
|
146
132
|
{ name: 'Revise', value: 'revise' },
|
|
147
133
|
{ name: 'Explain', value: 'explain' },
|
|
148
|
-
{ name: 'Save Source Code
|
|
134
|
+
{ name: 'Save a Source Code File', value: 'save' },
|
|
135
|
+
{ name: 'Save All Source Code Files', value: 'saveAll' },
|
|
149
136
|
{ name: 'Copy Source Code to Clipboard', value: 'copySource' },
|
|
150
137
|
{ name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
|
|
151
138
|
{ name: 'Done', value: 'done' },
|
|
@@ -158,7 +145,7 @@ const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contex
|
|
|
158
145
|
{
|
|
159
146
|
type: 'input',
|
|
160
147
|
name: 'newPrompt',
|
|
161
|
-
message: '
|
|
148
|
+
message: 'How would you like to modify the output:',
|
|
162
149
|
},
|
|
163
150
|
]);
|
|
164
151
|
const newRawCodeCommand = await generateCode(gpt, newPrompt, allSrcFiles, contextFiles, rawCodeCommand);
|
|
@@ -194,7 +181,7 @@ const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contex
|
|
|
194
181
|
type: 'confirm',
|
|
195
182
|
name: 'copyMore',
|
|
196
183
|
message: 'Do you want to copy another file?',
|
|
197
|
-
default:
|
|
184
|
+
default: true,
|
|
198
185
|
},
|
|
199
186
|
]);
|
|
200
187
|
copyAnother = copyMore;
|
|
@@ -230,29 +217,96 @@ const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contex
|
|
|
230
217
|
type: 'confirm',
|
|
231
218
|
name: 'saveMore',
|
|
232
219
|
message: 'Do you want to save another file?',
|
|
233
|
-
default:
|
|
220
|
+
default: true,
|
|
234
221
|
},
|
|
235
222
|
]);
|
|
236
223
|
saveAnother = saveMore;
|
|
237
224
|
}
|
|
238
225
|
break;
|
|
226
|
+
case 'saveAll':
|
|
227
|
+
files.forEach(file => {
|
|
228
|
+
if (fileContents[file]) {
|
|
229
|
+
const filePath = path_1.default.resolve(file);
|
|
230
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
231
|
+
fs_1.default.writeFileSync(filePath, fileContents[file]);
|
|
232
|
+
console.log(`${file} has been saved.`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
console.log(`File content not found for ${file}.`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
console.log('All files have been saved.');
|
|
239
|
+
break;
|
|
239
240
|
case 'done':
|
|
240
241
|
break;
|
|
241
242
|
}
|
|
242
243
|
};
|
|
244
|
+
const addFilesToContext = async (contextFiles, filePath) => {
|
|
245
|
+
const stats = fs_1.default.statSync(filePath);
|
|
246
|
+
if (stats.isDirectory()) {
|
|
247
|
+
const files = fs_1.default.readdirSync(filePath);
|
|
248
|
+
for (const file of files) {
|
|
249
|
+
await addFilesToContext(contextFiles, path_1.default.join(filePath, file));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (stats.isFile()) {
|
|
253
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
254
|
+
contextFiles[filePath] = content;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
243
257
|
const code = async (options) => {
|
|
244
258
|
let { prompt, files } = options;
|
|
245
259
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
246
260
|
const gpt = new cloving_gpt_1.default(options);
|
|
247
|
-
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options,
|
|
261
|
+
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, false);
|
|
248
262
|
const contextFiles = {};
|
|
249
263
|
try {
|
|
264
|
+
if (files) {
|
|
265
|
+
files.forEach(async (file) => {
|
|
266
|
+
await addFilesToContext(contextFiles, file);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
let firstFile = true;
|
|
271
|
+
let includeMoreFiles = true;
|
|
272
|
+
while (includeMoreFiles) {
|
|
273
|
+
const { includeMore } = await inquirer_1.default.prompt([
|
|
274
|
+
{
|
|
275
|
+
type: 'confirm',
|
|
276
|
+
name: 'includeMore',
|
|
277
|
+
message: `Do you want to include any${firstFile ? '' : ' other'} files or directories as context for the prompt?`,
|
|
278
|
+
default: false,
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
firstFile = false;
|
|
282
|
+
includeMoreFiles = includeMore;
|
|
283
|
+
if (includeMoreFiles) {
|
|
284
|
+
const { contextFile } = await inquirer_1.default.prompt([
|
|
285
|
+
{
|
|
286
|
+
type: 'input',
|
|
287
|
+
name: 'contextFile',
|
|
288
|
+
message: 'Enter the relative path of a file or directory you would like to include as context:',
|
|
289
|
+
},
|
|
290
|
+
]);
|
|
291
|
+
if (contextFile) {
|
|
292
|
+
const filePath = path_1.default.resolve(contextFile);
|
|
293
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
294
|
+
await addFilesToContext(contextFiles, filePath);
|
|
295
|
+
console.log(`Added ${filePath} to context.`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
console.log(`File or directory ${contextFile} does not exist.`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
250
304
|
if (!prompt) {
|
|
251
305
|
const { userPrompt } = await inquirer_1.default.prompt([
|
|
252
306
|
{
|
|
253
307
|
type: 'input',
|
|
254
308
|
name: 'userPrompt',
|
|
255
|
-
message: 'What would you like to
|
|
309
|
+
message: 'What would you like the code to do:',
|
|
256
310
|
},
|
|
257
311
|
]);
|
|
258
312
|
prompt = userPrompt;
|
|
@@ -265,40 +319,7 @@ const code = async (options) => {
|
|
|
265
319
|
}
|
|
266
320
|
// Read the content of the provided files
|
|
267
321
|
for (const file of files) {
|
|
268
|
-
|
|
269
|
-
contextFiles[file] = content;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
// Prompt for additional context files if no files were passed in options
|
|
274
|
-
let includeMoreFiles = true;
|
|
275
|
-
while (includeMoreFiles) {
|
|
276
|
-
const { contextFile } = await inquirer_1.default.prompt([
|
|
277
|
-
{
|
|
278
|
-
type: 'input',
|
|
279
|
-
name: 'contextFile',
|
|
280
|
-
message: 'Relative path of an existing file you would like to include as context in the prompt [optional]:',
|
|
281
|
-
},
|
|
282
|
-
]);
|
|
283
|
-
if (contextFile) {
|
|
284
|
-
const filePath = path_1.default.resolve(contextFile);
|
|
285
|
-
if (fs_1.default.existsSync(filePath)) {
|
|
286
|
-
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
287
|
-
contextFiles[filePath] = content;
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
console.log(`File ${contextFile} does not exist.`);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
const { includeMore } = await inquirer_1.default.prompt([
|
|
294
|
-
{
|
|
295
|
-
type: 'confirm',
|
|
296
|
-
name: 'includeMore',
|
|
297
|
-
message: 'Do you want to include any other files?',
|
|
298
|
-
default: false,
|
|
299
|
-
},
|
|
300
|
-
]);
|
|
301
|
-
includeMoreFiles = includeMore;
|
|
322
|
+
await addFilesToContext(contextFiles, file);
|
|
302
323
|
}
|
|
303
324
|
}
|
|
304
325
|
const rawCodeCommand = await generateCode(gpt, prompt, allSrcFiles, contextFiles);
|
|
@@ -84,7 +84,13 @@ Format the output of this code review in Markdown format.`;
|
|
|
84
84
|
const lines = code.split('\n');
|
|
85
85
|
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
86
86
|
console.log(lines[0]);
|
|
87
|
-
|
|
87
|
+
try {
|
|
88
|
+
console.log((0, cli_highlight_1.default)(lines.slice(1, -1).join('\n'), { language }));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// don't highlight if it fails
|
|
92
|
+
console.log(lines.slice(1, -1).join('\n'));
|
|
93
|
+
}
|
|
88
94
|
console.log(lines.slice(-1)[0]);
|
|
89
95
|
}
|
|
90
96
|
else {
|
|
@@ -34,6 +34,68 @@ const generateExplainShellPrompt = (prompt) => {
|
|
|
34
34
|
|
|
35
35
|
Please briefly explain how this shell script works.`;
|
|
36
36
|
};
|
|
37
|
+
const generateShell = async (gpt, prompt) => {
|
|
38
|
+
const shellPrompt = generateShellPrompt(prompt);
|
|
39
|
+
return await gpt.generateText({ prompt: shellPrompt });
|
|
40
|
+
};
|
|
41
|
+
const displayGeneratedShell = (rawShellCommand) => {
|
|
42
|
+
const generatedShell = (0, string_utils_1.extractMarkdown)(rawShellCommand);
|
|
43
|
+
const generatedShellWithoutShebang = generatedShell.replace(/^#!.*?\s/, '');
|
|
44
|
+
console.log((0, cli_highlight_1.default)(generatedShellWithoutShebang));
|
|
45
|
+
};
|
|
46
|
+
const handleUserAction = async (gpt, rawShellCommand, prompt) => {
|
|
47
|
+
const generatedShell = (0, string_utils_1.extractMarkdown)(rawShellCommand);
|
|
48
|
+
const generatedShellWithoutShebang = generatedShell.replace(/^#!.*?\s/, '');
|
|
49
|
+
const { action } = await inquirer_1.default.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: 'list',
|
|
52
|
+
name: 'action',
|
|
53
|
+
message: 'What would you like to do?',
|
|
54
|
+
choices: [
|
|
55
|
+
{ name: 'Execute', value: 'execute' },
|
|
56
|
+
{ name: 'Revise', value: 'revise' },
|
|
57
|
+
{ name: 'Explain', value: 'explain' },
|
|
58
|
+
{ name: 'Copy to Clipboard', value: 'copy' },
|
|
59
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
switch (action) {
|
|
64
|
+
case 'execute':
|
|
65
|
+
(0, child_process_1.execSync)(generatedShellWithoutShebang, { stdio: 'inherit' });
|
|
66
|
+
break;
|
|
67
|
+
case 'revise':
|
|
68
|
+
const { newPrompt } = await inquirer_1.default.prompt([
|
|
69
|
+
{
|
|
70
|
+
type: 'input',
|
|
71
|
+
name: 'newPrompt',
|
|
72
|
+
message: 'How would you like to modify the output:',
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
const newRawShellCommand = await generateShell(gpt, newPrompt);
|
|
76
|
+
displayGeneratedShell(newRawShellCommand);
|
|
77
|
+
await handleUserAction(gpt, newRawShellCommand, newPrompt);
|
|
78
|
+
break;
|
|
79
|
+
case 'explain':
|
|
80
|
+
const explainPrompt = generateExplainShellPrompt(generatedShellWithoutShebang);
|
|
81
|
+
const explainShellCommand = await gpt.generateText({ prompt: explainPrompt });
|
|
82
|
+
console.log((0, cli_highlight_1.default)(explainShellCommand, { language: 'markdown' }));
|
|
83
|
+
break;
|
|
84
|
+
case 'copy':
|
|
85
|
+
copy_paste_1.default.copy(generatedShellWithoutShebang, (err) => {
|
|
86
|
+
if (err) {
|
|
87
|
+
console.error('Error: Unable to copy to clipboard.', err);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log('Script copied to clipboard.');
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
94
|
+
case 'cancel':
|
|
95
|
+
console.log('Operation cancelled.');
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
37
99
|
const shell = async (options) => {
|
|
38
100
|
let { prompt } = options;
|
|
39
101
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
@@ -49,60 +111,12 @@ const shell = async (options) => {
|
|
|
49
111
|
]);
|
|
50
112
|
prompt = userPrompt;
|
|
51
113
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const rawShellCommand = await gpt.generateText({ prompt: shellPrompt });
|
|
56
|
-
// Clean the commit message using extractMarkdown
|
|
57
|
-
const generatedShell = (0, string_utils_1.extractMarkdown)(rawShellCommand);
|
|
58
|
-
// remove #! from the generated shell script
|
|
59
|
-
const generatedShellWithoutShebang = generatedShell.replace(/^#!.*?\s/, '');
|
|
60
|
-
console.log((0, cli_highlight_1.default)(generatedShellWithoutShebang));
|
|
61
|
-
// Inquirer prompt for further actions
|
|
62
|
-
const { action } = await inquirer_1.default.prompt([
|
|
63
|
-
{
|
|
64
|
-
type: 'list',
|
|
65
|
-
name: 'action',
|
|
66
|
-
message: 'What would you like to do?',
|
|
67
|
-
choices: [
|
|
68
|
-
{ name: 'Execute', value: 'execute' },
|
|
69
|
-
{ name: 'Revise', value: 'revise' },
|
|
70
|
-
{ name: 'Explain', value: 'explain' },
|
|
71
|
-
{ name: 'Copy to Clipboard', value: 'copy' },
|
|
72
|
-
{ name: 'Cancel', value: 'cancel' },
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
]);
|
|
76
|
-
switch (action) {
|
|
77
|
-
case 'execute':
|
|
78
|
-
(0, child_process_1.execSync)(generatedShellWithoutShebang, { stdio: 'inherit' });
|
|
79
|
-
break;
|
|
80
|
-
case 'revise':
|
|
81
|
-
// Logic to revise the prompt (not implemented in this example)
|
|
82
|
-
console.log('Revise option selected. Implement revision logic here.');
|
|
83
|
-
break;
|
|
84
|
-
case 'explain':
|
|
85
|
-
const explainPrompt = generateExplainShellPrompt(generatedShellWithoutShebang);
|
|
86
|
-
const explainShellCommand = await gpt.generateText({ prompt: explainPrompt });
|
|
87
|
-
console.log((0, cli_highlight_1.default)(explainShellCommand, { language: 'markdown' }));
|
|
88
|
-
break;
|
|
89
|
-
case 'copy':
|
|
90
|
-
copy_paste_1.default.copy(generatedShellWithoutShebang, (err) => {
|
|
91
|
-
if (err) {
|
|
92
|
-
console.error('Error: Unable to copy to clipboard.', err);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
console.log('Script copied to clipboard.');
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
break;
|
|
99
|
-
case 'cancel':
|
|
100
|
-
console.log('Operation cancelled.');
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
114
|
+
const rawShellCommand = await generateShell(gpt, prompt);
|
|
115
|
+
displayGeneratedShell(rawShellCommand);
|
|
116
|
+
await handleUserAction(gpt, rawShellCommand, prompt);
|
|
103
117
|
}
|
|
104
118
|
catch (error) {
|
|
105
|
-
console.error('Could not generate shell script');
|
|
119
|
+
console.error('Could not generate shell script', error);
|
|
106
120
|
}
|
|
107
121
|
};
|
|
108
122
|
exports.default = shell;
|
|
@@ -6,8 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const fs_1 = require("fs");
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
9
11
|
const git_utils_1 = require("../../utils/git_utils");
|
|
10
12
|
const config_utils_1 = require("../../utils/config_utils");
|
|
13
|
+
const string_utils_1 = require("../../utils/string_utils");
|
|
11
14
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
12
15
|
const extractChangedFiles = (gitDiff) => {
|
|
13
16
|
const fileRegex = /diff --git a\/(.+?) b\/(.+?)\n/g;
|
|
@@ -20,33 +23,27 @@ const extractChangedFiles = (gitDiff) => {
|
|
|
20
23
|
};
|
|
21
24
|
const generatePrompt = (files, srcFiles, testFiles, gitDiff) => {
|
|
22
25
|
const filesList = files.length > 0 ? files.join('\n') : 'No files provided';
|
|
23
|
-
|
|
24
|
-
return `Here is a list of code files I want to generate unit tests for:
|
|
26
|
+
return `### List of Code Files
|
|
25
27
|
|
|
26
|
-
==== begin list of files ====
|
|
27
28
|
${filesList}
|
|
28
|
-
==== end list of files ====
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
### List of All Source Files
|
|
31
31
|
|
|
32
|
-
==== begin list of source files ====
|
|
33
32
|
${srcFiles}
|
|
34
|
-
==== end list of source files ====
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
### List of all test files
|
|
37
35
|
|
|
38
|
-
==== begin list of test files ====
|
|
39
36
|
${testFiles}
|
|
40
|
-
==== end list of test files ====
|
|
41
37
|
|
|
42
|
-
|
|
38
|
+
### Git Diff Content
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
${gitDiff}
|
|
45
41
|
|
|
46
|
-
|
|
42
|
+
## Example Output
|
|
47
43
|
|
|
48
44
|
Give me this output format for your answer:
|
|
49
45
|
|
|
46
|
+
\`\`\`plaintext
|
|
50
47
|
## files
|
|
51
48
|
|
|
52
49
|
app/models/foo.rb
|
|
@@ -55,7 +52,109 @@ app/views/baz/bar.html.erb
|
|
|
55
52
|
## relevant test files
|
|
56
53
|
|
|
57
54
|
test/models/foo_test.rb
|
|
58
|
-
test/controllers/baz_controller_test.rb
|
|
55
|
+
test/controllers/baz_controller_test.rb
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
### Request
|
|
59
|
+
|
|
60
|
+
Please enumerate all the files in the provided list${gitDiff ? ' and git diff' : ''} as well as the file names of anything that these files interact with.
|
|
61
|
+
|
|
62
|
+
Also, list any test files that might be relevant to these files.`;
|
|
63
|
+
};
|
|
64
|
+
const handleUserAction = async (analysis) => {
|
|
65
|
+
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(analysis);
|
|
66
|
+
const { action } = await inquirer_1.default.prompt([
|
|
67
|
+
{
|
|
68
|
+
type: 'list',
|
|
69
|
+
name: 'action',
|
|
70
|
+
message: 'What would you like to do?',
|
|
71
|
+
choices: [
|
|
72
|
+
{ name: 'Save a Unit Test File', value: 'save' },
|
|
73
|
+
{ name: 'Save All Unit Test Files', value: 'saveAll' },
|
|
74
|
+
{ name: 'Copy Unit Test to Clipboard', value: 'copyTest' },
|
|
75
|
+
{ name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
|
|
76
|
+
{ name: 'Done', value: 'done' },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
switch (action) {
|
|
81
|
+
case 'save':
|
|
82
|
+
let saveAnother = true;
|
|
83
|
+
while (saveAnother) {
|
|
84
|
+
const { fileToSave } = await inquirer_1.default.prompt([
|
|
85
|
+
{
|
|
86
|
+
type: 'list',
|
|
87
|
+
name: 'fileToSave',
|
|
88
|
+
message: 'Which unit test file do you want to save?',
|
|
89
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
90
|
+
},
|
|
91
|
+
]);
|
|
92
|
+
if (fileContents[fileToSave]) {
|
|
93
|
+
const filePath = path_1.default.resolve(fileToSave);
|
|
94
|
+
fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true })
|
|
95
|
+
.then(() => fs_1.promises.writeFile(filePath, fileContents[fileToSave]))
|
|
96
|
+
.then(() => console.log(`${fileToSave} has been saved.`))
|
|
97
|
+
.catch((error) => console.error(`Error saving ${fileToSave}:`, error));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log('File content not found.');
|
|
101
|
+
}
|
|
102
|
+
const { saveMore } = await inquirer_1.default.prompt([
|
|
103
|
+
{
|
|
104
|
+
type: 'confirm',
|
|
105
|
+
name: 'saveMore',
|
|
106
|
+
message: 'Do you want to save another file?',
|
|
107
|
+
default: false,
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
saveAnother = saveMore;
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case 'saveAll':
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
if (fileContents[file]) {
|
|
116
|
+
const filePath = path_1.default.resolve(file);
|
|
117
|
+
fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true })
|
|
118
|
+
.then(() => fs_1.promises.writeFile(filePath, fileContents[file]))
|
|
119
|
+
.then(() => console.log(`${file} has been saved.`))
|
|
120
|
+
.catch((error) => console.error(`Error saving ${file}:`, error));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(`File content not found for ${file}.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
console.log('All unit test files have been saved.');
|
|
127
|
+
break;
|
|
128
|
+
case 'copyTest':
|
|
129
|
+
const { fileToCopy } = await inquirer_1.default.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'fileToCopy',
|
|
133
|
+
message: 'Which unit test file do you want to copy to the clipboard?',
|
|
134
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
if (fileContents[fileToCopy]) {
|
|
138
|
+
process.nextTick(() => {
|
|
139
|
+
require('copy-paste').copy(fileContents[fileToCopy], () => {
|
|
140
|
+
console.log(`${fileToCopy} copied to clipboard`);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log('File content not found.');
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
case 'copyAll':
|
|
149
|
+
process.nextTick(() => {
|
|
150
|
+
require('copy-paste').copy(analysis, () => {
|
|
151
|
+
console.log('Entire response copied to clipboard');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
break;
|
|
155
|
+
case 'done':
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
59
158
|
};
|
|
60
159
|
const unitTests = async (options) => {
|
|
61
160
|
const { files } = options;
|
|
@@ -88,9 +187,9 @@ const unitTests = async (options) => {
|
|
|
88
187
|
}
|
|
89
188
|
});
|
|
90
189
|
for (const codeFile of lines) {
|
|
91
|
-
if (await fs_1.promises
|
|
190
|
+
if (await fs_1.promises.stat(codeFile).then(stat => stat.isFile()).catch(() => false)) {
|
|
92
191
|
const fileContents = await fs_1.promises.readFile(codeFile, 'utf-8');
|
|
93
|
-
context.push(`###
|
|
192
|
+
context.push(`### Contents of ${codeFile}\n\n${fileContents}\n\n### End of ${codeFile}`);
|
|
94
193
|
}
|
|
95
194
|
}
|
|
96
195
|
// Generate the message for unit test creation
|
|
@@ -98,11 +197,11 @@ const unitTests = async (options) => {
|
|
|
98
197
|
if ((files || []).length > 0) {
|
|
99
198
|
const message = `please create unit tests for these files:
|
|
100
199
|
|
|
101
|
-
##
|
|
200
|
+
## Begin list of files
|
|
102
201
|
${(files || []).join('\n')}
|
|
103
|
-
##
|
|
202
|
+
## End list of files
|
|
104
203
|
|
|
105
|
-
##
|
|
204
|
+
## Context
|
|
106
205
|
${context.join('\n\n')}`;
|
|
107
206
|
// Get the model and analysis using ClovingGPT
|
|
108
207
|
analysis = await gpt.generateText({ prompt: message });
|
|
@@ -110,16 +209,18 @@ ${context.join('\n\n')}`;
|
|
|
110
209
|
else {
|
|
111
210
|
const message = `please create unit tests for these changes:
|
|
112
211
|
|
|
113
|
-
##
|
|
212
|
+
## Begin diff
|
|
114
213
|
${await (0, git_utils_1.getGitDiff)()}
|
|
115
|
-
##
|
|
214
|
+
## End diff
|
|
116
215
|
|
|
117
|
-
##
|
|
216
|
+
## Context
|
|
118
217
|
|
|
119
218
|
${context.join('\n\n')}`;
|
|
120
219
|
// Get the model and analysis using ClovingGPT
|
|
121
220
|
analysis = await gpt.generateText({ prompt: message });
|
|
122
221
|
}
|
|
123
222
|
console.log((0, cli_highlight_1.default)(analysis));
|
|
223
|
+
// Add the new user action handling
|
|
224
|
+
await handleUserAction(analysis);
|
|
124
225
|
};
|
|
125
226
|
exports.default = unitTests;
|
|
@@ -64,7 +64,13 @@ const buildProject = async (options) => {
|
|
|
64
64
|
const lines = code.split('\n');
|
|
65
65
|
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
66
66
|
console.log(lines[0]);
|
|
67
|
-
|
|
67
|
+
try {
|
|
68
|
+
console.log((0, cli_highlight_1.default)(lines.slice(1, -1).join('\n'), { language }));
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
// don't highlight if it fails
|
|
72
|
+
console.log(lines.slice(1, -1).join('\n'));
|
|
73
|
+
}
|
|
68
74
|
console.log(lines.slice(-1)[0]);
|
|
69
75
|
}
|
|
70
76
|
else {
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ const generate = program
|
|
|
59
59
|
.description('Generate various items like unit-tests and code reviews');
|
|
60
60
|
generate
|
|
61
61
|
.command('shell')
|
|
62
|
-
.alias('
|
|
62
|
+
.alias('sh')
|
|
63
63
|
.description('Generate a shell command based on a prompt')
|
|
64
64
|
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
65
65
|
.option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5-sonnet-20240620)')
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloving",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -68,5 +68,10 @@
|
|
|
68
68
|
"lint-staged": "^15.2.7",
|
|
69
69
|
"ts-jest": "^29.2.0",
|
|
70
70
|
"typescript": "^5.5.3"
|
|
71
|
+
},
|
|
72
|
+
"resolutions": {
|
|
73
|
+
"eslint": "8.57.0",
|
|
74
|
+
"eslint-plugin-n": "16.6.2",
|
|
75
|
+
"eslint-plugin-import": "2.29.1"
|
|
71
76
|
}
|
|
72
77
|
}
|
|
@@ -61,7 +61,8 @@ const getClovingConfig = () => {
|
|
|
61
61
|
return clovingConfig;
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
64
|
-
|
|
64
|
+
console.log(`${exports.CLOVINGFILE_PATH} file not found, please run: cloving init`);
|
|
65
|
+
process.exit(1);
|
|
65
66
|
}
|
|
66
67
|
};
|
|
67
68
|
exports.getClovingConfig = getClovingConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseMarkdownInstructions = exports.extractMarkdown = exports.extractJsonMetadata = exports.estimateTokens = void 0;
|
|
3
|
+
exports.extractFilesAndContent = exports.parseMarkdownInstructions = exports.extractMarkdown = exports.extractJsonMetadata = exports.estimateTokens = void 0;
|
|
4
4
|
// Function to estimate token count
|
|
5
5
|
const estimateTokens = async (text) => {
|
|
6
6
|
const charCount = text.length;
|
|
@@ -91,3 +91,25 @@ const parseMarkdownInstructions = (input) => {
|
|
|
91
91
|
return result;
|
|
92
92
|
};
|
|
93
93
|
exports.parseMarkdownInstructions = parseMarkdownInstructions;
|
|
94
|
+
const extractFilesAndContent = (rawCodeCommand) => {
|
|
95
|
+
const files = [];
|
|
96
|
+
const fileContents = {};
|
|
97
|
+
const matches = rawCodeCommand.match(/(\*{2})([^\*]+)(\*{2})/g);
|
|
98
|
+
if (!matches)
|
|
99
|
+
return [files, fileContents];
|
|
100
|
+
for (const match of matches) {
|
|
101
|
+
const fileName = match.replace(/\*{2}/g, '').trim();
|
|
102
|
+
const escapedFileName = fileName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
103
|
+
const regex = new RegExp(`\\*\\*${escapedFileName}\\*\\*\\n\\n\\\`{3}([\\s\\S]+?)\\\`{3}`, 'g');
|
|
104
|
+
const contentMatch = regex.exec(rawCodeCommand);
|
|
105
|
+
if (contentMatch) {
|
|
106
|
+
files.push(fileName);
|
|
107
|
+
let content = contentMatch[1];
|
|
108
|
+
// Remove the first word after the opening triple backticks
|
|
109
|
+
content = content.split('\n').map((line, idx) => idx === 0 ? line.replace(/^\w+\s*/, '') : line).join('\n');
|
|
110
|
+
fileContents[fileName] = content;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return [files, fileContents];
|
|
114
|
+
};
|
|
115
|
+
exports.extractFilesAndContent = extractFilesAndContent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloving",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -68,5 +68,10 @@
|
|
|
68
68
|
"lint-staged": "^15.2.7",
|
|
69
69
|
"ts-jest": "^29.2.0",
|
|
70
70
|
"typescript": "^5.5.3"
|
|
71
|
+
},
|
|
72
|
+
"resolutions": {
|
|
73
|
+
"eslint": "8.57.0",
|
|
74
|
+
"eslint-plugin-n": "16.6.2",
|
|
75
|
+
"eslint-plugin-import": "2.29.1"
|
|
71
76
|
}
|
|
72
77
|
}
|