cloving 0.1.0
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/LICENSE.md +21 -0
- package/README.md +119 -0
- package/dist/abstract_gpt/index.js +55 -0
- package/dist/cloving_gpt/adapters/Adapter.js +2 -0
- package/dist/cloving_gpt/adapters/claude.js +60 -0
- package/dist/cloving_gpt/adapters/gpt4all.js +29 -0
- package/dist/cloving_gpt/adapters/index.js +2 -0
- package/dist/cloving_gpt/adapters/ollama.js +67 -0
- package/dist/cloving_gpt/adapters/openai.js +41 -0
- package/dist/cloving_gpt/index.js +116 -0
- package/dist/commands/analyze.js +47 -0
- package/dist/commands/check.js +7 -0
- package/dist/commands/commit.js +44 -0
- package/dist/commands/config.js +40 -0
- package/dist/commands/describe.js +235 -0
- package/dist/commands/init.js +169 -0
- package/dist/commands/models.js +17 -0
- package/dist/commands/review.js +50 -0
- package/dist/commands/unit_tests.js +119 -0
- package/dist/commands/update.js +7 -0
- package/dist/index.js +47 -0
- package/dist/utils/command_utils.js +151 -0
- package/dist/utils/gitUtils.js +42 -0
- package/dist/utils/git_utils.js +70 -0
- package/dist/utils/modelUtils.js +7 -0
- package/dist/utils/model_utils.js +7 -0
- package/dist/utils/string_utils.js +63 -0
- package/dist/utils/types.js +2 -0
- package/package.json +65 -0
|
@@ -0,0 +1,235 @@
|
|
|
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.describe = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const string_utils_1 = require("../utils/string_utils");
|
|
12
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
13
|
+
const readline_1 = __importDefault(require("readline"));
|
|
14
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
15
|
+
// List of special files to check
|
|
16
|
+
const specialFiles = [
|
|
17
|
+
'package.json',
|
|
18
|
+
'Gemfile',
|
|
19
|
+
'requirements.txt',
|
|
20
|
+
'Pipfile',
|
|
21
|
+
'pyproject.toml',
|
|
22
|
+
'pom.xml',
|
|
23
|
+
'build.gradle',
|
|
24
|
+
'.csproj',
|
|
25
|
+
'packages.config',
|
|
26
|
+
'composer.json',
|
|
27
|
+
'CMakeLists.txt',
|
|
28
|
+
'conanfile.txt',
|
|
29
|
+
'conanfile.py',
|
|
30
|
+
'go.mod',
|
|
31
|
+
'Cargo.toml',
|
|
32
|
+
'Package.swift',
|
|
33
|
+
'build.gradle.kts',
|
|
34
|
+
'Podfile',
|
|
35
|
+
'Cartfile',
|
|
36
|
+
'cpanfile',
|
|
37
|
+
'DESCRIPTION',
|
|
38
|
+
'mix.exs',
|
|
39
|
+
'build.sbt',
|
|
40
|
+
'pubspec.yaml',
|
|
41
|
+
'stack.yaml',
|
|
42
|
+
'cabal.project',
|
|
43
|
+
'Project.toml',
|
|
44
|
+
'rockspec',
|
|
45
|
+
'rebar.config',
|
|
46
|
+
'project.clj'
|
|
47
|
+
];
|
|
48
|
+
const runCommand = (command, args) => {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const process = (0, child_process_1.spawn)(command, args);
|
|
51
|
+
const output = [];
|
|
52
|
+
process.stdout.on('data', (data) => {
|
|
53
|
+
output.push(data.toString());
|
|
54
|
+
});
|
|
55
|
+
process.stderr.on('data', (data) => {
|
|
56
|
+
console.error(`stderr: ${data}`);
|
|
57
|
+
});
|
|
58
|
+
process.on('close', (code) => {
|
|
59
|
+
if (code !== 0) {
|
|
60
|
+
reject(new Error(`${command} process exited with code ${code}`));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
resolve(output.join('').trim().split('\n'));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const generateFileList = async () => {
|
|
69
|
+
try {
|
|
70
|
+
const lsOutput = await runCommand('ls', []);
|
|
71
|
+
const findOutput = await runCommand('find', ['.']);
|
|
72
|
+
const cleanedFindOutput = findOutput.map(file => file.startsWith('./') ? file.slice(2) : file);
|
|
73
|
+
const files = [...lsOutput, ...cleanedFindOutput];
|
|
74
|
+
const uniqueFiles = Array.from(new Set(files));
|
|
75
|
+
return uniqueFiles.filter(file => file && !file.includes('.git') && !file.includes('node_modules') && !file.includes('tmp'));
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('Error generating file list:', error.message);
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const collectSpecialFileContents = () => {
|
|
83
|
+
const specialFileContents = {};
|
|
84
|
+
for (const file of specialFiles) {
|
|
85
|
+
if (fs_1.default.existsSync(file)) {
|
|
86
|
+
try {
|
|
87
|
+
const content = fs_1.default.readFileSync(file, 'utf-8');
|
|
88
|
+
specialFileContents[file] = file.endsWith('.json') ? JSON.parse(content) : content;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
specialFileContents[file] = fs_1.default.readFileSync(file, 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return specialFileContents;
|
|
96
|
+
};
|
|
97
|
+
// Main function for the describe command
|
|
98
|
+
const describe = async () => {
|
|
99
|
+
// Generate a temporary file path
|
|
100
|
+
const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `describe_${Date.now()}.tmp`);
|
|
101
|
+
try {
|
|
102
|
+
// Read .gitignore and create ignore instance
|
|
103
|
+
const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
|
|
104
|
+
const ig = (0, ignore_1.default)();
|
|
105
|
+
if (fs_1.default.existsSync(gitignorePath)) {
|
|
106
|
+
const gitignoreContent = fs_1.default.readFileSync(gitignorePath, 'utf-8');
|
|
107
|
+
ig.add(gitignoreContent);
|
|
108
|
+
}
|
|
109
|
+
// Generate the file list excluding .git, node_modules and matching .gitignore patterns
|
|
110
|
+
const fileList = await generateFileList();
|
|
111
|
+
const filteredFileList = fileList.filter(file => {
|
|
112
|
+
try {
|
|
113
|
+
return !ig.ignores(file);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// Limit the file list to the first 100 files
|
|
120
|
+
const limitedFileList = filteredFileList.slice(0, 100);
|
|
121
|
+
// Collect the contents of special files
|
|
122
|
+
const specialFileContents = collectSpecialFileContents();
|
|
123
|
+
// Generate the JSON object for the AI chat model
|
|
124
|
+
const projectDetails = {
|
|
125
|
+
files: limitedFileList,
|
|
126
|
+
specialFiles: specialFileContents
|
|
127
|
+
};
|
|
128
|
+
const prompt = `Here is a JSON object describing my project:
|
|
129
|
+
${JSON.stringify(projectDetails, null, 2)}
|
|
130
|
+
|
|
131
|
+
Please return JSON-formatted metadata about the project, including:
|
|
132
|
+
- The programming language(s) used
|
|
133
|
+
- The detected framework(s) (if any)
|
|
134
|
+
- The version of the language(s)
|
|
135
|
+
|
|
136
|
+
Here is an example response:
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
"languages": [
|
|
140
|
+
{
|
|
141
|
+
"name": "TypeScript",
|
|
142
|
+
"version": "~> 5.5.3"
|
|
143
|
+
"primary": true
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "JavaScript",
|
|
147
|
+
"version": "ES6+"
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"frameworks": [
|
|
151
|
+
{
|
|
152
|
+
"name": "Node.js",
|
|
153
|
+
"type": "Runtime environment",
|
|
154
|
+
"primary": true
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
"testingFrameworks": [
|
|
158
|
+
{
|
|
159
|
+
"name": "Jest",
|
|
160
|
+
"type": "Testing framework",
|
|
161
|
+
"version": "29.7.0"
|
|
162
|
+
"directory": "tests"
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
"buildTools": [
|
|
166
|
+
{
|
|
167
|
+
"name": "TypeScript Compiler (tsc)",
|
|
168
|
+
"type": "Transpiler"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "Vite",
|
|
172
|
+
"type": "Build tool",
|
|
173
|
+
"version": "5.3.3"
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"packageManager": "Yarn",
|
|
177
|
+
"linters": [
|
|
178
|
+
{
|
|
179
|
+
"name": "ESLint",
|
|
180
|
+
"version": "9.6.0"
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"databases": [
|
|
184
|
+
{
|
|
185
|
+
"name": "MongoDB",
|
|
186
|
+
"version": "5.0.3",
|
|
187
|
+
"primary": true
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"projectType": "Command-line tool",
|
|
191
|
+
}`;
|
|
192
|
+
// Estimate and print token count if DEBUG=1
|
|
193
|
+
if (process.env.DEBUG === '1') {
|
|
194
|
+
(0, string_utils_1.estimateTokens)(prompt).then(tokenCount => {
|
|
195
|
+
console.log(`Estimated token count: ${tokenCount}`);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// Instantiate ClovingGPT and get the AI chat response
|
|
199
|
+
const gpt = new cloving_gpt_1.default();
|
|
200
|
+
const aiChatResponse = await gpt.generateText({ prompt });
|
|
201
|
+
// Write the AI chat response to a temporary file
|
|
202
|
+
fs_1.default.writeFileSync(tempFilePath, aiChatResponse);
|
|
203
|
+
// Extract JSON metadata from the AI response
|
|
204
|
+
const jsonMetadata = (0, string_utils_1.extractJsonMetadata)(aiChatResponse);
|
|
205
|
+
if (jsonMetadata) {
|
|
206
|
+
console.log(jsonMetadata);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
console.log(aiChatResponse);
|
|
210
|
+
}
|
|
211
|
+
const response = jsonMetadata || aiChatResponse;
|
|
212
|
+
// Clean up
|
|
213
|
+
fs_1.default.unlinkSync(tempFilePath);
|
|
214
|
+
// Ask user if they want to save the result to cloving.json
|
|
215
|
+
const rl = readline_1.default.createInterface({
|
|
216
|
+
input: process.stdin,
|
|
217
|
+
output: process.stdout
|
|
218
|
+
});
|
|
219
|
+
rl.question('Do you want to save this file to cloving.json to improve the context of future cloving requests? [Yn] ', (answer) => {
|
|
220
|
+
rl.close();
|
|
221
|
+
if (answer.toLowerCase() === 'y' || answer.trim() === '') {
|
|
222
|
+
fs_1.default.writeFileSync('cloving.json', response);
|
|
223
|
+
console.log('File saved to cloving.json');
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
console.log('File not saved.');
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.error('Error describing the project:', error.message);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
exports.describe = describe;
|
|
235
|
+
exports.default = exports.describe;
|
|
@@ -0,0 +1,169 @@
|
|
|
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.init = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const string_utils_1 = require("../utils/string_utils");
|
|
11
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
12
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
13
|
+
const string_utils_2 = require("../utils/string_utils");
|
|
14
|
+
const command_utils_1 = require("../utils/command_utils");
|
|
15
|
+
// Main function for the describe command
|
|
16
|
+
const init = async (options) => {
|
|
17
|
+
options.silent = (0, command_utils_1.getConfig)().silent || false;
|
|
18
|
+
const gpt = new cloving_gpt_1.default(options);
|
|
19
|
+
const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
|
|
20
|
+
const specialFileNames = Object.keys(specialFileContents).map(file => ' - ' + file);
|
|
21
|
+
if (!options.silent) {
|
|
22
|
+
if (specialFileNames.length > 0) {
|
|
23
|
+
console.log(`Cloving will analyze the list of files and the contents of the following files:
|
|
24
|
+
|
|
25
|
+
${specialFileNames.join("\n")}
|
|
26
|
+
|
|
27
|
+
Cloving will send AI a request to summarize the technologies used in this project.
|
|
28
|
+
|
|
29
|
+
This will provide better context for future Cloving requests.
|
|
30
|
+
|
|
31
|
+
For increased privacy, you can run \`cloving config\` and make sure to configure a local ollama model (llama3:70b-instruct currently works well for this) so that this data is not sent to an AI service provider.
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.log(`
|
|
36
|
+
This script will analyze the list of files in the current directory using GPT to summarize the
|
|
37
|
+
technologies used. This will provide better context for future Cloving requests.
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const config = (0, command_utils_1.getConfig)();
|
|
42
|
+
if (!config || !config?.primaryModel || !config?.models) {
|
|
43
|
+
console.error('No cloving configuration found. Please run `cloving config`');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!(0, command_utils_1.checkForSpecialFiles)()) {
|
|
47
|
+
const continueAnswer = await (0, command_utils_1.promptUser)('No special files detected. Are you currently inside a software project\'s main directory? Do you want to continue analyzing this directory for the Cloving setup process? [Yn] ');
|
|
48
|
+
if (continueAnswer.toLowerCase() !== 'y' && continueAnswer.trim() !== '') {
|
|
49
|
+
console.log('Operation aborted by the user.');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `describe_${Date.now()}.tmp`);
|
|
54
|
+
try {
|
|
55
|
+
const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
|
|
56
|
+
const ig = (0, ignore_1.default)();
|
|
57
|
+
if (fs_1.default.existsSync(gitignorePath)) {
|
|
58
|
+
const gitignoreContent = fs_1.default.readFileSync(gitignorePath, 'utf-8');
|
|
59
|
+
ig.add(gitignoreContent);
|
|
60
|
+
}
|
|
61
|
+
const fileList = await (0, command_utils_1.generateFileList)();
|
|
62
|
+
const filteredFileList = fileList.filter(file => {
|
|
63
|
+
try {
|
|
64
|
+
return !ig.ignores(file);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const limitedFileList = filteredFileList.slice(0, 100);
|
|
71
|
+
if (specialFileNames.length === 0) {
|
|
72
|
+
console.error('No special files found.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const projectDetails = {
|
|
76
|
+
files: limitedFileList,
|
|
77
|
+
specialFiles: specialFileContents
|
|
78
|
+
};
|
|
79
|
+
const prompt = `Here is a JSON object describing my project:
|
|
80
|
+
${JSON.stringify(projectDetails, null, 2)}
|
|
81
|
+
|
|
82
|
+
Please return JSON-formatted metadata about the project, including:
|
|
83
|
+
- The programming language(s) used
|
|
84
|
+
- The detected framework(s) (if any)
|
|
85
|
+
- The version of the language(s)
|
|
86
|
+
|
|
87
|
+
Here is an example response:
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
"languages": [
|
|
91
|
+
{
|
|
92
|
+
"name": "TypeScript",
|
|
93
|
+
"version": "~> 5.5.3"
|
|
94
|
+
"primary": true
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "JavaScript",
|
|
98
|
+
"version": "ES6+"
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
"frameworks": [
|
|
102
|
+
{
|
|
103
|
+
"name": "Node.js",
|
|
104
|
+
"type": "Runtime environment",
|
|
105
|
+
"primary": true
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"testingFrameworks": [
|
|
109
|
+
{
|
|
110
|
+
"name": "Jest",
|
|
111
|
+
"type": "Testing framework",
|
|
112
|
+
"version": "29.7.0"
|
|
113
|
+
"directory": "tests"
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
"buildTools": [
|
|
117
|
+
{
|
|
118
|
+
"name": "TypeScript Compiler (tsc)",
|
|
119
|
+
"type": "Transpiler"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "Vite",
|
|
123
|
+
"type": "Build tool",
|
|
124
|
+
"version": "5.3.3"
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"packageManager": "Yarn",
|
|
128
|
+
"linters": [
|
|
129
|
+
{
|
|
130
|
+
"name": "ESLint",
|
|
131
|
+
"version": "9.6.0"
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
"databases": [
|
|
135
|
+
{
|
|
136
|
+
"name": "MongoDB",
|
|
137
|
+
"version": "5.0.3",
|
|
138
|
+
"primary": true
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
"projectType": "Command-line tool",
|
|
142
|
+
}`;
|
|
143
|
+
if (process.env.DEBUG === '1') {
|
|
144
|
+
(0, string_utils_1.estimateTokens)(prompt).then(tokenCount => {
|
|
145
|
+
console.log(`Estimated token count: ${tokenCount}`);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const aiChatResponse = await gpt.generateText({ prompt });
|
|
149
|
+
const cleanAiChatResponse = (0, string_utils_2.extractJsonMetadata)(aiChatResponse);
|
|
150
|
+
fs_1.default.writeFileSync(tempFilePath, cleanAiChatResponse);
|
|
151
|
+
// Save the AI chat response to cloving.json
|
|
152
|
+
fs_1.default.writeFileSync('cloving.json', cleanAiChatResponse);
|
|
153
|
+
console.log('[done] Project data saved to cloving.json');
|
|
154
|
+
// Prompt the user if they want to review the generated cloving.json
|
|
155
|
+
if (!options.silent) {
|
|
156
|
+
const reviewAnswer = await (0, command_utils_1.promptUser)('Do you want to review the generated data? [Yn] ');
|
|
157
|
+
if (reviewAnswer.toLowerCase() === 'y' || reviewAnswer.trim() === '') {
|
|
158
|
+
console.log(cleanAiChatResponse);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Clean up
|
|
162
|
+
fs_1.default.unlinkSync(tempFilePath);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.error('Error describing the project:', error.message);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
exports.init = init;
|
|
169
|
+
exports.default = exports.init;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// models.ts
|
|
4
|
+
const claude_1 = require("../cloving_gpt/adapters/claude");
|
|
5
|
+
const openai_1 = require("../cloving_gpt/adapters/openai");
|
|
6
|
+
const ollama_1 = require("../cloving_gpt/adapters/ollama");
|
|
7
|
+
const listModels = async () => {
|
|
8
|
+
try {
|
|
9
|
+
await ollama_1.OllamaAdapter.listSupportedModels();
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
// do nothing, no ollama server running
|
|
13
|
+
}
|
|
14
|
+
claude_1.ClaudeAdapter.listSupportedModels();
|
|
15
|
+
openai_1.OpenAIAdapter.listSupportedModels();
|
|
16
|
+
};
|
|
17
|
+
exports.default = listModels;
|
|
@@ -0,0 +1,50 @@
|
|
|
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 readline_1 = __importDefault(require("readline"));
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const git_utils_1 = require("../utils/git_utils");
|
|
9
|
+
const string_utils_1 = require("../utils/string_utils");
|
|
10
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
11
|
+
const command_utils_1 = require("../utils/command_utils");
|
|
12
|
+
const review = async (options) => {
|
|
13
|
+
options.silent = (0, command_utils_1.getConfig)().silent || false;
|
|
14
|
+
const gpt = new cloving_gpt_1.default(options);
|
|
15
|
+
try {
|
|
16
|
+
// Define the prompt for analysis
|
|
17
|
+
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
18
|
+
const prompt = `Explain why the change is being made and document a description of these changes.
|
|
19
|
+
Also list any bugs in the new code as well as recommended fixes for those bugs with code examples.
|
|
20
|
+
Format the output of this code review in Markdown format.
|
|
21
|
+
|
|
22
|
+
${gitDiff}`;
|
|
23
|
+
// get the analysis
|
|
24
|
+
const analysis = await gpt.generateText({ prompt });
|
|
25
|
+
const markdown = (0, string_utils_1.extractMarkdown)(analysis);
|
|
26
|
+
// Print the analysis to the console
|
|
27
|
+
console.log(markdown);
|
|
28
|
+
// Prompt the user to copy the analysis to the clipboard
|
|
29
|
+
const rl = readline_1.default.createInterface({
|
|
30
|
+
input: process.stdin,
|
|
31
|
+
output: process.stdout
|
|
32
|
+
});
|
|
33
|
+
rl.question('Do you want to copy the analysis to the clipboard? [Y/n] ', (answer) => {
|
|
34
|
+
if (answer.toLowerCase() === 'y' || answer === '') {
|
|
35
|
+
try {
|
|
36
|
+
(0, child_process_1.execFileSync)('pbcopy', { input: markdown });
|
|
37
|
+
console.log('Analysis copied to clipboard');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error('Error: pbcopy command not found. Unable to copy to clipboard.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
rl.close();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error('Error during analysis:', error.message);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.default = review;
|
|
@@ -0,0 +1,119 @@
|
|
|
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 child_process_1 = require("child_process");
|
|
10
|
+
const git_utils_1 = require("../utils/git_utils");
|
|
11
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
12
|
+
// Function to estimate token count
|
|
13
|
+
const estimateTokens = async (text) => {
|
|
14
|
+
const charCount = text.length;
|
|
15
|
+
const tokenCount = Math.ceil(charCount / 4);
|
|
16
|
+
return tokenCount;
|
|
17
|
+
};
|
|
18
|
+
// Main function for the unit_tests command
|
|
19
|
+
const unitTests = async (options) => {
|
|
20
|
+
const gpt = new cloving_gpt_1.default(options);
|
|
21
|
+
// Generate a temporary file path
|
|
22
|
+
const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `unit_tests_${Date.now()}.tmp`);
|
|
23
|
+
try {
|
|
24
|
+
// Generate the prompt for the AI chat model
|
|
25
|
+
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
26
|
+
const modelFiles = (0, child_process_1.execFileSync)('find', ['app/models', '-type', 'f']).toString().trim();
|
|
27
|
+
const testFiles = (0, child_process_1.execFileSync)('find', ['spec', '-type', 'f']).toString().trim();
|
|
28
|
+
const prompt = `Here is a list of all my model files:
|
|
29
|
+
|
|
30
|
+
==== begin list of models ====
|
|
31
|
+
${modelFiles}
|
|
32
|
+
==== end list of models ====
|
|
33
|
+
|
|
34
|
+
Here is a list of all my test files:
|
|
35
|
+
|
|
36
|
+
==== begin list of test files ====
|
|
37
|
+
${testFiles}
|
|
38
|
+
==== end list of test files ====
|
|
39
|
+
|
|
40
|
+
Please enumerate all the files in this git diff as well as the file names of any models that
|
|
41
|
+
this code interacts with. Also, list any test files that might be relevant to these changes:
|
|
42
|
+
|
|
43
|
+
==== begin git diff ====
|
|
44
|
+
${gitDiff}
|
|
45
|
+
==== end git diff ====
|
|
46
|
+
|
|
47
|
+
Give me this output format for your answer:
|
|
48
|
+
|
|
49
|
+
## files
|
|
50
|
+
|
|
51
|
+
app/models/foo.rb
|
|
52
|
+
app/views/baz/bar.html.erb
|
|
53
|
+
|
|
54
|
+
## interacted models
|
|
55
|
+
|
|
56
|
+
app/models/pop.rb
|
|
57
|
+
|
|
58
|
+
## relevant test files
|
|
59
|
+
|
|
60
|
+
test/models/foo_test.rb
|
|
61
|
+
test/controllers/baz_controller_test.rb`;
|
|
62
|
+
// Estimate and print token count
|
|
63
|
+
let tokenCount = await estimateTokens(prompt);
|
|
64
|
+
console.log(`Estimated token count: ${tokenCount}`);
|
|
65
|
+
// Instantiate ClovingGPT and get the AI chat response
|
|
66
|
+
const gpt = new cloving_gpt_1.default();
|
|
67
|
+
const aiChatResponse = await gpt.generateText({ prompt });
|
|
68
|
+
fs_1.default.writeFileSync(tempFilePath, aiChatResponse);
|
|
69
|
+
// Handle "files" argument
|
|
70
|
+
if (process.argv.includes('files')) {
|
|
71
|
+
console.log(aiChatResponse);
|
|
72
|
+
fs_1.default.unlinkSync(tempFilePath);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
// Initialize variables
|
|
76
|
+
const lines = [];
|
|
77
|
+
// Read input from temporary file
|
|
78
|
+
const fileContent = fs_1.default.readFileSync(tempFilePath, 'utf-8');
|
|
79
|
+
fileContent.split('\n').forEach((line) => {
|
|
80
|
+
if (line.trim()) {
|
|
81
|
+
lines.push(line.trim());
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// Generate output
|
|
85
|
+
fs_1.default.writeFileSync(tempFilePath, '## files\n\n');
|
|
86
|
+
lines.forEach((line) => {
|
|
87
|
+
if (fs_1.default.existsSync(line) && fs_1.default.statSync(line).isFile()) {
|
|
88
|
+
const fileContents = fs_1.default.readFileSync(line, 'utf-8');
|
|
89
|
+
fs_1.default.appendFileSync(tempFilePath, `### **Contents of ${line}**\n\n${fileContents}\n\n`);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// Handle "code" argument
|
|
93
|
+
if (process.argv.includes('code')) {
|
|
94
|
+
console.log(fs_1.default.readFileSync(tempFilePath, 'utf-8'));
|
|
95
|
+
fs_1.default.unlinkSync(tempFilePath);
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
// Generate the message for unit test creation
|
|
99
|
+
const message = `please create unit tests for these changes:
|
|
100
|
+
|
|
101
|
+
${await (0, git_utils_1.getGitDiff)()}
|
|
102
|
+
|
|
103
|
+
## context
|
|
104
|
+
|
|
105
|
+
${fs_1.default.readFileSync(tempFilePath, 'utf-8')}`;
|
|
106
|
+
tokenCount = await estimateTokens(message);
|
|
107
|
+
console.log(`Estimated token count: ${tokenCount}`);
|
|
108
|
+
// Clean up
|
|
109
|
+
fs_1.default.unlinkSync(tempFilePath);
|
|
110
|
+
// Get the model and analysis using ClovingGPT
|
|
111
|
+
const analysis = await gpt.generateText({ prompt: message });
|
|
112
|
+
// Print the output
|
|
113
|
+
console.log(analysis);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.error('Error processing unit tests:', error.message);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
exports.default = unitTests;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const commit_1 = __importDefault(require("./commands/commit"));
|
|
9
|
+
const unit_tests_1 = __importDefault(require("./commands/unit_tests"));
|
|
10
|
+
const review_1 = __importDefault(require("./commands/review"));
|
|
11
|
+
const init_1 = __importDefault(require("./commands/init"));
|
|
12
|
+
const config_1 = __importDefault(require("./commands/config"));
|
|
13
|
+
const models_1 = __importDefault(require("./commands/models"));
|
|
14
|
+
const program = new commander_1.Command();
|
|
15
|
+
program
|
|
16
|
+
.name('cloving')
|
|
17
|
+
.description('Integrate AI into your development workflow for generating commit messages, code reviews, and unit tests.')
|
|
18
|
+
.version('1.0.0');
|
|
19
|
+
program
|
|
20
|
+
.command('commit')
|
|
21
|
+
.description('Generate a commit message and commit the changes')
|
|
22
|
+
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
23
|
+
.action(commit_1.default);
|
|
24
|
+
program
|
|
25
|
+
.command('unit-tests')
|
|
26
|
+
.description('Generate unit tests for the changes')
|
|
27
|
+
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
28
|
+
.action(unit_tests_1.default);
|
|
29
|
+
program
|
|
30
|
+
.command('models')
|
|
31
|
+
.description('List available models')
|
|
32
|
+
.action(models_1.default);
|
|
33
|
+
program
|
|
34
|
+
.command('review')
|
|
35
|
+
.description('Review the changes and propose ')
|
|
36
|
+
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
37
|
+
.action(review_1.default);
|
|
38
|
+
program
|
|
39
|
+
.command('init')
|
|
40
|
+
.description('Setup cloving in the current project')
|
|
41
|
+
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
42
|
+
.action(init_1.default);
|
|
43
|
+
program
|
|
44
|
+
.command('config')
|
|
45
|
+
.description('Configure cloving with your API key and model')
|
|
46
|
+
.action(config_1.default);
|
|
47
|
+
program.parse(process.argv);
|