cloving 0.1.18 → 0.1.20
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/index.js +31 -29
- package/dist/commands/generate/code.js +14 -2
- package/dist/commands/generate/review.js +91 -42
- package/dist/index.js +2 -0
- package/dist/package.json +1 -1
- package/dist/utils/command_utils.js +13 -1
- package/package.json +1 -1
|
@@ -13,6 +13,9 @@ const mistral_1 = require("./adapters/mistral");
|
|
|
13
13
|
const ollama_1 = require("./adapters/ollama");
|
|
14
14
|
const gemini_1 = require("./adapters/gemini");
|
|
15
15
|
const config_utils_1 = require("../utils/config_utils");
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const os_1 = __importDefault(require("os"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
16
19
|
class ClovingGPT {
|
|
17
20
|
constructor(options) {
|
|
18
21
|
const { model: partialModel } = options;
|
|
@@ -52,51 +55,53 @@ class ClovingGPT {
|
|
|
52
55
|
}
|
|
53
56
|
async reviewPrompt(prompt, endpoint) {
|
|
54
57
|
if (this.silent)
|
|
55
|
-
return;
|
|
58
|
+
return prompt;
|
|
56
59
|
const tokenCount = Math.ceil(prompt.length / 4).toLocaleString();
|
|
57
60
|
const reviewPrompt = await (0, prompts_1.confirm)({
|
|
58
|
-
message: `Do you want to review the ~${tokenCount} token prompt before sending it to ${endpoint}?`,
|
|
61
|
+
message: `Do you want to review/edit the ~${tokenCount} token prompt before sending it to ${endpoint}?`,
|
|
59
62
|
default: true
|
|
60
63
|
});
|
|
61
64
|
if (reviewPrompt) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const tempFile = path_1.default.join(os_1.default.tmpdir(), `cloving_prompt_${Date.now()}.txt`);
|
|
66
|
+
fs_1.default.writeFileSync(tempFile, prompt);
|
|
67
|
+
const editor = process_1.default.env.EDITOR || 'nano';
|
|
68
|
+
const editProcess = (0, child_process_1.spawn)(editor, [tempFile], { stdio: 'inherit' });
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
editProcess.on('close', async (code) => {
|
|
67
71
|
if (code !== 0) {
|
|
68
|
-
console.error(
|
|
69
|
-
|
|
72
|
+
console.error(`${editor} exited with code ${code}`);
|
|
73
|
+
fs_1.default.unlinkSync(tempFile);
|
|
74
|
+
reject(new Error(`Editor exited with non-zero code`));
|
|
70
75
|
return;
|
|
71
76
|
}
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
process_1.default.exit(1);
|
|
77
|
+
const editedPrompt = fs_1.default.readFileSync(tempFile, 'utf-8').trim();
|
|
78
|
+
fs_1.default.unlinkSync(tempFile);
|
|
79
|
+
if (editedPrompt === '') {
|
|
80
|
+
console.log('Prompt is empty.');
|
|
81
|
+
resolve(null);
|
|
82
|
+
return;
|
|
79
83
|
}
|
|
80
|
-
resolve();
|
|
84
|
+
resolve(editedPrompt);
|
|
81
85
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
else {
|
|
87
|
-
console.error('Pipeline error:', err);
|
|
88
|
-
reject(err);
|
|
89
|
-
}
|
|
86
|
+
editProcess.on('error', (err) => {
|
|
87
|
+
console.error('Error launching editor:', err);
|
|
88
|
+
fs_1.default.unlinkSync(tempFile);
|
|
89
|
+
reject(err);
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
+
return prompt;
|
|
93
94
|
}
|
|
94
95
|
async generateText(request) {
|
|
95
96
|
request.temperature || (request.temperature = this.temperature);
|
|
96
97
|
const endpoint = this.adapter.getEndpoint();
|
|
97
98
|
const payload = this.adapter.getPayload(request);
|
|
98
99
|
const headers = this.adapter.getHeaders(this.apiKey);
|
|
99
|
-
await this.reviewPrompt(request.prompt, endpoint);
|
|
100
|
+
const reviewedPrompt = await this.reviewPrompt(request.prompt, endpoint);
|
|
101
|
+
if (reviewedPrompt === null) {
|
|
102
|
+
throw new Error('Operation cancelled by user');
|
|
103
|
+
}
|
|
104
|
+
request.prompt = reviewedPrompt;
|
|
100
105
|
try {
|
|
101
106
|
const response = await axios_1.default.post(endpoint, payload, { headers });
|
|
102
107
|
return this.adapter.extractResponse(response.data);
|
|
@@ -111,7 +116,4 @@ class ClovingGPT {
|
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
|
-
function isNodeError(error) {
|
|
115
|
-
return error instanceof Error && 'code' in error;
|
|
116
|
-
}
|
|
117
119
|
exports.default = ClovingGPT;
|
|
@@ -232,8 +232,20 @@ const code = async (options) => {
|
|
|
232
232
|
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, false);
|
|
233
233
|
let contextFiles = {};
|
|
234
234
|
try {
|
|
235
|
-
if (
|
|
236
|
-
|
|
235
|
+
if (files) {
|
|
236
|
+
let expandedFiles = [];
|
|
237
|
+
for (const file of files) {
|
|
238
|
+
const filePath = path_1.default.resolve(file);
|
|
239
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
|
|
240
|
+
const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
|
|
241
|
+
expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
files = expandedFiles;
|
|
248
|
+
for (const file of files) {
|
|
237
249
|
const filePath = path_1.default.resolve(file);
|
|
238
250
|
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
239
251
|
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
@@ -6,69 +6,111 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
7
7
|
const prompts_1 = require("@inquirer/prompts");
|
|
8
8
|
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
9
11
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
10
12
|
const git_utils_1 = require("../../utils/git_utils");
|
|
11
|
-
const config_utils_1 = require("../../utils/config_utils");
|
|
12
13
|
const string_utils_1 = require("../../utils/string_utils");
|
|
14
|
+
const command_utils_1 = require("../../utils/command_utils");
|
|
15
|
+
const config_utils_1 = require("../../utils/config_utils");
|
|
13
16
|
const review = async (options) => {
|
|
17
|
+
let { prompt, files } = options;
|
|
14
18
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
15
19
|
const gpt = new cloving_gpt_1.default(options);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const promptText = `==== begin diff =====
|
|
20
|
-
${gitDiff}
|
|
21
|
-
==== end diff =====
|
|
20
|
+
let promptText = `### Prompt
|
|
21
|
+
|
|
22
|
+
${prompt || 'Please provide a code review.'}
|
|
22
23
|
|
|
23
24
|
### Example of a well-structured response
|
|
24
25
|
|
|
25
|
-
# Code Review: Enhanced Code Generation and File Handling
|
|
26
|
+
# Code Review: Enhanced Code Generation and File Handling
|
|
26
27
|
|
|
27
|
-
## Changes Overview
|
|
28
|
+
## Changes Overview
|
|
28
29
|
|
|
29
|
-
These changes primarily focus on improving the code generation process and adding more robust file handling capabilities. The main modifications include:
|
|
30
|
+
These changes primarily focus on improving the code generation process and adding more robust file handling capabilities. The main modifications include:
|
|
30
31
|
|
|
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.
|
|
32
|
+
1. Enhancing the code generation prompt with a structured example response.
|
|
33
|
+
2. Implementing a new function to extract file names and contents from the generated code.
|
|
34
|
+
3. Adding a new option to save generated code directly to files.
|
|
35
|
+
4. Improving the copy-to-clipboard functionality to allow copying individual files.
|
|
36
|
+
5. Removing an unused import in the review.ts file.
|
|
36
37
|
|
|
37
|
-
## Reason for Changes
|
|
38
|
+
## Reason for Changes
|
|
38
39
|
|
|
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
|
+
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
|
|
|
41
|
-
## Detailed Description
|
|
42
|
+
## Detailed Description
|
|
42
43
|
|
|
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.
|
|
44
|
+
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.
|
|
45
|
+
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.
|
|
46
|
+
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.
|
|
47
|
+
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.
|
|
48
|
+
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
|
|
|
49
|
-
## Potential Bugs and Recommended Fixes
|
|
50
|
+
## Potential Bugs and Recommended Fixes
|
|
50
51
|
|
|
51
|
-
### 1. Possible undefined access in \`extractFilesAndContent\`
|
|
52
|
+
### 1. Possible undefined access in \`extractFilesAndContent\`
|
|
52
53
|
|
|
53
|
-
\`\`\`typescript
|
|
54
|
-
const contentMatch = regex.exec(rawCodeCommand)
|
|
55
|
-
if (contentMatch) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
\`\`\`
|
|
54
|
+
\`\`\`typescript
|
|
55
|
+
const contentMatch = regex.exec(rawCodeCommand)
|
|
56
|
+
if (contentMatch) {
|
|
57
|
+
files.push(fileName)
|
|
58
|
+
let content = contentMatch[1]
|
|
59
|
+
// ...
|
|
60
|
+
}
|
|
61
|
+
\`\`\`
|
|
61
62
|
|
|
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
|
+
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
|
|
|
64
|
-
\`\`\`typescript
|
|
65
|
-
const contentMatch = regex.exec(rawCodeCommand)
|
|
66
|
-
if (contentMatch && contentMatch[1]) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
\`\`\`
|
|
65
|
+
\`\`\`typescript
|
|
66
|
+
const contentMatch = regex.exec(rawCodeCommand)
|
|
67
|
+
if (contentMatch && contentMatch[1]) {
|
|
68
|
+
files.push(fileName)
|
|
69
|
+
let content = contentMatch[1]
|
|
70
|
+
// ...
|
|
71
|
+
}
|
|
72
|
+
\`\`\`
|
|
73
|
+
`;
|
|
74
|
+
let contextFiles = {};
|
|
75
|
+
try {
|
|
76
|
+
// Define the prompt for analysis
|
|
77
|
+
if (files) {
|
|
78
|
+
let expandedFiles = [];
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
const filePath = path_1.default.resolve(file);
|
|
81
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
|
|
82
|
+
const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
|
|
83
|
+
expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
files = expandedFiles;
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
const filePath = path_1.default.resolve(file);
|
|
92
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
93
|
+
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
94
|
+
contextFiles[file] = content;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const contextFileContents = Object.keys(contextFiles).map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`).join('\n');
|
|
98
|
+
promptText += `
|
|
99
|
+
### Description of App
|
|
100
|
+
|
|
101
|
+
${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
|
|
102
|
+
|
|
103
|
+
${contextFileContents}
|
|
104
|
+
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
109
|
+
promptText = `==== begin diff =====
|
|
110
|
+
${gitDiff}
|
|
111
|
+
==== end diff =====`;
|
|
112
|
+
}
|
|
113
|
+
promptText += `
|
|
72
114
|
|
|
73
115
|
### Request
|
|
74
116
|
|
|
@@ -76,6 +118,13 @@ Do not use any data from the example response structure, only use the structure.
|
|
|
76
118
|
I would like you to explain why these change are being made and document a description of these changes.
|
|
77
119
|
Also list any bugs in the new code as well as recommended fixes for those bugs with code examples.
|
|
78
120
|
Format the output of this code review in Markdown format.`;
|
|
121
|
+
if (prompt) {
|
|
122
|
+
promptText += `
|
|
123
|
+
|
|
124
|
+
### Additional Context
|
|
125
|
+
|
|
126
|
+
${prompt}`;
|
|
127
|
+
}
|
|
79
128
|
// get the analysis
|
|
80
129
|
const analysis = await gpt.generateText({ prompt: promptText });
|
|
81
130
|
// Print the analysis to the console
|
package/dist/index.js
CHANGED
|
@@ -106,6 +106,8 @@ generate
|
|
|
106
106
|
.alias('r')
|
|
107
107
|
.description('Review the code for commited changes that differ from the main/master branch')
|
|
108
108
|
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
109
|
+
.option('-f, --files <filenames...>', 'Specify filenames for files you want to review')
|
|
110
|
+
.option('-p, --prompt <prompt>', 'Add a prompt to the review')
|
|
109
111
|
.option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5-sonnet-20240620)')
|
|
110
112
|
.option('-t, --temperature <temperature>', 'Temperature for the model (default 0.2)')
|
|
111
113
|
.action(review_1.default);
|
package/dist/package.json
CHANGED
|
@@ -3,7 +3,7 @@ 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
|
-
exports.addFileOrDirectoryToContext = exports.readFileContent = exports.fetchModels = exports.runCommand = exports.generateFileList = exports.checkForSpecialFiles = exports.collectSpecialFileContents = exports.SPECIAL_FILES = void 0;
|
|
6
|
+
exports.addFileOrDirectoryToContext = exports.readFileContent = exports.fetchModels = exports.runCommand = exports.generateFileList = exports.getAllFilesInDirectory = exports.checkForSpecialFiles = exports.collectSpecialFileContents = exports.SPECIAL_FILES = void 0;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
@@ -33,6 +33,18 @@ const collectSpecialFileContents = () => {
|
|
|
33
33
|
exports.collectSpecialFileContents = collectSpecialFileContents;
|
|
34
34
|
const checkForSpecialFiles = () => exports.SPECIAL_FILES.some(file => fs_1.default.existsSync(file));
|
|
35
35
|
exports.checkForSpecialFiles = checkForSpecialFiles;
|
|
36
|
+
const getAllFilesInDirectory = async (dir) => {
|
|
37
|
+
const subdirs = await fs_1.default.promises.readdir(dir);
|
|
38
|
+
const files = await Promise.all(subdirs.map(async (subdir) => {
|
|
39
|
+
const res = path_1.default.resolve(dir, subdir);
|
|
40
|
+
if (subdir === 'node_modules' || subdir === '.git') {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
return (await fs_1.default.promises.stat(res)).isDirectory() ? (0, exports.getAllFilesInDirectory)(res) : res;
|
|
44
|
+
}));
|
|
45
|
+
return files.flat();
|
|
46
|
+
};
|
|
47
|
+
exports.getAllFilesInDirectory = getAllFilesInDirectory;
|
|
36
48
|
const generateFileList = async () => {
|
|
37
49
|
try {
|
|
38
50
|
const lsOutput = await (0, exports.runCommand)('ls', []);
|