cloving 0.1.9 → 0.1.11
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 +62 -0
- package/dist/cloving_gpt/adapters/mistral.js +46 -0
- package/dist/cloving_gpt/adapters/openai.js +1 -1
- package/dist/cloving_gpt/index.js +8 -4
- package/dist/commands/generate/code.js +208 -48
- package/dist/commands/generate/fizzbuzz.js +35 -0
- package/dist/commands/generate/review.js +103 -23
- package/dist/commands/generate/shell.js +67 -46
- package/dist/commands/generate/unit_tests.js +142 -22
- package/dist/commands/models.js +5 -2
- package/dist/index.js +1 -1
- package/dist/package.json +3 -2
- package/dist/utils/config_utils.js +2 -1
- package/package.json +6 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GeminiAdapter = void 0;
|
|
4
|
+
class GeminiAdapter {
|
|
5
|
+
constructor(model) {
|
|
6
|
+
this.model = model;
|
|
7
|
+
}
|
|
8
|
+
static async listSupportedModels() {
|
|
9
|
+
GeminiAdapter.supportedModels.forEach(model => {
|
|
10
|
+
console.log(model);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
getEndpoint() {
|
|
14
|
+
return `https://generativelanguage.googleapis.com/v1beta/models/${this.model.replace(':', '-')}:generateContent`;
|
|
15
|
+
}
|
|
16
|
+
getHeaders(apiKey) {
|
|
17
|
+
return {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'x-goog-api-key': apiKey
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
getPayload(request) {
|
|
23
|
+
return {
|
|
24
|
+
contents: [
|
|
25
|
+
{
|
|
26
|
+
parts: [
|
|
27
|
+
{
|
|
28
|
+
text: request.prompt
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
generationConfig: {
|
|
34
|
+
temperature: 0.2,
|
|
35
|
+
topK: 40,
|
|
36
|
+
topP: 0.95,
|
|
37
|
+
maxOutputTokens: 1024
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
extractResponse(data) {
|
|
42
|
+
try {
|
|
43
|
+
if (data && data.candidates && data.candidates[0] && data.candidates[0].content && data.candidates[0].content.parts && data.candidates[0].content.parts[0]) {
|
|
44
|
+
return data.candidates[0].content.parts[0].text;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.error('Unexpected response structure:', data);
|
|
48
|
+
throw new Error('Invalid response structure');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error('Error extracting response:', error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.GeminiAdapter = GeminiAdapter;
|
|
58
|
+
GeminiAdapter.supportedModels = [
|
|
59
|
+
'gemini:gemini-1.5:pro-latest',
|
|
60
|
+
'gemini:gemini-1.5:flash-latest',
|
|
61
|
+
'gemini:gemini-1.0:pro-latest',
|
|
62
|
+
];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MistralAdapter = void 0;
|
|
4
|
+
class MistralAdapter {
|
|
5
|
+
static listSupportedModels() {
|
|
6
|
+
MistralAdapter.supportedModels.forEach(model => {
|
|
7
|
+
console.log(model);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
constructor(model) {
|
|
11
|
+
this.model = model;
|
|
12
|
+
}
|
|
13
|
+
getEndpoint() {
|
|
14
|
+
return 'https://api.mistral.ai/v1/chat/completions';
|
|
15
|
+
}
|
|
16
|
+
getHeaders(apiKey) {
|
|
17
|
+
return {
|
|
18
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
19
|
+
'Content-Type': 'application/json'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
getPayload(request) {
|
|
23
|
+
return {
|
|
24
|
+
model: this.model.replace(':', '-'),
|
|
25
|
+
messages: [{ role: "user", content: request.prompt }],
|
|
26
|
+
max_tokens: request.maxTokens,
|
|
27
|
+
temperature: request.temperature || 0.2,
|
|
28
|
+
stream: false
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
extractResponse(data) {
|
|
32
|
+
return data.choices[0].message.content;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.MistralAdapter = MistralAdapter;
|
|
36
|
+
MistralAdapter.supportedModels = [
|
|
37
|
+
'mistral:mistral:embed',
|
|
38
|
+
'mistral:mistral:tiny-latest',
|
|
39
|
+
'mistral:mistral:small-latest',
|
|
40
|
+
'mistral:mistral:medium-latest',
|
|
41
|
+
'mistral:mistral:large-latest',
|
|
42
|
+
'mistral:open-mistral:7b',
|
|
43
|
+
'mistral:open-mixtral:8x7b',
|
|
44
|
+
'mistral:open-mixtral:8x22b',
|
|
45
|
+
'mistral:codestral:latest',
|
|
46
|
+
];
|
|
@@ -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) {
|
|
@@ -9,8 +9,9 @@ const process_1 = __importDefault(require("process"));
|
|
|
9
9
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
10
|
const claude_1 = require("./adapters/claude");
|
|
11
11
|
const openai_1 = require("./adapters/openai");
|
|
12
|
-
const
|
|
12
|
+
const mistral_1 = require("./adapters/mistral");
|
|
13
13
|
const ollama_1 = require("./adapters/ollama");
|
|
14
|
+
const gemini_1 = require("./adapters/gemini");
|
|
14
15
|
const config_utils_1 = require("../utils/config_utils");
|
|
15
16
|
class ClovingGPT {
|
|
16
17
|
constructor(options = { silent: false }) {
|
|
@@ -35,12 +36,15 @@ class ClovingGPT {
|
|
|
35
36
|
case 'openai':
|
|
36
37
|
this.adapter = new openai_1.OpenAIAdapter(model);
|
|
37
38
|
break;
|
|
38
|
-
case '
|
|
39
|
-
this.adapter = new
|
|
39
|
+
case 'mistral':
|
|
40
|
+
this.adapter = new mistral_1.MistralAdapter(model);
|
|
40
41
|
break;
|
|
41
42
|
case 'ollama':
|
|
42
43
|
this.adapter = new ollama_1.OllamaAdapter(model);
|
|
43
44
|
break;
|
|
45
|
+
case 'gemini':
|
|
46
|
+
this.adapter = new gemini_1.GeminiAdapter(model);
|
|
47
|
+
break;
|
|
44
48
|
default:
|
|
45
49
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
46
50
|
}
|
|
@@ -77,7 +81,7 @@ class ClovingGPT {
|
|
|
77
81
|
}
|
|
78
82
|
]);
|
|
79
83
|
if (!confirmContinue) {
|
|
80
|
-
console.error('
|
|
84
|
+
console.error('Operation cancelled');
|
|
81
85
|
process_1.default.exit(1);
|
|
82
86
|
}
|
|
83
87
|
resolve();
|
|
@@ -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
|
|
|
@@ -36,11 +36,60 @@ ${previousCode}
|
|
|
36
36
|
|
|
37
37
|
`;
|
|
38
38
|
}
|
|
39
|
-
promptText += `###
|
|
39
|
+
promptText += `### Example of a well-structured response
|
|
40
40
|
|
|
41
|
-
I would like to generate
|
|
41
|
+
Here are the files that I would like to generate:
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
1. **src/commands/generate/shell.ts**
|
|
44
|
+
|
|
45
|
+
\`\`\`typescript
|
|
46
|
+
import inquirer from 'inquirer'
|
|
47
|
+
import highlight from 'cli-highlight'
|
|
48
|
+
import { execSync } from 'child_process'
|
|
49
|
+
import ncp from 'copy-paste'
|
|
50
|
+
import { extractMarkdown } from '../../utils/string_utils'
|
|
51
|
+
import { getConfig } from '../../utils/config_utils'
|
|
52
|
+
import ClovingGPT from '../../cloving_gpt'
|
|
53
|
+
import type { ClovingGPTOptions } from '../../utils/types'
|
|
54
|
+
|
|
55
|
+
const generateShellPrompt = (prompt: string | undefined): string => {
|
|
56
|
+
const shell = execSync('echo $SHELL').toString().trim()
|
|
57
|
+
const os = execSync('echo $OSTYPE').toString().trim()
|
|
58
|
+
return \`Generate an executable \${shell} script that works on \${os}. Try to make it a single line if possible and as simple and straightforward as possible.
|
|
59
|
+
|
|
60
|
+
Do not add any commentary or context to the message other than the commit message itself.
|
|
61
|
+
|
|
62
|
+
An example of the output for this should look like the following:
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
2. **src/commands/generate/code.ts**
|
|
66
|
+
|
|
67
|
+
\`\`\`typescript
|
|
68
|
+
import ncp from 'copy-paste'
|
|
69
|
+
import inquirer from 'inquirer'
|
|
70
|
+
import highlight from 'cli-highlight'
|
|
71
|
+
import { collectSpecialFileContents } from '../../utils/command_utils'
|
|
72
|
+
import { getConfig, getClovingConfig, getAllFiles } from '../../utils/config_utils'
|
|
73
|
+
import { parseMarkdownInstructions, extractMarkdown } from '../../utils/string_utils'
|
|
74
|
+
import ClovingGPT from '../../cloving_gpt'
|
|
75
|
+
import type { ClovingGPTOptions } from '../../utils/types'
|
|
76
|
+
import fs from 'fs'
|
|
77
|
+
import path from 'path'
|
|
78
|
+
|
|
79
|
+
const generateCodePrompt = (prompt: string | undefined, files: string[], contextFiles: Record<string, string>, previousCode?: string): string => {
|
|
80
|
+
const specialFileContents = collectSpecialFileContents()
|
|
81
|
+
const specialFiles = Object.keys(specialFileContents).map((file) => \`### Contents of \${file}\\n\\n\${JSON.stringify(specialFileContents[file], null, 2)}\\n\\n\`).join('\\n')
|
|
82
|
+
const contextFileContents = Object.keys(contextFiles).map((file) => \`### Contents of \${file}\\n\\n\${contextFiles[file]}\\n\\n\`).join('\\n')
|
|
83
|
+
|
|
84
|
+
return 'Here is a description of my app:'
|
|
85
|
+
}
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
### Request
|
|
89
|
+
|
|
90
|
+
Generate code that does the following: ${prompt}
|
|
91
|
+
|
|
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.`;
|
|
44
93
|
return promptText;
|
|
45
94
|
};
|
|
46
95
|
const generateExplainCodePrompt = (prompt) => {
|
|
@@ -66,7 +115,28 @@ const displayGeneratedCode = (rawCodeCommand) => {
|
|
|
66
115
|
}
|
|
67
116
|
});
|
|
68
117
|
};
|
|
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
|
+
};
|
|
69
138
|
const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contextFiles) => {
|
|
139
|
+
const [files, fileContents] = extractFilesAndContent(rawCodeCommand);
|
|
70
140
|
const { action } = await inquirer_1.default.prompt([
|
|
71
141
|
{
|
|
72
142
|
type: 'list',
|
|
@@ -75,9 +145,11 @@ const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contex
|
|
|
75
145
|
choices: [
|
|
76
146
|
{ name: 'Revise', value: 'revise' },
|
|
77
147
|
{ name: 'Explain', value: 'explain' },
|
|
148
|
+
{ name: 'Save a Source Code File', value: 'save' },
|
|
149
|
+
{ name: 'Save All Source Code Files', value: 'saveAll' },
|
|
78
150
|
{ name: 'Copy Source Code to Clipboard', value: 'copySource' },
|
|
79
151
|
{ name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
|
|
80
|
-
{ name: '
|
|
152
|
+
{ name: 'Done', value: 'done' },
|
|
81
153
|
],
|
|
82
154
|
},
|
|
83
155
|
]);
|
|
@@ -87,7 +159,7 @@ const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contex
|
|
|
87
159
|
{
|
|
88
160
|
type: 'input',
|
|
89
161
|
name: 'newPrompt',
|
|
90
|
-
message: '
|
|
162
|
+
message: 'How would you like to modify the output:',
|
|
91
163
|
},
|
|
92
164
|
]);
|
|
93
165
|
const newRawCodeCommand = await generateCode(gpt, newPrompt, allSrcFiles, contextFiles, rawCodeCommand);
|
|
@@ -100,34 +172,155 @@ const handleUserAction = async (gpt, rawCodeCommand, prompt, allSrcFiles, contex
|
|
|
100
172
|
console.log((0, cli_highlight_1.default)(explainCodeCommand, { language: 'markdown' }));
|
|
101
173
|
break;
|
|
102
174
|
case 'copySource':
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
175
|
+
let copyAnother = true;
|
|
176
|
+
while (copyAnother) {
|
|
177
|
+
const { fileToCopy } = await inquirer_1.default.prompt([
|
|
178
|
+
{
|
|
179
|
+
type: 'list',
|
|
180
|
+
name: 'fileToCopy',
|
|
181
|
+
message: 'Which file do you want to copy to the clipboard?',
|
|
182
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
183
|
+
},
|
|
184
|
+
]);
|
|
185
|
+
if (fileContents[fileToCopy]) {
|
|
186
|
+
copy_paste_1.default.copy(fileContents[fileToCopy], () => {
|
|
187
|
+
console.log(`${fileToCopy} copied to clipboard`);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log('File content not found.');
|
|
192
|
+
}
|
|
193
|
+
const { copyMore } = await inquirer_1.default.prompt([
|
|
194
|
+
{
|
|
195
|
+
type: 'confirm',
|
|
196
|
+
name: 'copyMore',
|
|
197
|
+
message: 'Do you want to copy another file?',
|
|
198
|
+
default: true,
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
copyAnother = copyMore;
|
|
202
|
+
}
|
|
107
203
|
break;
|
|
108
204
|
case 'copyAll':
|
|
109
205
|
copy_paste_1.default.copy(rawCodeCommand, () => {
|
|
110
206
|
console.log('Entire response copied to clipboard');
|
|
111
207
|
});
|
|
112
208
|
break;
|
|
113
|
-
case '
|
|
114
|
-
|
|
209
|
+
case 'save':
|
|
210
|
+
let saveAnother = true;
|
|
211
|
+
while (saveAnother) {
|
|
212
|
+
const { fileToSave } = await inquirer_1.default.prompt([
|
|
213
|
+
{
|
|
214
|
+
type: 'list',
|
|
215
|
+
name: 'fileToSave',
|
|
216
|
+
message: 'Which file do you want to save?',
|
|
217
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
218
|
+
},
|
|
219
|
+
]);
|
|
220
|
+
if (fileContents[fileToSave]) {
|
|
221
|
+
const filePath = path_1.default.resolve(fileToSave);
|
|
222
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
223
|
+
fs_1.default.writeFileSync(filePath, fileContents[fileToSave]);
|
|
224
|
+
console.log(`${fileToSave} has been saved.`);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.log('File content not found.');
|
|
228
|
+
}
|
|
229
|
+
const { saveMore } = await inquirer_1.default.prompt([
|
|
230
|
+
{
|
|
231
|
+
type: 'confirm',
|
|
232
|
+
name: 'saveMore',
|
|
233
|
+
message: 'Do you want to save another file?',
|
|
234
|
+
default: true,
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
saveAnother = saveMore;
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
case 'saveAll':
|
|
241
|
+
files.forEach(file => {
|
|
242
|
+
if (fileContents[file]) {
|
|
243
|
+
const filePath = path_1.default.resolve(file);
|
|
244
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
245
|
+
fs_1.default.writeFileSync(filePath, fileContents[file]);
|
|
246
|
+
console.log(`${file} has been saved.`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.log(`File content not found for ${file}.`);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
console.log('All files have been saved.');
|
|
253
|
+
break;
|
|
254
|
+
case 'done':
|
|
115
255
|
break;
|
|
116
256
|
}
|
|
117
257
|
};
|
|
258
|
+
const addFilesToContext = async (contextFiles, filePath) => {
|
|
259
|
+
const stats = fs_1.default.statSync(filePath);
|
|
260
|
+
if (stats.isDirectory()) {
|
|
261
|
+
const files = fs_1.default.readdirSync(filePath);
|
|
262
|
+
for (const file of files) {
|
|
263
|
+
await addFilesToContext(contextFiles, path_1.default.join(filePath, file));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else if (stats.isFile()) {
|
|
267
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
268
|
+
contextFiles[filePath] = content;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
118
271
|
const code = async (options) => {
|
|
119
272
|
let { prompt, files } = options;
|
|
120
273
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
121
274
|
const gpt = new cloving_gpt_1.default(options);
|
|
122
|
-
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options,
|
|
275
|
+
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, false);
|
|
123
276
|
const contextFiles = {};
|
|
124
277
|
try {
|
|
278
|
+
if (files) {
|
|
279
|
+
files.forEach(async (file) => {
|
|
280
|
+
await addFilesToContext(contextFiles, file);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
let firstFile = true;
|
|
285
|
+
let includeMoreFiles = true;
|
|
286
|
+
while (includeMoreFiles) {
|
|
287
|
+
const { includeMore } = await inquirer_1.default.prompt([
|
|
288
|
+
{
|
|
289
|
+
type: 'confirm',
|
|
290
|
+
name: 'includeMore',
|
|
291
|
+
message: `Do you want to include any${firstFile ? '' : ' other'} files or directories as context for the prompt?`,
|
|
292
|
+
default: false,
|
|
293
|
+
},
|
|
294
|
+
]);
|
|
295
|
+
firstFile = false;
|
|
296
|
+
includeMoreFiles = includeMore;
|
|
297
|
+
if (includeMoreFiles) {
|
|
298
|
+
const { contextFile } = await inquirer_1.default.prompt([
|
|
299
|
+
{
|
|
300
|
+
type: 'input',
|
|
301
|
+
name: 'contextFile',
|
|
302
|
+
message: 'Enter the relative path of a file or directory you would like to include as context:',
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
305
|
+
if (contextFile) {
|
|
306
|
+
const filePath = path_1.default.resolve(contextFile);
|
|
307
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
308
|
+
await addFilesToContext(contextFiles, filePath);
|
|
309
|
+
console.log(`Added ${filePath} to context.`);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
console.log(`File or directory ${contextFile} does not exist.`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
125
318
|
if (!prompt) {
|
|
126
319
|
const { userPrompt } = await inquirer_1.default.prompt([
|
|
127
320
|
{
|
|
128
321
|
type: 'input',
|
|
129
322
|
name: 'userPrompt',
|
|
130
|
-
message: 'What would you like to
|
|
323
|
+
message: 'What would you like the code to do:',
|
|
131
324
|
},
|
|
132
325
|
]);
|
|
133
326
|
prompt = userPrompt;
|
|
@@ -140,40 +333,7 @@ const code = async (options) => {
|
|
|
140
333
|
}
|
|
141
334
|
// Read the content of the provided files
|
|
142
335
|
for (const file of files) {
|
|
143
|
-
|
|
144
|
-
contextFiles[file] = content;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
// Prompt for additional context files if no files were passed in options
|
|
149
|
-
let includeMoreFiles = true;
|
|
150
|
-
while (includeMoreFiles) {
|
|
151
|
-
const { contextFile } = await inquirer_1.default.prompt([
|
|
152
|
-
{
|
|
153
|
-
type: 'input',
|
|
154
|
-
name: 'contextFile',
|
|
155
|
-
message: 'Relative path of an existing file you would like to include as context in the prompt [optional]:',
|
|
156
|
-
},
|
|
157
|
-
]);
|
|
158
|
-
if (contextFile) {
|
|
159
|
-
const filePath = path_1.default.resolve(contextFile);
|
|
160
|
-
if (fs_1.default.existsSync(filePath)) {
|
|
161
|
-
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
162
|
-
contextFiles[filePath] = content;
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
console.log(`File ${contextFile} does not exist.`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const { includeMore } = await inquirer_1.default.prompt([
|
|
169
|
-
{
|
|
170
|
-
type: 'confirm',
|
|
171
|
-
name: 'includeMore',
|
|
172
|
-
message: 'Do you want to include any other files?',
|
|
173
|
-
default: false,
|
|
174
|
-
},
|
|
175
|
-
]);
|
|
176
|
-
includeMoreFiles = includeMore;
|
|
336
|
+
await addFilesToContext(contextFiles, file);
|
|
177
337
|
}
|
|
178
338
|
}
|
|
179
339
|
const rawCodeCommand = await generateCode(gpt, prompt, allSrcFiles, contextFiles);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.fizzbuzzCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
|
+
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
10
|
+
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
11
|
+
const fizzbuzz_utils_1 = require("../../utils/fizzbuzz_utils");
|
|
12
|
+
exports.fizzbuzzCommand = new commander_1.Command('fizzbuzz')
|
|
13
|
+
.description('Generate FizzBuzz sequence')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const answers = await inquirer_1.default.prompt([
|
|
16
|
+
{
|
|
17
|
+
type: 'number',
|
|
18
|
+
name: 'start',
|
|
19
|
+
message: 'Enter the starting number:',
|
|
20
|
+
default: 1,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'number',
|
|
24
|
+
name: 'end',
|
|
25
|
+
message: 'Enter the ending number:',
|
|
26
|
+
default: 100,
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
const fizzBuzzArray = (0, fizzbuzz_utils_1.generateFizzBuzzArray)(answers.start, answers.end);
|
|
30
|
+
const output = fizzBuzzArray.join('\n');
|
|
31
|
+
console.log((0, cli_highlight_1.default)(output, { language: 'plaintext', ignoreIllegals: true }));
|
|
32
|
+
copy_paste_1.default.copy(output, () => {
|
|
33
|
+
console.log('FizzBuzz sequence copied to clipboard!');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -3,30 +3,83 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const child_process_1 = require("child_process");
|
|
7
6
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
8
7
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
9
9
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
10
10
|
const git_utils_1 = require("../../utils/git_utils");
|
|
11
|
-
const string_utils_1 = require("../../utils/string_utils");
|
|
12
11
|
const config_utils_1 = require("../../utils/config_utils");
|
|
13
|
-
const
|
|
12
|
+
const string_utils_1 = require("../../utils/string_utils");
|
|
14
13
|
const review = async (options) => {
|
|
15
14
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
16
15
|
const gpt = new cloving_gpt_1.default(options);
|
|
17
16
|
try {
|
|
18
17
|
// Define the prompt for analysis
|
|
19
18
|
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
20
|
-
const prompt =
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const prompt = `==== begin diff =====
|
|
20
|
+
${gitDiff}
|
|
21
|
+
==== end diff =====
|
|
22
|
+
|
|
23
|
+
### Example of a well-structured response
|
|
24
|
+
|
|
25
|
+
# Code Review: Enhanced Code Generation and File Handling
|
|
26
|
+
|
|
27
|
+
## Changes Overview
|
|
28
|
+
|
|
29
|
+
These changes primarily focus on improving the code generation process and adding more robust file handling capabilities. The main modifications include:
|
|
30
|
+
|
|
31
|
+
1. Enhancing the code generation prompt with a structured example response.
|
|
32
|
+
2. Implementing a new function to extract file names and contents from the generated code.
|
|
33
|
+
3. Adding a new option to save generated code directly to files.
|
|
34
|
+
4. Improving the copy-to-clipboard functionality to allow copying individual files.
|
|
35
|
+
5. Removing an unused import in the review.ts file.
|
|
36
|
+
|
|
37
|
+
## Reason for Changes
|
|
38
|
+
|
|
39
|
+
The changes aim to enhance the usability and flexibility of the code generation tool by providing clearer prompts, better file management, and more control over the generated code.
|
|
40
|
+
|
|
41
|
+
## Detailed Description
|
|
23
42
|
|
|
24
|
-
|
|
43
|
+
1. **Enhanced Code Generation Prompt** - The \`generateCodePrompt\` function now includes a structured example of a well-formatted response. This helps guide the AI to produce more consistent and usable output, making it easier to parse and use the generated code.
|
|
44
|
+
2. **File Extraction Function** - A new function \`extractFilesAndContent\` has been added to parse the raw code command and extract individual file names and their contents. This enables more granular control over the generated code, allowing for file-specific operations.
|
|
45
|
+
3. **Save to File Option** - A new 'Save Source Code to Files' option has been added to the user action menu. This feature allows users to save generated code directly to files in the appropriate directory structure.
|
|
46
|
+
4. **Improved Copy to Clipboard** - The 'Copy Source Code to Clipboard' option has been enhanced to allow users to choose specific files to copy, rather than copying all generated code at once.
|
|
47
|
+
5. **Removed Unused Import** - The unused import of \`execFileSync\` from 'child_process' has been removed from the review.ts file, improving code cleanliness.
|
|
48
|
+
|
|
49
|
+
## Potential Bugs and Recommended Fixes
|
|
50
|
+
|
|
51
|
+
### 1. Possible undefined access in \`extractFilesAndContent\`
|
|
52
|
+
|
|
53
|
+
\`\`\`typescript
|
|
54
|
+
const contentMatch = regex.exec(rawCodeCommand)
|
|
55
|
+
if (contentMatch) {
|
|
56
|
+
files.push(fileName)
|
|
57
|
+
let content = contentMatch[1]
|
|
58
|
+
// ...
|
|
59
|
+
}
|
|
60
|
+
\`\`\`
|
|
61
|
+
|
|
62
|
+
There's a potential for \`contentMatch[1]\` to be undefined if the regex doesn't capture any groups. To fix this, add a null check:
|
|
63
|
+
|
|
64
|
+
\`\`\`typescript
|
|
65
|
+
const contentMatch = regex.exec(rawCodeCommand)
|
|
66
|
+
if (contentMatch && contentMatch[1]) {
|
|
67
|
+
files.push(fileName)
|
|
68
|
+
let content = contentMatch[1]
|
|
69
|
+
// ...
|
|
70
|
+
}
|
|
71
|
+
\`\`\`
|
|
72
|
+
|
|
73
|
+
### Request
|
|
74
|
+
|
|
75
|
+
Do not use any data from the example response structure, only use the structure.
|
|
76
|
+
I would like you to explain why these change are being made and document a description of these changes.
|
|
77
|
+
Also list any bugs in the new code as well as recommended fixes for those bugs with code examples.
|
|
78
|
+
Format the output of this code review in Markdown format.`;
|
|
25
79
|
// get the analysis
|
|
26
80
|
const analysis = await gpt.generateText({ prompt });
|
|
27
|
-
const markdown = (0, string_utils_1.extractMarkdown)(analysis);
|
|
28
81
|
// Print the analysis to the console
|
|
29
|
-
(0,
|
|
82
|
+
(0, string_utils_1.parseMarkdownInstructions)(analysis).map(code => {
|
|
30
83
|
if (code.trim().startsWith('```')) {
|
|
31
84
|
const lines = code.split('\n');
|
|
32
85
|
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
@@ -38,23 +91,50 @@ ${gitDiff}`;
|
|
|
38
91
|
console.log((0, cli_highlight_1.default)(code, { language: 'markdown' }));
|
|
39
92
|
}
|
|
40
93
|
});
|
|
41
|
-
//
|
|
42
|
-
const
|
|
94
|
+
// Modified Function to extract specific sections from the analysis
|
|
95
|
+
const extractSection = (section) => {
|
|
96
|
+
const regex = new RegExp(`## ${section}[\\s\\S]*?(?=\\n## Potential Bugs and Recommended Fixes|$)`, 'g');
|
|
97
|
+
const match = analysis.match(regex);
|
|
98
|
+
return match ? match[0].trim() : '';
|
|
99
|
+
};
|
|
100
|
+
// Prompt the user with options for copying to clipboard
|
|
101
|
+
const { clipboardOption } = await inquirer_1.default.prompt([
|
|
43
102
|
{
|
|
44
|
-
type: '
|
|
45
|
-
name: '
|
|
46
|
-
message: '
|
|
47
|
-
|
|
103
|
+
type: 'list',
|
|
104
|
+
name: 'clipboardOption',
|
|
105
|
+
message: 'What would you like to copy to the clipboard?',
|
|
106
|
+
choices: [
|
|
107
|
+
{ name: 'Copy only the Changes Overview', value: 'Changes Overview' },
|
|
108
|
+
{ name: 'Copy only the Potential Bugs and Recommended Fixes', value: 'Potential Bugs and Recommended Fixes' },
|
|
109
|
+
{ name: 'Copy everything', value: 'Everything' },
|
|
110
|
+
{ name: 'Done', value: 'Done' }
|
|
111
|
+
]
|
|
48
112
|
}
|
|
49
113
|
]);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
114
|
+
let contentToCopy = '';
|
|
115
|
+
switch (clipboardOption) {
|
|
116
|
+
case 'Changes Overview':
|
|
117
|
+
contentToCopy = extractSection('Changes Overview');
|
|
118
|
+
break;
|
|
119
|
+
case 'Potential Bugs and Recommended Fixes':
|
|
120
|
+
contentToCopy = extractSection('Potential Bugs and Recommended Fixes');
|
|
121
|
+
break;
|
|
122
|
+
case 'Everything':
|
|
123
|
+
contentToCopy = analysis;
|
|
124
|
+
break;
|
|
125
|
+
case 'Done':
|
|
126
|
+
console.log('No content copied to clipboard.');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (contentToCopy) {
|
|
130
|
+
copy_paste_1.default.copy(contentToCopy, (err) => {
|
|
131
|
+
if (err) {
|
|
132
|
+
console.error('Error: Unable to copy to clipboard.', err);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log('Selected content copied to clipboard');
|
|
136
|
+
}
|
|
137
|
+
});
|
|
58
138
|
}
|
|
59
139
|
}
|
|
60
140
|
catch (error) {
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
7
7
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
|
+
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
9
10
|
const string_utils_1 = require("../../utils/string_utils");
|
|
10
11
|
const config_utils_1 = require("../../utils/config_utils");
|
|
11
12
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
@@ -33,6 +34,68 @@ const generateExplainShellPrompt = (prompt) => {
|
|
|
33
34
|
|
|
34
35
|
Please briefly explain how this shell script works.`;
|
|
35
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
|
+
};
|
|
36
99
|
const shell = async (options) => {
|
|
37
100
|
let { prompt } = options;
|
|
38
101
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
@@ -48,54 +111,12 @@ const shell = async (options) => {
|
|
|
48
111
|
]);
|
|
49
112
|
prompt = userPrompt;
|
|
50
113
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const rawShellCommand = await gpt.generateText({ prompt: shellPrompt });
|
|
55
|
-
// Clean the commit message using extractMarkdown
|
|
56
|
-
const generatedShell = (0, string_utils_1.extractMarkdown)(rawShellCommand);
|
|
57
|
-
// remove #! from the generated shell script
|
|
58
|
-
const generatedShellWithoutShebang = generatedShell.replace(/^#!.*?\s/, '');
|
|
59
|
-
console.log((0, cli_highlight_1.default)(generatedShellWithoutShebang));
|
|
60
|
-
// Inquirer prompt for further actions
|
|
61
|
-
const { action } = await inquirer_1.default.prompt([
|
|
62
|
-
{
|
|
63
|
-
type: 'list',
|
|
64
|
-
name: 'action',
|
|
65
|
-
message: 'What would you like to do?',
|
|
66
|
-
choices: [
|
|
67
|
-
{ name: 'Execute', value: 'execute' },
|
|
68
|
-
{ name: 'Revise', value: 'revise' },
|
|
69
|
-
{ name: 'Explain', value: 'explain' },
|
|
70
|
-
{ name: 'Copy to Clipboard', value: 'copy' },
|
|
71
|
-
{ name: 'Cancel', value: 'cancel' },
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
]);
|
|
75
|
-
switch (action) {
|
|
76
|
-
case 'execute':
|
|
77
|
-
(0, child_process_1.execSync)(generatedShellWithoutShebang, { stdio: 'inherit' });
|
|
78
|
-
break;
|
|
79
|
-
case 'revise':
|
|
80
|
-
// Logic to revise the prompt (not implemented in this example)
|
|
81
|
-
console.log('Revise option selected. Implement revision logic here.');
|
|
82
|
-
break;
|
|
83
|
-
case 'explain':
|
|
84
|
-
const explainPrompt = generateExplainShellPrompt(generatedShellWithoutShebang);
|
|
85
|
-
const explainShellCommand = await gpt.generateText({ prompt: explainPrompt });
|
|
86
|
-
console.log((0, cli_highlight_1.default)(explainShellCommand, { language: 'markdown' }));
|
|
87
|
-
break;
|
|
88
|
-
case 'copy':
|
|
89
|
-
(0, child_process_1.execFileSync)('pbcopy', { input: generatedShellWithoutShebang });
|
|
90
|
-
console.log('Script copied to clipboard.');
|
|
91
|
-
break;
|
|
92
|
-
case 'cancel':
|
|
93
|
-
console.log('Operation cancelled.');
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
114
|
+
const rawShellCommand = await generateShell(gpt, prompt);
|
|
115
|
+
displayGeneratedShell(rawShellCommand);
|
|
116
|
+
await handleUserAction(gpt, rawShellCommand, prompt);
|
|
96
117
|
}
|
|
97
118
|
catch (error) {
|
|
98
|
-
console.error('Could not generate shell script');
|
|
119
|
+
console.error('Could not generate shell script', error);
|
|
99
120
|
}
|
|
100
121
|
};
|
|
101
122
|
exports.default = shell;
|
|
@@ -6,6 +6,8 @@ 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");
|
|
11
13
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
@@ -20,33 +22,27 @@ const extractChangedFiles = (gitDiff) => {
|
|
|
20
22
|
};
|
|
21
23
|
const generatePrompt = (files, srcFiles, testFiles, gitDiff) => {
|
|
22
24
|
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:
|
|
25
|
+
return `### List of Code Files
|
|
25
26
|
|
|
26
|
-
==== begin list of files ====
|
|
27
27
|
${filesList}
|
|
28
|
-
==== end list of files ====
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
### List of All Source Files
|
|
31
30
|
|
|
32
|
-
==== begin list of source files ====
|
|
33
31
|
${srcFiles}
|
|
34
|
-
==== end list of source files ====
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
### List of all test files
|
|
37
34
|
|
|
38
|
-
==== begin list of test files ====
|
|
39
35
|
${testFiles}
|
|
40
|
-
==== end list of test files ====
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
### Git Diff Content
|
|
43
38
|
|
|
44
|
-
|
|
39
|
+
${gitDiff}
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
## Example Output
|
|
47
42
|
|
|
48
43
|
Give me this output format for your answer:
|
|
49
44
|
|
|
45
|
+
\`\`\`plaintext
|
|
50
46
|
## files
|
|
51
47
|
|
|
52
48
|
app/models/foo.rb
|
|
@@ -55,7 +51,129 @@ app/views/baz/bar.html.erb
|
|
|
55
51
|
## relevant test files
|
|
56
52
|
|
|
57
53
|
test/models/foo_test.rb
|
|
58
|
-
test/controllers/baz_controller_test.rb
|
|
54
|
+
test/controllers/baz_controller_test.rb
|
|
55
|
+
\`\`\`
|
|
56
|
+
|
|
57
|
+
### Request
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
Also, list any test files that might be relevant to these files.`;
|
|
62
|
+
};
|
|
63
|
+
const extractFilesAndContent = (analysis) => {
|
|
64
|
+
const files = [];
|
|
65
|
+
const fileContents = {};
|
|
66
|
+
const matches = analysis.match(/(\*{2})([^\*]+)(\*{2})/g);
|
|
67
|
+
if (!matches)
|
|
68
|
+
return [files, fileContents];
|
|
69
|
+
for (const match of matches) {
|
|
70
|
+
const fileName = match.replace(/\*{2}/g, '').trim();
|
|
71
|
+
const regex = new RegExp(`\\*\\*${fileName}\\*\\*\\n\\n\\\`{3}([\\s\\S]+?)\\\`{3}`, 'g');
|
|
72
|
+
const contentMatch = regex.exec(analysis);
|
|
73
|
+
if (contentMatch) {
|
|
74
|
+
files.push(fileName);
|
|
75
|
+
let content = contentMatch[1];
|
|
76
|
+
// Remove the first word after the opening triple backticks
|
|
77
|
+
content = content.split('\n').map((line, idx) => idx === 0 ? line.replace(/^\w+\s*/, '') : line).join('\n');
|
|
78
|
+
fileContents[fileName] = content;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [files, fileContents];
|
|
82
|
+
};
|
|
83
|
+
const handleUserAction = async (analysis) => {
|
|
84
|
+
const [files, fileContents] = extractFilesAndContent(analysis);
|
|
85
|
+
const { action } = await inquirer_1.default.prompt([
|
|
86
|
+
{
|
|
87
|
+
type: 'list',
|
|
88
|
+
name: 'action',
|
|
89
|
+
message: 'What would you like to do?',
|
|
90
|
+
choices: [
|
|
91
|
+
{ name: 'Save a Unit Test File', value: 'save' },
|
|
92
|
+
{ name: 'Save All Unit Test Files', value: 'saveAll' },
|
|
93
|
+
{ name: 'Copy Unit Test to Clipboard', value: 'copyTest' },
|
|
94
|
+
{ name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
|
|
95
|
+
{ name: 'Done', value: 'done' },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
switch (action) {
|
|
100
|
+
case 'save':
|
|
101
|
+
let saveAnother = true;
|
|
102
|
+
while (saveAnother) {
|
|
103
|
+
const { fileToSave } = await inquirer_1.default.prompt([
|
|
104
|
+
{
|
|
105
|
+
type: 'list',
|
|
106
|
+
name: 'fileToSave',
|
|
107
|
+
message: 'Which unit test file do you want to save?',
|
|
108
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
109
|
+
},
|
|
110
|
+
]);
|
|
111
|
+
if (fileContents[fileToSave]) {
|
|
112
|
+
const filePath = path_1.default.resolve(fileToSave);
|
|
113
|
+
fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true })
|
|
114
|
+
.then(() => fs_1.promises.writeFile(filePath, fileContents[fileToSave]))
|
|
115
|
+
.then(() => console.log(`${fileToSave} has been saved.`))
|
|
116
|
+
.catch((error) => console.error(`Error saving ${fileToSave}:`, error));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log('File content not found.');
|
|
120
|
+
}
|
|
121
|
+
const { saveMore } = await inquirer_1.default.prompt([
|
|
122
|
+
{
|
|
123
|
+
type: 'confirm',
|
|
124
|
+
name: 'saveMore',
|
|
125
|
+
message: 'Do you want to save another file?',
|
|
126
|
+
default: false,
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
saveAnother = saveMore;
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
case 'saveAll':
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
if (fileContents[file]) {
|
|
135
|
+
const filePath = path_1.default.resolve(file);
|
|
136
|
+
fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true })
|
|
137
|
+
.then(() => fs_1.promises.writeFile(filePath, fileContents[file]))
|
|
138
|
+
.then(() => console.log(`${file} has been saved.`))
|
|
139
|
+
.catch((error) => console.error(`Error saving ${file}:`, error));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.log(`File content not found for ${file}.`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
console.log('All unit test files have been saved.');
|
|
146
|
+
break;
|
|
147
|
+
case 'copyTest':
|
|
148
|
+
const { fileToCopy } = await inquirer_1.default.prompt([
|
|
149
|
+
{
|
|
150
|
+
type: 'list',
|
|
151
|
+
name: 'fileToCopy',
|
|
152
|
+
message: 'Which unit test file do you want to copy to the clipboard?',
|
|
153
|
+
choices: files.map(file => ({ name: file, value: file })),
|
|
154
|
+
},
|
|
155
|
+
]);
|
|
156
|
+
if (fileContents[fileToCopy]) {
|
|
157
|
+
process.nextTick(() => {
|
|
158
|
+
require('copy-paste').copy(fileContents[fileToCopy], () => {
|
|
159
|
+
console.log(`${fileToCopy} copied to clipboard`);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.log('File content not found.');
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
case 'copyAll':
|
|
168
|
+
process.nextTick(() => {
|
|
169
|
+
require('copy-paste').copy(analysis, () => {
|
|
170
|
+
console.log('Entire response copied to clipboard');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
case 'done':
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
59
177
|
};
|
|
60
178
|
const unitTests = async (options) => {
|
|
61
179
|
const { files } = options;
|
|
@@ -88,9 +206,9 @@ const unitTests = async (options) => {
|
|
|
88
206
|
}
|
|
89
207
|
});
|
|
90
208
|
for (const codeFile of lines) {
|
|
91
|
-
if (await fs_1.promises
|
|
209
|
+
if (await fs_1.promises.stat(codeFile).then(stat => stat.isFile()).catch(() => false)) {
|
|
92
210
|
const fileContents = await fs_1.promises.readFile(codeFile, 'utf-8');
|
|
93
|
-
context.push(`###
|
|
211
|
+
context.push(`### Contents of ${codeFile}\n\n${fileContents}\n\n### End of ${codeFile}`);
|
|
94
212
|
}
|
|
95
213
|
}
|
|
96
214
|
// Generate the message for unit test creation
|
|
@@ -98,11 +216,11 @@ const unitTests = async (options) => {
|
|
|
98
216
|
if ((files || []).length > 0) {
|
|
99
217
|
const message = `please create unit tests for these files:
|
|
100
218
|
|
|
101
|
-
##
|
|
219
|
+
## Begin list of files
|
|
102
220
|
${(files || []).join('\n')}
|
|
103
|
-
##
|
|
221
|
+
## End list of files
|
|
104
222
|
|
|
105
|
-
##
|
|
223
|
+
## Context
|
|
106
224
|
${context.join('\n\n')}`;
|
|
107
225
|
// Get the model and analysis using ClovingGPT
|
|
108
226
|
analysis = await gpt.generateText({ prompt: message });
|
|
@@ -110,16 +228,18 @@ ${context.join('\n\n')}`;
|
|
|
110
228
|
else {
|
|
111
229
|
const message = `please create unit tests for these changes:
|
|
112
230
|
|
|
113
|
-
##
|
|
231
|
+
## Begin diff
|
|
114
232
|
${await (0, git_utils_1.getGitDiff)()}
|
|
115
|
-
##
|
|
233
|
+
## End diff
|
|
116
234
|
|
|
117
|
-
##
|
|
235
|
+
## Context
|
|
118
236
|
|
|
119
237
|
${context.join('\n\n')}`;
|
|
120
238
|
// Get the model and analysis using ClovingGPT
|
|
121
239
|
analysis = await gpt.generateText({ prompt: message });
|
|
122
240
|
}
|
|
123
241
|
console.log((0, cli_highlight_1.default)(analysis));
|
|
242
|
+
// Add the new user action handling
|
|
243
|
+
await handleUserAction(analysis);
|
|
124
244
|
};
|
|
125
245
|
exports.default = unitTests;
|
package/dist/commands/models.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
// models.ts
|
|
4
3
|
const claude_1 = require("../cloving_gpt/adapters/claude");
|
|
5
4
|
const openai_1 = require("../cloving_gpt/adapters/openai");
|
|
6
5
|
const ollama_1 = require("../cloving_gpt/adapters/ollama");
|
|
6
|
+
const gemini_1 = require("../cloving_gpt/adapters/gemini");
|
|
7
|
+
const mistral_1 = require("../cloving_gpt/adapters/mistral");
|
|
7
8
|
const listModels = async () => {
|
|
9
|
+
claude_1.ClaudeAdapter.listSupportedModels();
|
|
10
|
+
gemini_1.GeminiAdapter.listSupportedModels();
|
|
11
|
+
mistral_1.MistralAdapter.listSupportedModels();
|
|
8
12
|
try {
|
|
9
13
|
await ollama_1.OllamaAdapter.listSupportedModels();
|
|
10
14
|
}
|
|
11
15
|
catch (error) {
|
|
12
16
|
// do nothing, no ollama server running
|
|
13
17
|
}
|
|
14
|
-
claude_1.ClaudeAdapter.listSupportedModels();
|
|
15
18
|
openai_1.OpenAIAdapter.listSupportedModels();
|
|
16
19
|
};
|
|
17
20
|
exports.default = listModels;
|
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.11",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"axios": "^1.7.2",
|
|
41
41
|
"cli-highlight": "^2.1.11",
|
|
42
|
-
"clipboard-copy": "^4.0.1",
|
|
43
42
|
"commander": "^12.1.0",
|
|
43
|
+
"copy-paste": "^1.5.3",
|
|
44
44
|
"eslint": "^9.6.0",
|
|
45
45
|
"eslint-config-prettier": "^9.1.0",
|
|
46
46
|
"eslint-config-raine": "^0.5.0",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"vite-node": "^2.0.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
+
"@types/copy-paste": "^1.1.33",
|
|
60
61
|
"@types/ini": "^4.1.1",
|
|
61
62
|
"@types/inquirer": "^9.0.7",
|
|
62
63
|
"@types/jest": "^29.5.12",
|
|
@@ -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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloving",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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
|
}
|