cloving 0.3.3 → 0.3.5

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.
@@ -7,7 +7,7 @@ class ClaudeAdapter {
7
7
  this.anthropicVersion = '2023-06-01';
8
8
  }
9
9
  static listSupportedModels() {
10
- ClaudeAdapter.supportedModels.forEach(model => {
10
+ ClaudeAdapter.supportedModels.forEach((model) => {
11
11
  console.log(model);
12
12
  });
13
13
  }
@@ -18,7 +18,7 @@ class ClaudeAdapter {
18
18
  return {
19
19
  'x-api-key': apiKey,
20
20
  'Content-Type': 'application/json',
21
- 'anthropic-version': this.anthropicVersion
21
+ 'anthropic-version': this.anthropicVersion,
22
22
  };
23
23
  }
24
24
  getPayload(request, stream = false) {
@@ -30,10 +30,12 @@ class ClaudeAdapter {
30
30
  return {
31
31
  model: this.model.replace(':', '-'),
32
32
  system,
33
- messages: request.messages && request.messages.length > 0 ? request.messages : [{ role: "user", content: request.prompt }],
33
+ messages: request.messages && request.messages.length > 0
34
+ ? request.messages
35
+ : [{ role: 'user', content: request.prompt }],
34
36
  max_tokens: request.maxTokens || 4096,
35
37
  temperature: request.temperature || 0.2,
36
- stream
38
+ stream,
37
39
  };
38
40
  }
39
41
  extractResponse(data) {
@@ -67,7 +69,7 @@ class ClaudeAdapter {
67
69
  const output = parsedObject?.delta?.text || '';
68
70
  return {
69
71
  output,
70
- lastChar
72
+ lastChar,
71
73
  };
72
74
  }
73
75
  catch (error) {
@@ -6,7 +6,7 @@ class GeminiAdapter {
6
6
  this.model = model;
7
7
  }
8
8
  static async listSupportedModels() {
9
- GeminiAdapter.supportedModels.forEach(model => {
9
+ GeminiAdapter.supportedModels.forEach((model) => {
10
10
  console.log(model);
11
11
  });
12
12
  }
@@ -21,7 +21,7 @@ class GeminiAdapter {
21
21
  getHeaders(apiKey) {
22
22
  return {
23
23
  'Content-Type': 'application/json',
24
- 'x-goog-api-key': apiKey
24
+ 'x-goog-api-key': apiKey,
25
25
  };
26
26
  }
27
27
  getPayload(request, stream = false) {
@@ -31,22 +31,27 @@ class GeminiAdapter {
31
31
  role: 'user', // or 'model' if generated from the model
32
32
  parts: [
33
33
  {
34
- text: request.prompt
35
- }
36
- ]
34
+ text: request.prompt,
35
+ },
36
+ ],
37
37
  },
38
38
  ],
39
39
  generationConfig: {
40
40
  temperature: request.temperature || 0.2,
41
41
  topK: 40,
42
42
  topP: 0.95,
43
- maxOutputTokens: 1024
44
- }
43
+ maxOutputTokens: 1024,
44
+ },
45
45
  };
46
46
  }
47
47
  extractResponse(data) {
48
48
  try {
49
- if (data && data.candidates && data.candidates[0] && data.candidates[0].content && data.candidates[0].content.parts && data.candidates[0].content.parts[0]) {
49
+ if (data &&
50
+ data.candidates &&
51
+ data.candidates[0] &&
52
+ data.candidates[0].content &&
53
+ data.candidates[0].content.parts &&
54
+ data.candidates[0].content.parts[0]) {
50
55
  return data.candidates[0].content.parts[0].text;
51
56
  }
52
57
  else {
@@ -74,7 +79,7 @@ class GeminiAdapter {
74
79
  const output = parsedObject?.candidates[0]?.content?.parts[0]?.text || '';
75
80
  return {
76
81
  output,
77
- lastChar
82
+ lastChar,
78
83
  };
79
84
  }
80
85
  catch (error) {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MistralAdapter = void 0;
4
4
  class MistralAdapter {
5
5
  static listSupportedModels() {
6
- MistralAdapter.supportedModels.forEach(model => {
6
+ MistralAdapter.supportedModels.forEach((model) => {
7
7
  console.log(model);
8
8
  });
9
9
  }
@@ -15,23 +15,25 @@ class MistralAdapter {
15
15
  }
16
16
  getHeaders(apiKey) {
17
17
  return {
18
- 'Authorization': `Bearer ${apiKey}`,
19
- 'Content-Type': 'application/json'
18
+ Authorization: `Bearer ${apiKey}`,
19
+ 'Content-Type': 'application/json',
20
20
  };
21
21
  }
22
22
  getPayload(request, stream = false) {
23
23
  return {
24
24
  model: this.model.replace(':', '-'),
25
- messages: request.messages && request.messages.length > 0 ? request.messages : [{ role: "user", content: request.prompt }],
25
+ messages: request.messages && request.messages.length > 0
26
+ ? request.messages
27
+ : [{ role: 'user', content: request.prompt }],
26
28
  max_tokens: request.maxTokens,
27
29
  temperature: request.temperature || 0.2,
28
- stream
30
+ stream,
29
31
  };
30
32
  }
31
33
  extractResponse(data) {
32
34
  return data.choices[0].message.content;
33
35
  }
34
- // data example:
36
+ // data example:
35
37
  // {
36
38
  // id: 'chatcmpl-9tL477A98d54qvVQqJT010bm8BLJl',
37
39
  // object: 'chat.completion.chunk',
@@ -64,7 +66,7 @@ class MistralAdapter {
64
66
  const output = parsedObject?.choices[0]?.delta?.content || '';
65
67
  return {
66
68
  output,
67
- lastChar
69
+ lastChar,
68
70
  };
69
71
  }
70
72
  catch (error) {
@@ -13,13 +13,13 @@ class OllamaAdapter {
13
13
  try {
14
14
  const endpoint = 'http://localhost:11434/api/tags';
15
15
  const headers = {
16
- 'Content-Type': 'application/json'
16
+ 'Content-Type': 'application/json',
17
17
  };
18
18
  const response = await axios_1.default.get(endpoint, { headers });
19
19
  const data = response.data;
20
20
  if (data && Array.isArray(data.models)) {
21
21
  OllamaAdapter.supportedModels = data.models.map((model) => `ollama:${model.name}`);
22
- OllamaAdapter.supportedModels.forEach(model => {
22
+ OllamaAdapter.supportedModels.forEach((model) => {
23
23
  console.log(model);
24
24
  });
25
25
  }
@@ -36,15 +36,15 @@ class OllamaAdapter {
36
36
  }
37
37
  getHeaders(apiKey) {
38
38
  return {
39
- 'Authorization': `Bearer ${apiKey}`,
40
- 'Content-Type': 'application/json'
39
+ Authorization: `Bearer ${apiKey}`,
40
+ 'Content-Type': 'application/json',
41
41
  };
42
42
  }
43
43
  getPayload(request, stream = false) {
44
44
  return {
45
45
  model: this.model,
46
46
  prompt: request.prompt,
47
- stream
47
+ stream,
48
48
  };
49
49
  }
50
50
  extractResponse(data) {
@@ -62,7 +62,7 @@ class OllamaAdapter {
62
62
  throw error;
63
63
  }
64
64
  }
65
- // data example:
65
+ // data example:
66
66
  // {
67
67
  // model: 'dolphin-llama3:latest',
68
68
  // created_at: '2024-08-06T21:04:34.616355Z',
@@ -84,7 +84,7 @@ class OllamaAdapter {
84
84
  const output = parsedObject?.response || '';
85
85
  return {
86
86
  output,
87
- lastChar
87
+ lastChar,
88
88
  };
89
89
  }
90
90
  catch (error) {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OpenAIAdapter = void 0;
4
4
  class OpenAIAdapter {
5
5
  static listSupportedModels() {
6
- OpenAIAdapter.supportedModels.forEach(model => {
6
+ OpenAIAdapter.supportedModels.forEach((model) => {
7
7
  console.log(model);
8
8
  });
9
9
  }
@@ -15,23 +15,25 @@ class OpenAIAdapter {
15
15
  }
16
16
  getHeaders(apiKey) {
17
17
  return {
18
- 'Authorization': `Bearer ${apiKey}`,
19
- 'Content-Type': 'application/json'
18
+ Authorization: `Bearer ${apiKey}`,
19
+ 'Content-Type': 'application/json',
20
20
  };
21
21
  }
22
22
  getPayload(request, stream = false) {
23
23
  return {
24
24
  model: this.model.replace(':', '-'),
25
- messages: request.messages && request.messages.length > 0 ? request.messages : [{ role: "user", content: request.prompt }],
25
+ messages: request.messages && request.messages.length > 0
26
+ ? request.messages
27
+ : [{ role: 'user', content: request.prompt }],
26
28
  max_tokens: request.maxTokens,
27
29
  temperature: request.temperature || 0.2,
28
- stream
30
+ stream,
29
31
  };
30
32
  }
31
33
  extractResponse(data) {
32
34
  return data.choices[0].message.content;
33
35
  }
34
- // data example:
36
+ // data example:
35
37
  // {
36
38
  // id: 'chatcmpl-9tL477A98d54qvVQqJT010bm8BLJl',
37
39
  // object: 'chat.completion.chunk',
@@ -64,7 +66,7 @@ class OpenAIAdapter {
64
66
  const output = parsedObject?.choices[0]?.delta?.content || '';
65
67
  return {
66
68
  output,
67
- lastChar
69
+ lastChar,
68
70
  };
69
71
  }
70
72
  catch (error) {
@@ -10,6 +10,7 @@ const axios_1 = __importDefault(require("axios"));
10
10
  const child_process_1 = require("child_process");
11
11
  const process_1 = __importDefault(require("process"));
12
12
  const prompts_1 = require("@inquirer/prompts");
13
+ const colors_1 = __importDefault(require("colors"));
13
14
  const claude_1 = require("./adapters/claude");
14
15
  const openai_1 = require("./adapters/openai");
15
16
  const mistral_1 = require("./adapters/mistral");
@@ -58,11 +59,13 @@ class ClovingGPT {
58
59
  async reviewPrompt(prompt, messages, endpoint) {
59
60
  if (this.silent)
60
61
  return prompt;
61
- const fullPrompt = messages.map(m => m.role === 'user' ? `# Task\n\n${m.content}` : m.content).join('\n\n');
62
+ const fullPrompt = messages
63
+ .map((m) => (m.role === 'user' ? `# Task\n\n${m.content}` : m.content))
64
+ .join('\n\n');
62
65
  const tokenCount = Math.ceil(fullPrompt.length / 4).toLocaleString();
63
66
  const reviewPrompt = await (0, prompts_1.confirm)({
64
67
  message: `Do you want to review the ~${tokenCount} token prompt before sending it to ${endpoint}?`,
65
- default: true
68
+ default: true,
66
69
  });
67
70
  if (reviewPrompt) {
68
71
  const tempFile = path_1.default.join(os_1.default.tmpdir(), `cloving_prompt_${Date.now()}.txt`);
@@ -86,7 +89,7 @@ class ClovingGPT {
86
89
  }
87
90
  const confirmPrompt = await (0, prompts_1.confirm)({
88
91
  message: `Are you sure you want to continue?`,
89
- default: true
92
+ default: true,
90
93
  });
91
94
  if (!confirmPrompt) {
92
95
  resolve(null);
@@ -125,16 +128,18 @@ class ClovingGPT {
125
128
  data: payload,
126
129
  headers: headers,
127
130
  responseType: 'stream',
128
- timeout: this.timeout
131
+ timeout: this.timeout,
129
132
  });
130
133
  }
131
134
  catch (err) {
132
135
  const error = err;
133
136
  if (error.response && error.response.status === 429) {
134
137
  attempt++;
135
- const tokenCount = payload.messages.reduce((acc, message) => acc + Math.ceil(message.content.length / 4), 0).toLocaleString();
136
- console.warn(`Rate limit error for this ${tokenCount} token prompt. Possibly past the token limit for this AI API. Try including fewer code files. Retrying in ${delay / 1000} seconds... (Attempt ${attempt} of ${maxRetries})`);
137
- await new Promise(resolve => setTimeout(resolve, delay));
138
+ const tokenCount = payload.messages
139
+ .reduce((acc, message) => acc + Math.ceil(message.content.length / 4), 0)
140
+ .toLocaleString();
141
+ console.warn(`${colors_1.default.red.bold('ERROR')} Rate limit error for this ${tokenCount} token prompt. Possibly past the token limit for this AI API. Try including fewer code files. Retrying in ${delay / 1000} seconds... (Attempt ${attempt} of ${maxRetries})`);
142
+ await new Promise((resolve) => setTimeout(resolve, delay));
138
143
  delay *= 2; // Exponential backoff
139
144
  }
140
145
  else {
@@ -17,20 +17,20 @@ const config = async () => {
17
17
  }
18
18
  let continueConfig = true;
19
19
  while (continueConfig) {
20
- const providers = [...new Set(models.map(model => model.split(':')[0]))];
20
+ const providers = [...new Set(models.map((model) => model.split(':')[0]))];
21
21
  const selectedProvider = await (0, prompts_1.select)({
22
22
  message: 'Select a provider:',
23
- choices: providers.map(provider => ({ name: provider, value: provider })),
23
+ choices: providers.map((provider) => ({ name: provider, value: provider })),
24
24
  });
25
- const providerModels = models.filter(model => model.startsWith(selectedProvider));
26
- const modelCategories = [...new Set(providerModels.map(model => model.split(':')[1]))];
25
+ const providerModels = models.filter((model) => model.startsWith(selectedProvider));
26
+ const modelCategories = [...new Set(providerModels.map((model) => model.split(':')[1]))];
27
27
  const selectedCategory = await (0, prompts_1.select)({
28
28
  message: 'Select a category:',
29
- choices: modelCategories.map(category => ({ name: category, value: category })),
29
+ choices: modelCategories.map((category) => ({ name: category, value: category })),
30
30
  });
31
- const specificModels = providerModels.filter(model => model.split(':')[1] === selectedCategory);
31
+ const specificModels = providerModels.filter((model) => model.split(':')[1] === selectedCategory);
32
32
  const modelIndex = await (0, prompts_1.select)({
33
- message: 'Select an AI model you\'d like to use:',
33
+ message: "Select an AI model you'd like to use:",
34
34
  choices: specificModels.map((model, index) => ({ name: model, value: index })),
35
35
  pageSize: specificModels.length,
36
36
  });
@@ -12,8 +12,12 @@ const command_utils_1 = require("../../utils/command_utils");
12
12
  const config_utils_1 = require("../../utils/config_utils");
13
13
  const generateContextPrompt = (files, contextFiles) => {
14
14
  const specialFileContents = (0, command_utils_1.collectSpecialFileContents)();
15
- const specialFiles = Object.keys(specialFileContents).map((file) => `### Contents of ${file}\n\n${JSON.stringify(specialFileContents[file], null, 2)}\n\n`).join('\n');
16
- const contextFileContents = Object.keys(contextFiles).map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`).join('\n');
15
+ const specialFiles = Object.keys(specialFileContents)
16
+ .map((file) => `### Contents of ${file}\n\n${JSON.stringify(specialFileContents[file], null, 2)}\n\n`)
17
+ .join('\n');
18
+ const contextFileContents = Object.keys(contextFiles)
19
+ .map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`)
20
+ .join('\n');
17
21
  return `### Description of App
18
22
 
19
23
  ${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
@@ -35,7 +39,10 @@ const context = async (options) => {
35
39
  if (files) {
36
40
  for (const file of files) {
37
41
  const filePath = path_1.default.resolve(file);
38
- if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
42
+ if (await fs_1.default.promises
43
+ .stat(filePath)
44
+ .then((stat) => stat.isFile())
45
+ .catch(() => false)) {
39
46
  const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
40
47
  contextFiles[file] = content;
41
48
  }
@@ -3,267 +3,9 @@ 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 = require("fs");
7
- const child_process_1 = require("child_process");
8
- const cli_highlight_1 = __importDefault(require("cli-highlight"));
9
- const prompts_1 = require("@inquirer/prompts");
10
- const git_utils_1 = require("../../utils/git_utils");
11
- const config_utils_1 = require("../../utils/config_utils");
12
- const string_utils_1 = require("../../utils/string_utils");
13
- const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
14
- const extractChangedFiles = (gitDiff) => {
15
- const fileRegex = /diff --git a\/(.+?) b\/(.+?)\n/g;
16
- const files = new Set();
17
- let match;
18
- while ((match = fileRegex.exec(gitDiff)) !== null) {
19
- files.add(match[1]);
20
- }
21
- return Array.from(files);
22
- };
23
- const generatePrompt = (files, srcFiles, testFiles, gitDiff) => {
24
- const filesList = files.length > 0 ? files.join('\n') : 'No files provided';
25
- return `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.
26
- Also, list any test files that might be relevant to these files.
27
-
28
- ### List of Code Files
29
-
30
- ${filesList}
31
-
32
- ### List of All Source Files
33
-
34
- ${srcFiles}
35
-
36
- ### List of all test files
37
-
38
- ${testFiles}
39
-
40
- ${gitDiff ? '### Git Diff Content' : ''}
41
-
42
- ${gitDiff || ''}
43
-
44
- ## Example Output
45
-
46
- Here are the files that are relevant:
47
-
48
- \`\`\`plaintext
49
- ## files
50
-
51
- app/models/foo.rb
52
- app/views/baz/bar.html.erb
53
-
54
- ## relevant test files
55
-
56
- test/models/foo_test.rb
57
- test/controllers/baz_controller_test.rb
58
- \`\`\`
59
-
60
- ### Prompt
61
-
62
- 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.
63
- Also, list any test files that might be relevant to these files.`;
64
- };
65
- const handleUserAction = async (analysis, autoSave = false) => {
66
- const blocks = (0, string_utils_1.extractCurrentNewBlocks)(analysis);
67
- if (autoSave) {
68
- await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(blocks);
69
- console.log('All unit test files have been saved.');
70
- return;
71
- }
72
- const files = Array.from(new Set(blocks.map(block => block.filePath)));
73
- const action = await (0, prompts_1.select)({
74
- message: 'What would you like to do?',
75
- choices: [
76
- { name: 'Save a Unit Test File', value: 'save' },
77
- { name: 'Save All Unit Test Files', value: 'saveAll' },
78
- { name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
79
- { name: 'Done', value: 'done' },
80
- ],
81
- });
82
- switch (action) {
83
- case 'save':
84
- let saveAnother = true;
85
- while (saveAnother) {
86
- const fileToSave = await (0, prompts_1.select)({
87
- message: 'Which unit test file do you want to save?',
88
- choices: files.map(file => ({ name: file, value: file })),
89
- });
90
- await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(blocks.filter(block => block.filePath === fileToSave));
91
- saveAnother = await (0, prompts_1.confirm)({
92
- message: 'Do you want to save another file?',
93
- default: false,
94
- });
95
- }
96
- break;
97
- case 'saveAll':
98
- await (0, string_utils_1.applyAndSaveCurrentNewBlocks)(blocks);
99
- console.log('All unit test files have been saved.');
100
- break;
101
- case 'copyAll':
102
- process.nextTick(() => {
103
- require('copy-paste').copy(analysis, () => {
104
- console.log('Entire response copied to clipboard');
105
- });
106
- });
107
- break;
108
- case 'done':
109
- break;
110
- }
111
- };
6
+ const UnitTestManager_1 = __importDefault(require("../../managers/UnitTestManager"));
112
7
  const unitTests = async (options) => {
113
- const { files, save } = options;
114
- const gpt = new cloving_gpt_1.default(options);
115
- const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, true);
116
- const testingDirectory = (0, config_utils_1.getTestingDirectory)();
117
- if (allSrcFiles.length === 0 || !testingDirectory) {
118
- console.error('Could not find any source files to generate unit tests. Please run: cloving init. Then try again.');
119
- process.exit(1);
120
- }
121
- const testFiles = (0, child_process_1.execFileSync)('find', [testingDirectory, '-type', 'f']).toString().trim();
122
- let contextFiles;
123
- if (files) {
124
- console.log('Generating unit tests for the provided files...');
125
- console.log('Gathering a list of files related to the changes...');
126
- const prompt = generatePrompt(files, allSrcFiles.join('\n'), testFiles);
127
- contextFiles = await gpt.generateText({ prompt });
128
- }
129
- else {
130
- console.log('Generating unit tests for the git diff between this branch and the default branch...');
131
- console.log('Gathering a list of files related to the changes...');
132
- const gitDiff = await (0, git_utils_1.getGitDiff)();
133
- const changedFiles = extractChangedFiles(gitDiff);
134
- const prompt = generatePrompt(changedFiles, allSrcFiles.join('\n'), testFiles, gitDiff);
135
- contextFiles = await gpt.generateText({ prompt });
136
- }
137
- contextFiles = (0, string_utils_1.extractMarkdown)(contextFiles);
138
- console.log(contextFiles);
139
- console.log('\nBuilding the tests for these files...');
140
- // Initialize variables
141
- const lines = [];
142
- const context = [];
143
- // Read each line of the context files
144
- contextFiles.split('\n').forEach((line) => {
145
- if (line.trim()) {
146
- lines.push(line.trim());
147
- }
148
- });
149
- for (const codeFile of lines) {
150
- if (await fs_1.promises.stat(codeFile).then(stat => stat.isFile()).catch(() => false)) {
151
- const fileContents = await fs_1.promises.readFile(codeFile, 'utf-8');
152
- context.push(`### Contents of **${codeFile}\n\n${fileContents.split('\n').map(line => ` ${line}`).join('\n')}**\n\n`);
153
- }
154
- }
155
- // Generate the message for unit test creation
156
- let analysis = '';
157
- if ((files || []).length > 0) {
158
- const message = `Create unit tests for the List of Files. Always show filenames for the generated code.
159
-
160
- ## List of Files
161
- ${(files || []).join('\n')}
162
-
163
- ## Example Response Structure
164
-
165
- Here are the unit tests for the provided list of files:
166
-
167
- 1. **tests/utils/model_utils.test.ts**
168
-
169
- \`\`\`typescript
170
- import { getModel } from '../../src/utils/model_utils'
171
-
172
- describe('modelUtils', () => {
173
- const originalEnv = process.env
174
-
175
- beforeEach(() => {
176
- jest.resetModules()
177
- process.env = { ...originalEnv }
178
- })
179
-
180
- afterAll(() => {
181
- process.env = originalEnv
182
- })
183
-
184
- test('getModel should return default model when CLOVING_MODEL is not set', () => {
185
- delete process.env.CLOVING_MODEL
186
- const result = getModel()
187
- expect(result).toBe('claude:claude-3-5-sonnet-20240620')
188
- })
189
-
190
- test('getModel should return CLOVING_MODEL when it is set', () => {
191
- process.env.CLOVING_MODEL = 'openai:gpt-4'
192
- const result = getModel()
193
- expect(result).toBe('openai:gpt-4')
194
- })
195
- })
196
- \`\`\`
197
-
198
- ## Context
199
-
200
- ${context.join('\n\n')}
201
-
202
- ## Prompt
203
-
204
- Create unit tests for the List of Files. Always show filenames for the generated code.`;
205
- // Get the model and analysis using ClovingGPT
206
- analysis = await gpt.generateText({ prompt: message });
207
- }
208
- else {
209
- const message = `Create unit tests for the Code Diff. Always show filenames for the generated code.
210
-
211
- ## Code Diff
212
-
213
- ${await (0, git_utils_1.getGitDiff)()}
214
-
215
- ## Context
216
-
217
- ${context.join('\n\n')}
218
-
219
- ## Example Response Structure
220
-
221
- Here are the unit tests for the code diff:
222
-
223
- 1. **tests/utils/model_utils.test.ts**
224
-
225
- \`\`\`typescript
226
- import { getModel } from '../../src/utils/model_utils'
227
-
228
- describe('modelUtils', () => {
229
- const originalEnv = process.env
230
-
231
- beforeEach(() => {
232
- jest.resetModules()
233
- process.env = { ...originalEnv }
234
- })
235
-
236
- afterAll(() => {
237
- process.env = originalEnv
238
- })
239
-
240
- test('getModel should return default model when CLOVING_MODEL is not set', () => {
241
- delete process.env.CLOVING_MODEL
242
- const result = getModel()
243
- expect(result).toBe('claude:claude-3-5-sonnet-20240620')
244
- })
245
-
246
- test('getModel should return CLOVING_MODEL when it is set', () => {
247
- process.env.CLOVING_MODEL = 'openai:gpt-4'
248
- const result = getModel()
249
- expect(result).toBe('openai:gpt-4')
250
- })
251
- })
252
- \`\`\`
253
-
254
- ## Prompt
255
-
256
- Create unit tests for the Code Diff. Always show filenames for the generated code.`;
257
- // Get the model and analysis using ClovingGPT
258
- analysis = await gpt.generateText({ prompt: message });
259
- }
260
- if (analysis) {
261
- console.log((0, cli_highlight_1.default)(analysis));
262
- }
263
- else {
264
- console.log('No unit tests generated.');
265
- }
266
- // Add the new user action handling
267
- await handleUserAction(analysis, save);
8
+ const unitTestManager = new UnitTestManager_1.default(options);
9
+ await unitTestManager.generateUnitTests();
268
10
  };
269
11
  exports.default = unitTests;