cloving 0.1.1 → 0.1.3
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 +5 -5
- package/dist/cloving_gpt/index.js +8 -2
- package/dist/commands/config.js +1 -2
- package/dist/commands/init.js +70 -9
- package/dist/commands/unit_tests.js +108 -77
- package/dist/index.js +1 -0
- package/dist/utils/command_utils.js +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Cloving is an innovative tool designed to seamlessly integrate AI into various a
|
|
|
20
20
|
|
|
21
21
|
1. Clone this repository:
|
|
22
22
|
```bash
|
|
23
|
-
git clone https://github.com/
|
|
23
|
+
git clone https://github.com/cloving-ai/cloving-cli.git
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
2. Navigate to the project directory:
|
|
@@ -28,15 +28,15 @@ Cloving is an innovative tool designed to seamlessly integrate AI into various a
|
|
|
28
28
|
cd cloving-cli
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
3. Install dependencies:
|
|
31
|
+
3. Install dependencies and link the package:
|
|
32
32
|
```bash
|
|
33
33
|
yarn install
|
|
34
|
+
yarn link
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
4.
|
|
37
|
+
4. Configure Cloving with your API key and model:
|
|
37
38
|
```bash
|
|
38
|
-
|
|
39
|
-
export CLOVING_API_KEY=your_api_key
|
|
39
|
+
cloving config
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Usage
|
|
@@ -58,7 +58,7 @@ class ClovingGPT {
|
|
|
58
58
|
less.stdin.end();
|
|
59
59
|
less.on('close', (code) => {
|
|
60
60
|
if (code !== 0) {
|
|
61
|
-
console.error('less command exited with an error
|
|
61
|
+
console.error('less command exited with an error');
|
|
62
62
|
resolve(false);
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
@@ -69,7 +69,13 @@ class ClovingGPT {
|
|
|
69
69
|
rlConfirm.question('Do you still want to continue? [Yn]: ', (confirmAnswer) => {
|
|
70
70
|
rlConfirm.close();
|
|
71
71
|
confirmAnswer = confirmAnswer.trim().toLowerCase();
|
|
72
|
-
|
|
72
|
+
if (confirmAnswer !== 'y' && confirmAnswer !== '') {
|
|
73
|
+
console.error('User cancelled the operation');
|
|
74
|
+
process_1.default.exit(1);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
resolve(true);
|
|
78
|
+
}
|
|
73
79
|
});
|
|
74
80
|
});
|
|
75
81
|
less.stdin.on('error', (err) => {
|
package/dist/commands/config.js
CHANGED
|
@@ -18,8 +18,7 @@ const config = async () => {
|
|
|
18
18
|
console.log('Available models:');
|
|
19
19
|
models.forEach((model, index) => console.log(`${index + 1}. ${model}`));
|
|
20
20
|
const modelIndex = parseInt(await (0, command_utils_1.promptUser)('Select a model by number: '), 10) - 1;
|
|
21
|
-
if (modelIndex < 0 || modelIndex >= models.length) {
|
|
22
|
-
console.error('Invalid selection.');
|
|
21
|
+
if (isNaN(modelIndex) || modelIndex < 0 || modelIndex >= models.length) {
|
|
23
22
|
throw new Error('Invalid selection');
|
|
24
23
|
}
|
|
25
24
|
const selectedModel = models[modelIndex];
|
package/dist/commands/init.js
CHANGED
|
@@ -22,19 +22,19 @@ const init = async (options) => {
|
|
|
22
22
|
if (specialFileNames.length > 0) {
|
|
23
23
|
console.log(`Cloving will analyze the list of files and the contents of the following files:
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
${specialFileNames.join("\n")}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Cloving will send AI a request to summarize the technologies used in this project.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
This will provide better context for future Cloving requests.
|
|
30
30
|
|
|
31
|
-
|
|
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
32
|
`);
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
35
|
console.log(`
|
|
36
|
-
|
|
37
|
-
|
|
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
38
|
`);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -80,10 +80,65 @@ const init = async (options) => {
|
|
|
80
80
|
${JSON.stringify(projectDetails, null, 2)}
|
|
81
81
|
|
|
82
82
|
Please return JSON-formatted metadata about the project, including:
|
|
83
|
+
|
|
83
84
|
- The programming language(s) used
|
|
84
85
|
- The detected framework(s) (if any)
|
|
85
86
|
- The version of the language(s)
|
|
86
87
|
|
|
88
|
+
Here is a typescript interface for the expected response:
|
|
89
|
+
|
|
90
|
+
interface LanguageConfig {
|
|
91
|
+
name: string
|
|
92
|
+
version?: string
|
|
93
|
+
primary?: boolean
|
|
94
|
+
directory: string
|
|
95
|
+
extension: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface FrameworkConfig {
|
|
99
|
+
name: string
|
|
100
|
+
type: string
|
|
101
|
+
version?: string
|
|
102
|
+
primary?: boolean
|
|
103
|
+
directory?: string
|
|
104
|
+
extension?: string
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface TestingFrameworkConfig {
|
|
108
|
+
name: string
|
|
109
|
+
type: string
|
|
110
|
+
version?: string
|
|
111
|
+
directory?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface BuildToolConfig {
|
|
115
|
+
name: string
|
|
116
|
+
type: string
|
|
117
|
+
version?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface LinterConfig {
|
|
121
|
+
name: string
|
|
122
|
+
version?: string
|
|
123
|
+
type?: string
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface DatabaseConfig {
|
|
127
|
+
name: string
|
|
128
|
+
primary?: boolean
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ClovingGPTConfig {
|
|
132
|
+
languages: LanguageConfig[]
|
|
133
|
+
frameworks: FrameworkConfig[]
|
|
134
|
+
testingFrameworks?: TestingFrameworkConfig[]
|
|
135
|
+
buildTools: BuildToolConfig[]
|
|
136
|
+
packageManager: string
|
|
137
|
+
linters: LinterConfig[]
|
|
138
|
+
databases?: DatabaseConfig[]
|
|
139
|
+
projectType: string
|
|
140
|
+
}
|
|
141
|
+
|
|
87
142
|
Here is an example response:
|
|
88
143
|
|
|
89
144
|
{
|
|
@@ -91,18 +146,24 @@ Here is an example response:
|
|
|
91
146
|
{
|
|
92
147
|
"name": "TypeScript",
|
|
93
148
|
"version": "~> 5.5.3"
|
|
94
|
-
"primary": true
|
|
149
|
+
"primary": true,
|
|
150
|
+
"directory": "src",
|
|
151
|
+
"extension": ".ts"
|
|
95
152
|
},
|
|
96
153
|
{
|
|
97
154
|
"name": "JavaScript",
|
|
98
|
-
"version": "ES6+"
|
|
155
|
+
"version": "ES6+",
|
|
156
|
+
"directory": "src",
|
|
157
|
+
"extension": ".js"
|
|
99
158
|
}
|
|
100
159
|
],
|
|
101
160
|
"frameworks": [
|
|
102
161
|
{
|
|
103
162
|
"name": "Node.js",
|
|
104
163
|
"type": "Runtime environment",
|
|
105
|
-
"primary": true
|
|
164
|
+
"primary": true,
|
|
165
|
+
"directory": "src",
|
|
166
|
+
"extension": ".js"
|
|
106
167
|
}
|
|
107
168
|
],
|
|
108
169
|
"testingFrameworks": [
|
|
@@ -3,33 +3,46 @@ 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 fs_1 =
|
|
7
|
-
const os_1 = __importDefault(require("os"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
6
|
+
const fs_1 = require("fs");
|
|
9
7
|
const child_process_1 = require("child_process");
|
|
10
8
|
const git_utils_1 = require("../utils/git_utils");
|
|
11
9
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
10
|
+
const command_utils_1 = require("../utils/command_utils");
|
|
11
|
+
const extractChangedFiles = (gitDiff) => {
|
|
12
|
+
const fileRegex = /diff --git a\/(.+?) b\/(.+?)\n/g;
|
|
13
|
+
const files = new Set();
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = fileRegex.exec(gitDiff)) !== null) {
|
|
16
|
+
files.add(match[1]);
|
|
17
|
+
}
|
|
18
|
+
return Array.from(files);
|
|
17
19
|
};
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
const setupTestingPrompt = async (gpt, config) => {
|
|
21
|
+
const prompt = `I have the following cloving.json configuration file:
|
|
22
|
+
|
|
23
|
+
==== begin cloving.json ====
|
|
24
|
+
${JSON.stringify(config, null, 2)}
|
|
25
|
+
==== end cloving.json ====
|
|
26
|
+
|
|
27
|
+
Please provide instructions on how to set up testing for this project.`;
|
|
28
|
+
const setupInstructions = await gpt.generateText({ prompt });
|
|
29
|
+
console.log(setupInstructions);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
};
|
|
32
|
+
const generatePrompt = (files, srcFiles, testFiles, gitDiff) => {
|
|
33
|
+
const filesList = files.length > 0 ? files.join('\n') : 'No files provided';
|
|
34
|
+
const diffSection = gitDiff ? `Here is my git diff:\n\n==== begin git diff ====\n${gitDiff}\n==== end git diff ====\n` : '';
|
|
35
|
+
return `Here is a list of code files I want to generate unit tests for:
|
|
36
|
+
|
|
37
|
+
==== begin list of files ====
|
|
38
|
+
${filesList}
|
|
39
|
+
==== end list of files ====
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
====
|
|
41
|
+
Here is a list of all my source files:
|
|
42
|
+
|
|
43
|
+
==== begin list of source files ====
|
|
44
|
+
${srcFiles}
|
|
45
|
+
==== end list of source files ====
|
|
33
46
|
|
|
34
47
|
Here is a list of all my test files:
|
|
35
48
|
|
|
@@ -37,12 +50,11 @@ Here is a list of all my test files:
|
|
|
37
50
|
${testFiles}
|
|
38
51
|
==== end list of test files ====
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
${diffSection}
|
|
54
|
+
|
|
55
|
+
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.
|
|
42
56
|
|
|
43
|
-
|
|
44
|
-
${gitDiff}
|
|
45
|
-
==== end git diff ====
|
|
57
|
+
Also, list any test files that might be relevant to these files.
|
|
46
58
|
|
|
47
59
|
Give me this output format for your answer:
|
|
48
60
|
|
|
@@ -51,69 +63,88 @@ Give me this output format for your answer:
|
|
|
51
63
|
app/models/foo.rb
|
|
52
64
|
app/views/baz/bar.html.erb
|
|
53
65
|
|
|
54
|
-
## interacted models
|
|
55
|
-
|
|
56
|
-
app/models/pop.rb
|
|
57
|
-
|
|
58
66
|
## relevant test files
|
|
59
67
|
|
|
60
68
|
test/models/foo_test.rb
|
|
61
69
|
test/controllers/baz_controller_test.rb`;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
};
|
|
71
|
+
const unitTests = async (options) => {
|
|
72
|
+
const { files } = options;
|
|
73
|
+
const gpt = new cloving_gpt_1.default(options);
|
|
74
|
+
// Read the cloving.json file
|
|
75
|
+
const config = (0, command_utils_1.readClovingConfig)();
|
|
76
|
+
const testingDirectory = config.testingFrameworks?.find((framework) => framework.directory)?.directory;
|
|
77
|
+
if (!testingDirectory) {
|
|
78
|
+
await setupTestingPrompt(gpt, config);
|
|
79
|
+
}
|
|
80
|
+
let allSrcFiles = [];
|
|
81
|
+
// Collect srcFiles for each language with specified directory and extension
|
|
82
|
+
for (const language of config.languages) {
|
|
83
|
+
if (language.directory && language.extension) {
|
|
84
|
+
const srcFiles = (0, child_process_1.execFileSync)('find', [language.directory, '-type', 'f', '-name', `*${language.extension}`]).toString().trim().split('\n');
|
|
85
|
+
allSrcFiles = allSrcFiles.concat(srcFiles);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Ensure allSrcFiles is unique
|
|
89
|
+
allSrcFiles = Array.from(new Set(allSrcFiles));
|
|
90
|
+
if (allSrcFiles.length === 0 || !testingDirectory) {
|
|
91
|
+
console.error('Could not find any source files to generate unit tests. Please run: cloving init. Then try again.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const testFiles = (0, child_process_1.execFileSync)('find', [testingDirectory, '-type', 'f']).toString().trim();
|
|
95
|
+
let contextFiles;
|
|
96
|
+
if (files) {
|
|
97
|
+
const prompt = generatePrompt(files, allSrcFiles.join('\n'), testFiles);
|
|
98
|
+
contextFiles = await gpt.generateText({ prompt });
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
102
|
+
const changedFiles = extractChangedFiles(gitDiff);
|
|
103
|
+
const prompt = generatePrompt(changedFiles, allSrcFiles.join('\n'), testFiles, gitDiff);
|
|
104
|
+
contextFiles = await gpt.generateText({ prompt });
|
|
105
|
+
}
|
|
106
|
+
// Initialize variables
|
|
107
|
+
const lines = [];
|
|
108
|
+
const context = [];
|
|
109
|
+
// Read input from temporary file
|
|
110
|
+
contextFiles.split('\n').forEach((line) => {
|
|
111
|
+
if (line.trim()) {
|
|
112
|
+
lines.push(line.trim());
|
|
74
113
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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);
|
|
114
|
+
});
|
|
115
|
+
for (const codeFile of lines) {
|
|
116
|
+
if (await fs_1.promises?.stat(codeFile).then(stat => stat.isFile()).catch(() => false)) {
|
|
117
|
+
const fileContents = await fs_1.promises.readFile(codeFile, 'utf-8');
|
|
118
|
+
context.push(`### **Contents of ${codeFile}**\n\n${fileContents}\n\n### **End of ${codeFile}**`);
|
|
97
119
|
}
|
|
98
|
-
|
|
120
|
+
}
|
|
121
|
+
// Generate the message for unit test creation
|
|
122
|
+
let analysis = '';
|
|
123
|
+
if ((files || []).length > 0) {
|
|
124
|
+
const message = `please create unit tests for these files:
|
|
125
|
+
|
|
126
|
+
## begin list of files
|
|
127
|
+
${(files || []).join('\n')}
|
|
128
|
+
## end list of files
|
|
129
|
+
|
|
130
|
+
## context
|
|
131
|
+
${context.join('\n\n')}`;
|
|
132
|
+
// Get the model and analysis using ClovingGPT
|
|
133
|
+
analysis = await gpt.generateText({ prompt: message });
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
99
136
|
const message = `please create unit tests for these changes:
|
|
100
137
|
|
|
138
|
+
## begin diff
|
|
101
139
|
${await (0, git_utils_1.getGitDiff)()}
|
|
140
|
+
## end diff
|
|
102
141
|
|
|
103
142
|
## context
|
|
104
143
|
|
|
105
|
-
${
|
|
106
|
-
tokenCount = await estimateTokens(message);
|
|
107
|
-
console.log(`Estimated token count: ${tokenCount}`);
|
|
108
|
-
// Clean up
|
|
109
|
-
fs_1.default.unlinkSync(tempFilePath);
|
|
144
|
+
${context.join('\n\n')}`;
|
|
110
145
|
// Get the model and analysis using ClovingGPT
|
|
111
|
-
|
|
112
|
-
// Print the output
|
|
113
|
-
console.log(analysis);
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
console.error('Error processing unit tests:', error.message);
|
|
146
|
+
analysis = await gpt.generateText({ prompt: message });
|
|
117
147
|
}
|
|
148
|
+
console.log(analysis);
|
|
118
149
|
};
|
|
119
150
|
exports.default = unitTests;
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ program
|
|
|
25
25
|
.command('unit-tests')
|
|
26
26
|
.description('Generate unit tests for the changes')
|
|
27
27
|
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
28
|
+
.option('-f, --files <filenames...>', 'Specify filenames for the unit tests')
|
|
28
29
|
.action(unit_tests_1.default);
|
|
29
30
|
program
|
|
30
31
|
.command('models')
|
|
@@ -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.getConfig = exports.saveConfig = exports.fetchModels = exports.promptUser = exports.runCommand = exports.generateFileList = exports.checkForSpecialFiles = exports.collectSpecialFileContents = exports.SPECIAL_FILES = exports.CONFIG_PATH = void 0;
|
|
6
|
+
exports.readClovingConfig = exports.getConfig = exports.saveConfig = exports.fetchModels = exports.promptUser = exports.runCommand = exports.generateFileList = exports.checkForSpecialFiles = exports.collectSpecialFileContents = exports.SPECIAL_FILES = exports.CONFIG_PATH = void 0;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const readline_1 = __importDefault(require("readline"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -155,3 +155,14 @@ const getConfig = (options) => {
|
|
|
155
155
|
}
|
|
156
156
|
};
|
|
157
157
|
exports.getConfig = getConfig;
|
|
158
|
+
const readClovingConfig = () => {
|
|
159
|
+
const configPath = path_1.default.resolve(process.cwd(), 'cloving.json');
|
|
160
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
161
|
+
const configFile = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
162
|
+
return JSON.parse(configFile);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
throw new Error('cloving.json file not found');
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
exports.readClovingConfig = readClovingConfig;
|