cloving 0.3.4 → 0.3.6
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/README.md +8 -2
- 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 +13 -8
- package/dist/commands/config.js +9 -9
- package/dist/commands/generate/context.js +13 -6
- package/dist/commands/generate/unit_tests.js +3 -261
- package/dist/commands/init.js +3 -218
- package/dist/commands/tokens.js +14 -6
- package/dist/index.js +5 -8
- package/dist/managers/BlockManager.js +121 -0
- package/dist/managers/ChatManager.js +202 -42
- package/dist/managers/ChunkManager.js +12 -0
- package/dist/managers/CodeManager.js +24 -10
- package/dist/managers/CommitManager.js +6 -3
- package/dist/managers/InitManager.js +216 -0
- package/dist/managers/ProxyManager.js +33 -10
- package/dist/managers/ReviewManager.js +40 -12
- package/dist/managers/ShellManager.js +7 -4
- 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/prompt_utils.js +515 -0
- package/dist/utils/string_utils.js +119 -12
- package/package.json +13 -8
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const prompts_1 = require("@inquirer/prompts");
|
|
7
7
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
8
8
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
9
|
-
const
|
|
9
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
10
10
|
const string_utils_1 = require("../utils/string_utils");
|
|
11
11
|
class CodeManager {
|
|
12
12
|
constructor(options) {
|
|
@@ -19,7 +19,7 @@ class CodeManager {
|
|
|
19
19
|
try {
|
|
20
20
|
if (this.options.files) {
|
|
21
21
|
for (const file of this.options.files) {
|
|
22
|
-
this.contextFiles = await (0,
|
|
22
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(file, this.contextFiles, this.options);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
@@ -29,7 +29,7 @@ class CodeManager {
|
|
|
29
29
|
message: `Enter the relative path of a file or directory you would like to include as context (or press enter to continue):`,
|
|
30
30
|
});
|
|
31
31
|
if (contextFile) {
|
|
32
|
-
this.contextFiles = await (0,
|
|
32
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(contextFile, this.contextFiles, this.options);
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
35
|
includeMoreFiles = false;
|
|
@@ -43,6 +43,17 @@ class CodeManager {
|
|
|
43
43
|
this.options.prompt = userPrompt;
|
|
44
44
|
}
|
|
45
45
|
let response = await this.generateCode(this.options.prompt);
|
|
46
|
+
const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(response);
|
|
47
|
+
const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
|
|
48
|
+
if (!canApply) {
|
|
49
|
+
console.log('The generated code could not be automatically applied. It will try again asking for more details.');
|
|
50
|
+
console.log(`Some of the provided code blocks could not be applied,
|
|
51
|
+
please match the existing code with a few more lines of context and make sure it is a character for character exact match.
|
|
52
|
+
|
|
53
|
+
${summary}`);
|
|
54
|
+
response = await this.generateCode(this.options.prompt);
|
|
55
|
+
this.displayGeneratedCode(response);
|
|
56
|
+
}
|
|
46
57
|
this.displayGeneratedCode(response);
|
|
47
58
|
if (this.options.save) {
|
|
48
59
|
const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(response);
|
|
@@ -59,7 +70,7 @@ class CodeManager {
|
|
|
59
70
|
}
|
|
60
71
|
async generateCode(userPrompt) {
|
|
61
72
|
if (this.chatHistory.length === 0) {
|
|
62
|
-
const systemPrompt = (0,
|
|
73
|
+
const systemPrompt = (0, prompt_utils_1.generateCodegenPrompt)(this.contextFiles);
|
|
63
74
|
this.chatHistory.push({ role: 'user', content: systemPrompt });
|
|
64
75
|
this.chatHistory.push({ role: 'assistant', content: 'What would you like to do?' });
|
|
65
76
|
}
|
|
@@ -67,7 +78,7 @@ class CodeManager {
|
|
|
67
78
|
return await this.gpt.generateText({ prompt: userPrompt, messages: this.chatHistory });
|
|
68
79
|
}
|
|
69
80
|
displayGeneratedCode(rawCodeCommand) {
|
|
70
|
-
(0, string_utils_1.parseMarkdownInstructions)(rawCodeCommand).map(code => {
|
|
81
|
+
(0, string_utils_1.parseMarkdownInstructions)(rawCodeCommand).map((code) => {
|
|
71
82
|
if (code.trim().startsWith('```')) {
|
|
72
83
|
const lines = code.split('\n');
|
|
73
84
|
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
@@ -88,7 +99,7 @@ class CodeManager {
|
|
|
88
99
|
}
|
|
89
100
|
async handleUserAction(response, prompt) {
|
|
90
101
|
const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(response);
|
|
91
|
-
const files = Array.from(new Set(currentNewBlocks.map(block => block.filePath)));
|
|
102
|
+
const files = Array.from(new Set(currentNewBlocks.map((block) => block.filePath)));
|
|
92
103
|
if (this.options.save) {
|
|
93
104
|
await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(currentNewBlocks);
|
|
94
105
|
return;
|
|
@@ -116,7 +127,7 @@ class CodeManager {
|
|
|
116
127
|
message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
117
128
|
});
|
|
118
129
|
if (contextFile) {
|
|
119
|
-
this.contextFiles = await (0,
|
|
130
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(contextFile, this.contextFiles, this.options);
|
|
120
131
|
}
|
|
121
132
|
else {
|
|
122
133
|
includeMoreFiles = false;
|
|
@@ -129,7 +140,10 @@ class CodeManager {
|
|
|
129
140
|
case 'explain':
|
|
130
141
|
const explainPrompt = this.generateExplainCodePrompt(response);
|
|
131
142
|
this.chatHistory.push({ role: 'user', content: explainPrompt });
|
|
132
|
-
const explainCodeCommand = await this.gpt.generateText({
|
|
143
|
+
const explainCodeCommand = await this.gpt.generateText({
|
|
144
|
+
prompt: explainPrompt,
|
|
145
|
+
messages: this.chatHistory,
|
|
146
|
+
});
|
|
133
147
|
console.log((0, cli_highlight_1.default)(explainCodeCommand, { language: 'markdown' }));
|
|
134
148
|
break;
|
|
135
149
|
case 'save':
|
|
@@ -137,9 +151,9 @@ class CodeManager {
|
|
|
137
151
|
while (saveAnother) {
|
|
138
152
|
const fileToSave = await (0, prompts_1.select)({
|
|
139
153
|
message: 'Which file do you want to save?',
|
|
140
|
-
choices: files.map(file => ({ name: file, value: file })),
|
|
154
|
+
choices: files.map((file) => ({ name: file, value: file })),
|
|
141
155
|
});
|
|
142
|
-
await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(currentNewBlocks.filter(block => block.filePath === fileToSave));
|
|
156
|
+
await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(currentNewBlocks.filter((block) => block.filePath === fileToSave));
|
|
143
157
|
const saveMore = await (0, prompts_1.confirm)({
|
|
144
158
|
message: 'Do you want to save another file?',
|
|
145
159
|
default: true,
|
|
@@ -35,7 +35,9 @@ class CommitManager {
|
|
|
35
35
|
fs_1.default.writeFileSync(tempCommitFilePath, commitMessage);
|
|
36
36
|
// Commit the changes using the generated commit message
|
|
37
37
|
try {
|
|
38
|
-
(0, child_process_1.execFileSync)('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], {
|
|
38
|
+
(0, child_process_1.execFileSync)('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], {
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
});
|
|
39
41
|
}
|
|
40
42
|
catch (commitError) {
|
|
41
43
|
// If commit is canceled (non-zero exit), handle it here
|
|
@@ -47,8 +49,9 @@ class CommitManager {
|
|
|
47
49
|
throw err;
|
|
48
50
|
});
|
|
49
51
|
}
|
|
50
|
-
catch (
|
|
51
|
-
|
|
52
|
+
catch (err) {
|
|
53
|
+
const error = err;
|
|
54
|
+
console.error('Could not generate commit message:', error.message);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
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
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const colors_1 = __importDefault(require("colors"));
|
|
10
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
11
|
+
const fs_2 = require("fs");
|
|
12
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
13
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
14
|
+
const string_utils_1 = require("../utils/string_utils");
|
|
15
|
+
const config_utils_1 = require("../utils/config_utils");
|
|
16
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
17
|
+
class InitManager {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.options = options;
|
|
20
|
+
this.chatHistory = [];
|
|
21
|
+
this.options.silent = (0, config_utils_1.getConfig)(this.options).globalSilent || false;
|
|
22
|
+
this.gpt = new cloving_gpt_1.default(this.options);
|
|
23
|
+
this.chatHistory.push({
|
|
24
|
+
role: 'system',
|
|
25
|
+
content: prompt_utils_1.INIT_INSTRUCTIONS,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initializes the InitManager by collecting special file contents, checking for dependencies,
|
|
30
|
+
* and processing the AI response for the project details.
|
|
31
|
+
*
|
|
32
|
+
* @return {Promise<void>} A promise that resolves when the initialization is complete.
|
|
33
|
+
*/
|
|
34
|
+
async initialize() {
|
|
35
|
+
const specialFileContents = (0, prompt_utils_1.collectSpecialFileContents)();
|
|
36
|
+
const specialFileNames = Object.keys(specialFileContents).map((file) => ' - ' + file);
|
|
37
|
+
this.logInitializationMessage(specialFileNames);
|
|
38
|
+
const config = (0, config_utils_1.getConfig)(this.options);
|
|
39
|
+
if (!config || !config?.models) {
|
|
40
|
+
console.error('No cloving configuration found. Please run `cloving config`');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!(0, prompt_utils_1.checkForSpecialFiles)()) {
|
|
44
|
+
console.error('No dependencies files detected. Please add a dependency file (e.g. package.json, Gemfile, requirements.txt, etc.) to your project and run `cloving init` again.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `describe_${Date.now()}.tmp`);
|
|
48
|
+
try {
|
|
49
|
+
const limitedFileList = await this.getFilteredFileList();
|
|
50
|
+
const projectDetails = {
|
|
51
|
+
files: limitedFileList,
|
|
52
|
+
specialFiles: specialFileContents,
|
|
53
|
+
};
|
|
54
|
+
const [success, errorMessage] = await this.processAIResponse(projectDetails, tempFilePath);
|
|
55
|
+
if (!success) {
|
|
56
|
+
console.log(errorMessage);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await this.promptUserForReview();
|
|
60
|
+
// Clean up
|
|
61
|
+
await fs_2.promises.unlink(tempFilePath);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error('Error describing the project:', error.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Checks if a directory exists at the specified path.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} directory - The path of the directory to check.
|
|
71
|
+
* @return {Promise<boolean>} A promise that resolves to true if the directory exists, false otherwise.
|
|
72
|
+
*/
|
|
73
|
+
async directoryExists(directory) {
|
|
74
|
+
try {
|
|
75
|
+
const stats = await fs_2.promises.stat(directory);
|
|
76
|
+
return stats.isDirectory();
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Returns a filtered list of files, excluding those ignored by the .gitignore file.
|
|
84
|
+
*
|
|
85
|
+
* @return {string[]} A list of file paths, limited to the first 100 files.
|
|
86
|
+
*/
|
|
87
|
+
async getFilteredFileList() {
|
|
88
|
+
const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
|
|
89
|
+
const ig = (0, ignore_1.default)();
|
|
90
|
+
if (fs_1.default.existsSync(gitignorePath)) {
|
|
91
|
+
const gitignoreContent = fs_1.default.readFileSync(gitignorePath, 'utf-8');
|
|
92
|
+
ig.add(gitignoreContent);
|
|
93
|
+
}
|
|
94
|
+
const fileList = await (0, prompt_utils_1.generateFileList)();
|
|
95
|
+
const filteredFileList = fileList.filter((file) => {
|
|
96
|
+
try {
|
|
97
|
+
return !ig.ignores(file);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return filteredFileList.slice(0, 100);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Logs the initialization message based on the special file names and silent mode.
|
|
107
|
+
*
|
|
108
|
+
* @param {string[]} specialFileNames - A list of special file names to be analyzed.
|
|
109
|
+
* @return {void} No return value.
|
|
110
|
+
*/
|
|
111
|
+
logInitializationMessage(specialFileNames) {
|
|
112
|
+
if (!this.options.silent) {
|
|
113
|
+
if (specialFileNames.length > 0) {
|
|
114
|
+
console.log(`Cloving will analyze the list of files and the contents of the following files:
|
|
115
|
+
|
|
116
|
+
${specialFileNames.join('\n')}
|
|
117
|
+
|
|
118
|
+
Cloving will send AI a request to summarize the technologies used in this project.
|
|
119
|
+
|
|
120
|
+
This will provide better context for future Cloving requests.`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(`
|
|
124
|
+
This script will analyze the list of files in the current directory using GPT to summarize the
|
|
125
|
+
technologies used. This will provide better context for future Cloving requests.
|
|
126
|
+
`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Processes the AI response by generating a prompt, sending it to the AI,
|
|
132
|
+
* and saving the response to a temporary file and cloving.json.
|
|
133
|
+
*
|
|
134
|
+
* @param {any} projectDetails - A JSON object describing the project.
|
|
135
|
+
* @param {string} tempFilePath - The path to the temporary file.
|
|
136
|
+
* @param {string} errorMessage - An error message if there was an error.
|
|
137
|
+
* @return {[boolean, string]} A promise that resolves with an array containing a boolean
|
|
138
|
+
* indicating whether the AI response was successfully processed,
|
|
139
|
+
* and a message explaining any errors.
|
|
140
|
+
*/
|
|
141
|
+
async processAIResponse(projectDetails, tempFilePath, errorMessage = '') {
|
|
142
|
+
const prompt = errorMessage !== ''
|
|
143
|
+
? errorMessage
|
|
144
|
+
: `Here is a JSON object describing my project:
|
|
145
|
+
${JSON.stringify(projectDetails, null, 2)}`;
|
|
146
|
+
this.chatHistory.push({ role: 'user', content: prompt });
|
|
147
|
+
const aiChatResponse = await this.gpt.generateText({ prompt, messages: this.chatHistory });
|
|
148
|
+
this.chatHistory.push({ role: 'assistant', content: aiChatResponse });
|
|
149
|
+
let cleanAiChatResponse = (0, string_utils_1.extractJsonMetadata)(aiChatResponse);
|
|
150
|
+
const [validSyntax, syntaxErrorMessage] = this.checkClovingJsonSyntax(cleanAiChatResponse);
|
|
151
|
+
if (!validSyntax) {
|
|
152
|
+
console.log(syntaxErrorMessage);
|
|
153
|
+
console.log('Retrying...');
|
|
154
|
+
return this.processAIResponse(projectDetails, tempFilePath, syntaxErrorMessage);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
await fs_2.promises.writeFile(tempFilePath, cleanAiChatResponse);
|
|
158
|
+
// Save the AI chat response to cloving.json
|
|
159
|
+
await fs_2.promises.writeFile('cloving.json', cleanAiChatResponse);
|
|
160
|
+
console.log(`Project data saved to ${colors_1.default.green.bold('cloving.json')}`);
|
|
161
|
+
return [true, ''];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Checks the syntax of the generated cloving.json file.
|
|
166
|
+
*
|
|
167
|
+
* @param {string} cleanAiChatResponse - The cleaned AI chat response.
|
|
168
|
+
* @return {[boolean, string]} An array where the first element is a boolean indicating
|
|
169
|
+
* whether the syntax was valid, and the second element is a
|
|
170
|
+
* message explaining any errors.
|
|
171
|
+
*/
|
|
172
|
+
checkClovingJsonSyntax(cleanAiChatResponse) {
|
|
173
|
+
try {
|
|
174
|
+
JSON.parse(cleanAiChatResponse);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
return [
|
|
178
|
+
false,
|
|
179
|
+
`${colors_1.default.red.bold('ERROR:')} parsing cloving.json: ${colors_1.default.red.bold(error.message)}`,
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
const clovingJson = JSON.parse(cleanAiChatResponse);
|
|
183
|
+
const directoriesToCheck = [
|
|
184
|
+
...clovingJson.languages.map((language) => language.directory),
|
|
185
|
+
...clovingJson.frameworks.map((framework) => framework.directory),
|
|
186
|
+
...clovingJson.testingFrameworks.map((testingFramework) => testingFramework.directory),
|
|
187
|
+
];
|
|
188
|
+
const missingDirectories = directoriesToCheck.filter((directory) => !fs_1.default.existsSync(directory));
|
|
189
|
+
if (missingDirectories.length > 0) {
|
|
190
|
+
return [
|
|
191
|
+
false,
|
|
192
|
+
`${colors_1.default.red.bold('ERROR:')} The following directories were not found: ${colors_1.default.red.bold(missingDirectories.join(', '))}`,
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
return [true, ''];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Prompts the user to review the generated data if the `silent` option is not set.
|
|
199
|
+
* If the user confirms, reads the content of 'cloving.json' and logs it to the console.
|
|
200
|
+
*
|
|
201
|
+
* @return {Promise<void>} A promise that resolves when the review is complete.
|
|
202
|
+
*/
|
|
203
|
+
async promptUserForReview() {
|
|
204
|
+
if (!this.options.silent) {
|
|
205
|
+
const reviewAnswer = await (0, prompts_1.confirm)({
|
|
206
|
+
message: 'Do you want to review the generated data?',
|
|
207
|
+
default: true,
|
|
208
|
+
});
|
|
209
|
+
if (reviewAnswer) {
|
|
210
|
+
const clovingJsonContent = await fs_2.promises.readFile('cloving.json', 'utf-8');
|
|
211
|
+
console.log(clovingJsonContent);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.default = InitManager;
|
|
@@ -10,8 +10,17 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
11
11
|
const ChunkManager_1 = __importDefault(require("./ChunkManager"));
|
|
12
12
|
const config_utils_1 = require("../utils/config_utils");
|
|
13
|
-
const
|
|
13
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
14
14
|
class ProxyManager {
|
|
15
|
+
/**
|
|
16
|
+
* Creates an instance of ProxyManager.
|
|
17
|
+
* @param {ClovingGPTOptions} options - Configuration options for the ProxyManager.
|
|
18
|
+
* @description
|
|
19
|
+
* Initializes the ProxyManager with the given options. It sets up:
|
|
20
|
+
* - A ClovingGPT instance with silent and stream options enabled
|
|
21
|
+
* - An Express application with JSON body parsing
|
|
22
|
+
* - A ChunkManager for handling streaming responses
|
|
23
|
+
*/
|
|
15
24
|
constructor(options) {
|
|
16
25
|
this.options = options;
|
|
17
26
|
this.contextFiles = {};
|
|
@@ -38,9 +47,12 @@ class ProxyManager {
|
|
|
38
47
|
let expandedFiles = [];
|
|
39
48
|
for (const file of files) {
|
|
40
49
|
const filePath = path_1.default.resolve(file);
|
|
41
|
-
if (await fs_1.default.promises
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
if (await fs_1.default.promises
|
|
51
|
+
.stat(filePath)
|
|
52
|
+
.then((stat) => stat.isDirectory())
|
|
53
|
+
.catch(() => false)) {
|
|
54
|
+
const dirFiles = await (0, prompt_utils_1.getAllFilesInDirectory)(filePath);
|
|
55
|
+
expandedFiles = expandedFiles.concat(dirFiles.map((f) => path_1.default.relative(process.cwd(), f)));
|
|
44
56
|
}
|
|
45
57
|
else {
|
|
46
58
|
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
@@ -49,7 +61,10 @@ class ProxyManager {
|
|
|
49
61
|
files = expandedFiles;
|
|
50
62
|
for (const file of files) {
|
|
51
63
|
const filePath = path_1.default.resolve(file);
|
|
52
|
-
if (await fs_1.default.promises
|
|
64
|
+
if (await fs_1.default.promises
|
|
65
|
+
.stat(filePath)
|
|
66
|
+
.then((stat) => stat.isFile())
|
|
67
|
+
.catch(() => false)) {
|
|
53
68
|
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
54
69
|
this.contextFiles[file] = content;
|
|
55
70
|
}
|
|
@@ -59,7 +74,9 @@ class ProxyManager {
|
|
|
59
74
|
try {
|
|
60
75
|
const { messages, stream } = req.body;
|
|
61
76
|
const content = messages.map((message) => message.content).join('\n\n');
|
|
62
|
-
const contextFileContents = Object.keys(this.contextFiles)
|
|
77
|
+
const contextFileContents = Object.keys(this.contextFiles)
|
|
78
|
+
.map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`)
|
|
79
|
+
.join('\n');
|
|
63
80
|
const prompt = `# Task
|
|
64
81
|
|
|
65
82
|
${content}
|
|
@@ -99,14 +116,20 @@ ${content}`;
|
|
|
99
116
|
let convertedStream = this.gpt.convertStream(buffer);
|
|
100
117
|
while (convertedStream !== null) {
|
|
101
118
|
const { output, lastChar } = convertedStream;
|
|
102
|
-
const content = JSON.stringify({
|
|
119
|
+
const content = JSON.stringify({
|
|
120
|
+
id: 'chatcmpl-9tLvSuRQaYzQIiKRAwI5a7WqdJspq',
|
|
121
|
+
object: 'chat.completion.chunk',
|
|
122
|
+
created: 1722979350,
|
|
123
|
+
model: 'cloving',
|
|
124
|
+
system_fingerprint: 'fp_3aa7262c27',
|
|
125
|
+
choices: [{ index: 0, delta: { content: output }, logprobs: null, finish_reason: null }],
|
|
126
|
+
});
|
|
103
127
|
convertedStreams.push(content);
|
|
104
128
|
if (convertedStreams.length >= 2) {
|
|
105
|
-
const dataToSend = convertedStreams.map(c => `data: ${c}`).join('\n\n');
|
|
129
|
+
const dataToSend = convertedStreams.map((c) => `data: ${c}`).join('\n\n');
|
|
106
130
|
res.write(`${dataToSend}\n\n`);
|
|
107
131
|
convertedStreams = [];
|
|
108
132
|
}
|
|
109
|
-
this.chunkManager.clearBuffer(lastChar);
|
|
110
133
|
buffer = buffer.slice(lastChar);
|
|
111
134
|
convertedStream = this.gpt.convertStream(buffer);
|
|
112
135
|
}
|
|
@@ -117,7 +140,7 @@ ${content}`;
|
|
|
117
140
|
});
|
|
118
141
|
responseStream.data.on('end', () => {
|
|
119
142
|
// Send any remaining converted streams
|
|
120
|
-
const dataToSend = convertedStreams.map(c => `data: ${c}`).join('\n\n');
|
|
143
|
+
const dataToSend = convertedStreams.map((c) => `data: ${c}`).join('\n\n');
|
|
121
144
|
res.write(`${dataToSend}\n\ndata: [DONE]\n\n`);
|
|
122
145
|
res.end();
|
|
123
146
|
});
|
|
@@ -11,7 +11,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
11
11
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
12
12
|
const git_utils_1 = require("../utils/git_utils");
|
|
13
13
|
const string_utils_1 = require("../utils/string_utils");
|
|
14
|
-
const
|
|
14
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
15
15
|
const config_utils_1 = require("../utils/config_utils");
|
|
16
16
|
class ReviewManager {
|
|
17
17
|
constructor(options) {
|
|
@@ -21,14 +21,22 @@ class ReviewManager {
|
|
|
21
21
|
this.options = options;
|
|
22
22
|
this.gpt = new cloving_gpt_1.default(options);
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Loads the context files specified in the options. If directories are provided,
|
|
26
|
+
* it expands them to include all files within. The contents of each file are
|
|
27
|
+
* stored in the `contextFiles` object for later use.
|
|
28
|
+
*/
|
|
24
29
|
async loadContextFiles() {
|
|
25
30
|
if (this.options.files) {
|
|
26
31
|
let expandedFiles = [];
|
|
27
32
|
for (const file of this.options.files) {
|
|
28
33
|
const filePath = path_1.default.resolve(file);
|
|
29
|
-
if (await fs_1.default.promises
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
if (await fs_1.default.promises
|
|
35
|
+
.stat(filePath)
|
|
36
|
+
.then((stat) => stat.isDirectory())
|
|
37
|
+
.catch(() => false)) {
|
|
38
|
+
const dirFiles = await (0, prompt_utils_1.getAllFilesInDirectory)(filePath);
|
|
39
|
+
expandedFiles = expandedFiles.concat(dirFiles.map((f) => path_1.default.relative(process.cwd(), f)));
|
|
32
40
|
}
|
|
33
41
|
else {
|
|
34
42
|
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
@@ -37,13 +45,23 @@ class ReviewManager {
|
|
|
37
45
|
this.options.files = expandedFiles;
|
|
38
46
|
for (const file of this.options.files) {
|
|
39
47
|
const filePath = path_1.default.resolve(file);
|
|
40
|
-
if (await fs_1.default.promises
|
|
48
|
+
if (await fs_1.default.promises
|
|
49
|
+
.stat(filePath)
|
|
50
|
+
.then((stat) => stat.isFile())
|
|
51
|
+
.catch(() => false)) {
|
|
41
52
|
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
42
53
|
this.contextFiles[file] = content;
|
|
43
54
|
}
|
|
44
55
|
}
|
|
45
56
|
}
|
|
46
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Generates a review prompt based on the current chat history and options.
|
|
60
|
+
* If no chat history exists, it initializes it with a system prompt and a request
|
|
61
|
+
* for user input. The prompt includes a structured example response for code review.
|
|
62
|
+
*
|
|
63
|
+
* @returns {string} The generated review prompt.
|
|
64
|
+
*/
|
|
47
65
|
generateReviewPrompt() {
|
|
48
66
|
if (this.chatHistory.length === 0) {
|
|
49
67
|
let systemPrompt = `### Example of a well-structured response
|
|
@@ -98,7 +116,9 @@ class ReviewManager {
|
|
|
98
116
|
|
|
99
117
|
`;
|
|
100
118
|
if (this.options.files) {
|
|
101
|
-
const contextFileContents = Object.keys(this.contextFiles)
|
|
119
|
+
const contextFileContents = Object.keys(this.contextFiles)
|
|
120
|
+
.map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`)
|
|
121
|
+
.join('\n');
|
|
102
122
|
systemPrompt += `### Description of App
|
|
103
123
|
|
|
104
124
|
${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
|
|
@@ -122,7 +142,10 @@ Format the output of this code review in Markdown format.`;
|
|
|
122
142
|
this.chatHistory.push({ role: 'user', content: systemPrompt });
|
|
123
143
|
this.chatHistory.push({ role: 'assistant', content: 'What would you like to do?' });
|
|
124
144
|
}
|
|
125
|
-
this.chatHistory.push({
|
|
145
|
+
this.chatHistory.push({
|
|
146
|
+
role: 'user',
|
|
147
|
+
content: this.options.prompt || 'Please provide a code review.',
|
|
148
|
+
});
|
|
126
149
|
return this.options.prompt || 'Please provide a code review.';
|
|
127
150
|
}
|
|
128
151
|
async getGitDiffPrompt() {
|
|
@@ -137,7 +160,7 @@ ${this.options.prompt || 'Please provide a code review.'}`;
|
|
|
137
160
|
return prompt;
|
|
138
161
|
}
|
|
139
162
|
displayAnalysis(analysis) {
|
|
140
|
-
(0, string_utils_1.parseMarkdownInstructions)(analysis).map(code => {
|
|
163
|
+
(0, string_utils_1.parseMarkdownInstructions)(analysis).map((code) => {
|
|
141
164
|
if (code.trim().startsWith('```')) {
|
|
142
165
|
const lines = code.split('\n');
|
|
143
166
|
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
@@ -166,10 +189,13 @@ ${this.options.prompt || 'Please provide a code review.'}`;
|
|
|
166
189
|
message: 'What would you like to copy to the clipboard?',
|
|
167
190
|
choices: [
|
|
168
191
|
{ name: 'Copy only the Changes Overview', value: 'Changes Overview' },
|
|
169
|
-
{
|
|
192
|
+
{
|
|
193
|
+
name: 'Copy only the Potential Bugs and Recommended Fixes',
|
|
194
|
+
value: 'Potential Bugs and Recommended Fixes',
|
|
195
|
+
},
|
|
170
196
|
{ name: 'Copy everything', value: 'Everything' },
|
|
171
|
-
{ name: 'Done', value: 'Done' }
|
|
172
|
-
]
|
|
197
|
+
{ name: 'Done', value: 'Done' },
|
|
198
|
+
],
|
|
173
199
|
});
|
|
174
200
|
let contentToCopy = '';
|
|
175
201
|
switch (clipboardOption) {
|
|
@@ -200,7 +226,9 @@ ${this.options.prompt || 'Please provide a code review.'}`;
|
|
|
200
226
|
async review() {
|
|
201
227
|
try {
|
|
202
228
|
await this.loadContextFiles();
|
|
203
|
-
const prompt = this.options.files
|
|
229
|
+
const prompt = this.options.files
|
|
230
|
+
? this.generateReviewPrompt()
|
|
231
|
+
: await this.getGitDiffPrompt();
|
|
204
232
|
const analysis = await this.gpt.generateText({ prompt, messages: this.chatHistory });
|
|
205
233
|
this.displayAnalysis(analysis);
|
|
206
234
|
await this.handleUserAction(analysis);
|
|
@@ -8,7 +8,7 @@ const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
9
|
const prompts_1 = require("@inquirer/prompts");
|
|
10
10
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
11
|
-
const
|
|
11
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
12
12
|
const string_utils_1 = require("../utils/string_utils");
|
|
13
13
|
const config_utils_1 = require("../utils/config_utils");
|
|
14
14
|
class ShellManager {
|
|
@@ -27,7 +27,7 @@ Please briefly explain how this shell script works.`;
|
|
|
27
27
|
}
|
|
28
28
|
async generateShell(prompt) {
|
|
29
29
|
if (this.chatHistory.length === 0) {
|
|
30
|
-
const systemPrompt = (0,
|
|
30
|
+
const systemPrompt = (0, prompt_utils_1.generateShellPrompt)();
|
|
31
31
|
this.chatHistory.push({ role: 'user', content: systemPrompt });
|
|
32
32
|
this.chatHistory.push({ role: 'assistant', content: 'What would you like to do?' });
|
|
33
33
|
}
|
|
@@ -68,7 +68,10 @@ Please briefly explain how this shell script works.`;
|
|
|
68
68
|
case 'explain':
|
|
69
69
|
const explainPrompt = this.generateExplainShellPrompt(generatedShellWithoutShebang);
|
|
70
70
|
this.chatHistory.push({ role: 'user', content: explainPrompt });
|
|
71
|
-
const explainShellCommand = await this.gpt.generateText({
|
|
71
|
+
const explainShellCommand = await this.gpt.generateText({
|
|
72
|
+
prompt: explainPrompt,
|
|
73
|
+
messages: this.chatHistory,
|
|
74
|
+
});
|
|
72
75
|
console.log((0, cli_highlight_1.default)(explainShellCommand, { language: 'markdown' }));
|
|
73
76
|
break;
|
|
74
77
|
case 'copy':
|
|
@@ -91,7 +94,7 @@ Please briefly explain how this shell script works.`;
|
|
|
91
94
|
try {
|
|
92
95
|
if (!userPrompt) {
|
|
93
96
|
userPrompt = await (0, prompts_1.input)({
|
|
94
|
-
message: 'What would you like to do: '
|
|
97
|
+
message: 'What would you like to do: ',
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
const rawShellCommand = await this.generateShell(userPrompt);
|