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 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/clovingai/cloving-cli.git
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. Set up your AI chat model environment variables:
37
+ 4. Configure Cloving with your API key and model:
37
38
  ```bash
38
- export CLOVING_MODEL=your_preferred_model
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
- resolve(confirmAnswer === 'y' || confirmAnswer === '');
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) => {
@@ -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];
@@ -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
- ${specialFileNames.join("\n")}
25
+ ${specialFileNames.join("\n")}
26
26
 
27
- Cloving will send AI a request to summarize the technologies used in this project.
27
+ Cloving will send AI a request to summarize the technologies used in this project.
28
28
 
29
- This will provide better context for future Cloving requests.
29
+ This will provide better context for future Cloving requests.
30
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.
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
- 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.
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 = __importDefault(require("fs"));
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
- // 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;
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
- // 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:
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
- ==== begin list of models ====
31
- ${modelFiles}
32
- ==== end list of models ====
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
- 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:
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
- ==== begin git diff ====
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
- // 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);
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
- // 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);
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
- // Generate the message for unit test creation
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
- ${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);
144
+ ${context.join('\n\n')}`;
110
145
  // 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);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {