cloving 0.3.12 → 0.3.13

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.
@@ -32,7 +32,7 @@ class ClovingGPT {
32
32
  const { provider, model, config: modelConfig } = primaryModel;
33
33
  this.apiKey = modelConfig.apiKey;
34
34
  this.temperature = options.temperature || modelConfig.temperature || 0.2;
35
- this.silent = options.silent || modelConfig.silent;
35
+ this.silent = options.silent || modelConfig.silent || false;
36
36
  this.stream = options.stream || false;
37
37
  this.timeout = options.timeout || 60000; // Default timeout of 60 seconds
38
38
  switch (provider) {
@@ -89,14 +89,14 @@ const config = async () => {
89
89
  */
90
90
  const review = await (0, prompts_1.confirm)({
91
91
  message: `Do you want to review all prompts before they are sent to ${selectedModel}?`,
92
- default: true,
92
+ default: false,
93
93
  });
94
94
  /**
95
95
  * Prompts the user to decide if they trust the selected model with sensitive information.
96
96
  */
97
97
  const trust = await (0, prompts_1.confirm)({
98
98
  message: `Do you trust ${selectedModel} with sensitive information?`,
99
- default: false,
99
+ default: true,
100
100
  });
101
101
  /**
102
102
  * Prompts the user to enter the temperature setting for the selected model.
@@ -24,14 +24,19 @@ class BlockManager extends events_1.EventEmitter {
24
24
  this.buffer = '';
25
25
  }
26
26
  // if content has multiple \n``` in the string, then we need to split it up
27
- if (content.includes('\n```')) {
28
- while (content.includes('\n```')) {
29
- const index = content.indexOf('\n```');
30
- this.processCodeBlocks(content.slice(0, index + 4));
31
- content = content.slice(index + 4);
27
+ const codeBlockRegex = /\n```[\s\S]*?\n```/g;
28
+ const codeBlocks = content.match(codeBlockRegex);
29
+ if (codeBlocks) {
30
+ let lastIndex = 0;
31
+ for (const block of codeBlocks) {
32
+ const index = content.indexOf(block, lastIndex);
33
+ this.processCodeBlocks(content.slice(lastIndex, index));
34
+ this.processCodeBlocks(block);
35
+ lastIndex = index + block.length;
32
36
  }
37
+ this.processCodeBlocks(content.slice(lastIndex));
33
38
  }
34
- if (content !== '') {
39
+ else {
35
40
  this.processCodeBlocks(content);
36
41
  }
37
42
  }
@@ -95,7 +100,15 @@ class BlockManager extends events_1.EventEmitter {
95
100
  */
96
101
  parseCodeBuffer() {
97
102
  const results = (0, string_utils_1.extractCurrentNewBlocks)(this.codeBuffer);
98
- return results[0];
103
+ if (results.length === 0) {
104
+ return null;
105
+ }
106
+ const block = results[0];
107
+ if (!block.currentContent || !block.newContent) {
108
+ console.warn('Incomplete code block detected. Skipping.');
109
+ return null;
110
+ }
111
+ return block;
99
112
  }
100
113
  clearBuffer() {
101
114
  this.buffer = '';
@@ -11,6 +11,7 @@ const child_process_1 = require("child_process");
11
11
  const CommitManager_1 = __importDefault(require("./CommitManager"));
12
12
  const ReviewManager_1 = __importDefault(require("./ReviewManager"));
13
13
  const string_utils_1 = require("../utils/string_utils");
14
+ const config_utils_1 = require("../utils/config_utils");
14
15
  const prompt_utils_1 = require("../utils/prompt_utils");
15
16
  const prompts_1 = require("../utils/prompts");
16
17
  const StreamManager_1 = __importDefault(require("./StreamManager"));
@@ -39,7 +40,7 @@ class ChatManager extends StreamManager_1.default {
39
40
  this.multilineInput = '';
40
41
  this.isMultilineMode = false;
41
42
  this.isSilent = false;
42
- this.isSilent = options.silent || false;
43
+ this.isSilent = options.silent || (0, config_utils_1.getConfig)(options).globalSilent || false;
43
44
  options.stream = true;
44
45
  options.silent = true;
45
46
  this.rl = readline_1.default.createInterface({
@@ -98,6 +99,15 @@ class ChatManager extends StreamManager_1.default {
98
99
  return;
99
100
  }
100
101
  const trimmedLine = line.trim();
102
+ if (this.isPastedCode(trimmedLine)) {
103
+ console.log('Detected pasted code. Processing as multiline input...');
104
+ this.multilineInput = trimmedLine;
105
+ this.isMultilineMode = true;
106
+ await this.handleCommand(this.multilineInput);
107
+ this.multilineInput = '';
108
+ this.isMultilineMode = false;
109
+ return;
110
+ }
101
111
  if (this.handleMultilineInput(trimmedLine)) {
102
112
  return;
103
113
  }
@@ -133,6 +143,10 @@ class ChatManager extends StreamManager_1.default {
133
143
  }
134
144
  return false;
135
145
  }
146
+ isPastedCode(line) {
147
+ // Check if the line contains multiple lines (indicating a paste)
148
+ return line.includes('\n') || line.length > 100;
149
+ }
136
150
  handleExitCommand(command) {
137
151
  if (command.toLowerCase() === 'exit') {
138
152
  this.rl.close();
@@ -178,6 +192,11 @@ class ChatManager extends StreamManager_1.default {
178
192
  if (matchingCommand) {
179
193
  command = matchingCommand.split(' ')[0]; // Use only the command part without arguments
180
194
  }
195
+ else {
196
+ console.log(`No matching command found for '${command}'. Processing as user input.`);
197
+ await this.processUserInput(command);
198
+ return;
199
+ }
181
200
  }
182
201
  switch (command) {
183
202
  case 'help':
@@ -438,7 +457,7 @@ class ChatManager extends StreamManager_1.default {
438
457
  try {
439
458
  await this.refreshContext();
440
459
  this.addUserPrompt(this.generatePrompt(input));
441
- if (!this.isSilent) {
460
+ if (!this.gpt.silent) {
442
461
  const fullPrompt = this.chatHistory
443
462
  .map((m) => {
444
463
  const content = m.content
@@ -461,25 +480,11 @@ class ChatManager extends StreamManager_1.default {
461
480
  // Review the prompt using the REPL
462
481
  const tokenCount = Math.ceil(fullPrompt.length / 4).toLocaleString();
463
482
  console.log(`${colors_1.default.yellow.bold('INFORMATION')} The generated prompt is approximately ${tokenCount} tokens long. Would you like to review the prompt before sending it? ${colors_1.default.gray('(Y/n)')}`);
464
- const reviewPrompt = await new Promise((resolve) => {
465
- this.rl.question(colors_1.default.green.bold('cloving> '), (answer) => {
466
- resolve(answer.trim().toLowerCase());
467
- });
468
- });
483
+ const reviewPrompt = await this.askQuestion('cloving> ');
469
484
  if (reviewPrompt.startsWith('y') || reviewPrompt === '') {
470
- console.log(colors_1.default.gray.bold('\n---------------------- PROMPT START ----------------------\n'));
471
- const promptParts = fullPrompt.split('\n```');
472
- const highlightedPrompt = promptParts
473
- .map((part, index) => index % 2 === 1 ? (0, cli_highlight_1.default)(part, { language: 'typescript' }) : part)
474
- .join('\n```');
475
- console.log(highlightedPrompt);
476
- console.log(colors_1.default.gray.bold('\n---------------------- PROMPT END ----------------------\n'));
485
+ this.reviewPrompt(fullPrompt);
477
486
  console.log(`Do you want to proceed with this prompt? ${colors_1.default.gray('(Y/n)')}`);
478
- const confirmPrompt = await new Promise((resolve) => {
479
- this.rl.question(colors_1.default.green.bold('cloving> '), (answer) => {
480
- resolve(answer.trim().toLowerCase());
481
- });
482
- });
487
+ const confirmPrompt = await this.askQuestion('cloving> ');
483
488
  if (!confirmPrompt.startsWith('y') && confirmPrompt !== '') {
484
489
  console.log('Operation cancelled by user.');
485
490
  this.isProcessing = false;
@@ -498,9 +503,26 @@ class ChatManager extends StreamManager_1.default {
498
503
  this.handleError(err);
499
504
  }
500
505
  }
506
+ askQuestion(prompt) {
507
+ return new Promise((resolve) => {
508
+ this.rl.question(colors_1.default.green.bold(prompt), (answer) => {
509
+ resolve(answer.trim().toLowerCase());
510
+ });
511
+ });
512
+ }
513
+ reviewPrompt(fullPrompt) {
514
+ console.log(colors_1.default.gray.bold('\n---------------------- PROMPT START ----------------------\n'));
515
+ const promptParts = fullPrompt.split('\n```');
516
+ const highlightedPrompt = promptParts
517
+ .map((part, index) => (index % 2 === 1 ? (0, cli_highlight_1.default)(part, { language: 'typescript' }) : part))
518
+ .join('\n```');
519
+ console.log(highlightedPrompt);
520
+ console.log(colors_1.default.gray.bold('\n---------------------- PROMPT END ----------------------\n'));
521
+ }
501
522
  async finalizeResponse() {
502
523
  this.addAssistantResponse(this.responseString);
503
524
  this.isProcessing = false;
525
+ process.stdout.write('\b'.repeat(100));
504
526
  if (this.responseString !== '') {
505
527
  const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(this.responseString);
506
528
  const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
@@ -162,22 +162,24 @@ Please briefly explain how the code works in this.`;
162
162
  const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(this.responseString);
163
163
  const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
164
164
  if (!canApply) {
165
- if (this.retryCount < this.maxRetries) {
166
- console.log(`\n\n${summary}\n\n${colors_1.default.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied. Retrying (Attempt ${this.retryCount + 1}/${this.maxRetries})...\n`);
167
- this.retryCount++;
168
- this.generateCode(`${prompts_2.CODEGEN_COULDNT_APPLY}\n\n${summary}`);
169
- return;
170
- }
171
- else {
172
- console.log(`\n\n${colors_1.default.red.bold('ERROR')} Failed to generate a diff that could be cleanly applied after ${this.maxRetries} attempts. Please review the changes manually and try again.\n`);
173
- this.retryCount = 0; // Reset the retry count for future attempts
174
- }
165
+ await this.handleUnapplicableBlocks(summary);
175
166
  }
176
167
  else {
177
168
  this.retryCount = 0; // Reset the retry count on successful application
178
169
  }
179
170
  }
180
- this.handleUserAction(this.responseString);
171
+ await this.handleUserAction(this.responseString);
172
+ }
173
+ async handleUnapplicableBlocks(summary) {
174
+ if (this.retryCount < this.maxRetries) {
175
+ console.log(`\n\n${summary}\n\n${colors_1.default.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied. Retrying (Attempt ${this.retryCount + 1}/${this.maxRetries})...\n`);
176
+ this.retryCount++;
177
+ await this.generateCode(`${prompts_2.CODEGEN_COULDNT_APPLY}\n\n${summary}`);
178
+ }
179
+ else {
180
+ console.log(`\n\n${colors_1.default.red.bold('ERROR')} Failed to generate a diff that could be cleanly applied after ${this.maxRetries} attempts. Please review the changes manually and try again.\n`);
181
+ this.retryCount = 0; // Reset the retry count for future attempts
182
+ }
181
183
  }
182
184
  }
183
185
  exports.default = CodeManager;
@@ -81,6 +81,8 @@ class DocsManager extends StreamManager_1.default {
81
81
  }
82
82
  }
83
83
  }
84
+ // Reset responseString after processing
85
+ this.responseString = '';
84
86
  }
85
87
  }
86
88
  exports.default = DocsManager;
@@ -182,15 +182,28 @@ ${JSON.stringify(projectDetails, null, 2)}`;
182
182
  }
183
183
  const clovingJson = JSON.parse(cleanAiChatResponse);
184
184
  const directoriesToCheck = [
185
- ...clovingJson.languages.map((language) => language.directory),
186
- ...clovingJson.frameworks.map((framework) => framework.directory),
187
- ...clovingJson.testingFrameworks.map((testingFramework) => testingFramework.directory),
185
+ ...clovingJson.languages.map((language) => ({
186
+ type: 'Language',
187
+ name: language.name,
188
+ directory: language.directory,
189
+ })),
190
+ ...clovingJson.frameworks.map((framework) => ({
191
+ type: 'Framework',
192
+ name: framework.name,
193
+ directory: framework.directory,
194
+ })),
195
+ ...clovingJson.testingFrameworks.map((testingFramework) => ({
196
+ type: 'Testing Framework',
197
+ name: testingFramework.name,
198
+ directory: testingFramework.directory,
199
+ })),
188
200
  ];
189
- const missingDirectories = directoriesToCheck.filter((directory) => !fs_1.default.existsSync(directory));
201
+ const missingDirectories = directoriesToCheck.filter((item) => !fs_1.default.existsSync(item.directory));
190
202
  if (missingDirectories.length > 0) {
203
+ const missingDirMessages = missingDirectories.map((item) => `${item.type} ${colors_1.default.bold(item.name)}: ${colors_1.default.red.bold(item.directory)}`);
191
204
  return [
192
205
  false,
193
- `${colors_1.default.red.bold('ERROR:')} The following directories were not found: ${colors_1.default.red.bold(missingDirectories.join(', '))}`,
206
+ `${colors_1.default.red.bold('ERROR:')} The following directories were not found:\n${missingDirMessages.join('\n')}`,
194
207
  ];
195
208
  }
196
209
  return [true, ''];
@@ -73,7 +73,17 @@ class ProxyManager {
73
73
  async handleChatCompletions(req, res) {
74
74
  try {
75
75
  const { messages, stream } = req.body;
76
- const content = messages.map((message) => message.content).join('\n\n');
76
+ if (!Array.isArray(messages)) {
77
+ return res.status(400).json({ error: 'Invalid request body: messages must be an array' });
78
+ }
79
+ const content = messages
80
+ .map((message) => {
81
+ if (typeof message !== 'object' || typeof message.content !== 'string') {
82
+ throw new Error('Invalid message format');
83
+ }
84
+ return message.content;
85
+ })
86
+ .join('\n\n');
77
87
  const contextFileContents = Object.keys(this.contextFiles)
78
88
  .map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`)
79
89
  .join('\n');
@@ -99,7 +109,14 @@ ${content}`;
99
109
  }
100
110
  catch (error) {
101
111
  console.error('Error processing request:', error);
102
- res.status(500).json({ error: 'Internal server error' });
112
+ if (error instanceof Error && error.message === 'Invalid message format') {
113
+ res.status(400).json({
114
+ error: 'Invalid request body: each message must have a content property of type string',
115
+ });
116
+ }
117
+ else {
118
+ res.status(500).json({ error: 'Internal server error' });
119
+ }
103
120
  }
104
121
  }
105
122
  async handleStreamResponse(prompt, res) {
@@ -142,17 +142,23 @@ class StreamManager extends events_1.EventEmitter {
142
142
  * @param {CurrentNewBlock} currentNewBlock - The current and new content of the code block.
143
143
  */
144
144
  displayCurrentNewBlock(currentNewBlock) {
145
+ const language = currentNewBlock.language.split('\n')[0];
146
+ process.stdout.write(colors_1.default.gray.bold(`\`\`\`${language} \n`));
147
+ process.stdout.write(colors_1.default.gray.bold(`<<<<<<< CURRENT ${currentNewBlock.filePath}\n`));
145
148
  try {
146
- process.stdout.write(colors_1.default.gray.bold(`\`\`\`${currentNewBlock.language} \n`));
147
- process.stdout.write(colors_1.default.gray.bold(`<<<<<<< CURRENT ${currentNewBlock.filePath}\n`));
148
- process.stdout.write((0, cli_highlight_1.default)(currentNewBlock.currentContent, { language: currentNewBlock.language }));
149
- process.stdout.write(colors_1.default.gray.bold('\n=======\n'));
150
- process.stdout.write((0, cli_highlight_1.default)(currentNewBlock.newContent, { language: currentNewBlock.language }));
151
- process.stdout.write(colors_1.default.gray.bold('\n>>>>>>> NEW\n```'));
149
+ process.stdout.write((0, cli_highlight_1.default)(currentNewBlock.currentContent, { language }));
152
150
  }
153
151
  catch (error) {
154
- console.error(colors_1.default.red('Error displaying the code block:'), error);
152
+ process.stdout.write(currentNewBlock.currentContent);
155
153
  }
154
+ process.stdout.write(colors_1.default.gray.bold('\n=======\n'));
155
+ try {
156
+ process.stdout.write((0, cli_highlight_1.default)(currentNewBlock.newContent, { language }));
157
+ }
158
+ catch (error) {
159
+ process.stdout.write(currentNewBlock.newContent);
160
+ }
161
+ process.stdout.write(colors_1.default.gray.bold('\n>>>>>>> NEW\n```'));
156
162
  }
157
163
  /**
158
164
  * Displays a raw code block.
@@ -235,27 +241,38 @@ class StreamManager extends events_1.EventEmitter {
235
241
  * @returns {Promise<void>}
236
242
  */
237
243
  async loadContextFiles() {
238
- const config = (0, config_utils_1.getClovingConfig)();
239
- const primaryLanguage = config.languages.find((lang) => lang.primary);
240
- const defaultDirectory = primaryLanguage ? primaryLanguage.directory : '.';
241
- const testingDirectories = config.testingFrameworks?.map((framework) => framework.directory) || [];
242
- const testingDirectory = testingDirectories[0];
243
- let files = this.options.files || [defaultDirectory, testingDirectory].filter(Boolean);
244
- if (files.length > 0) {
245
- console.log(`\nBuilding AI prompt context...\n`);
244
+ try {
245
+ const config = (0, config_utils_1.getClovingConfig)();
246
+ const primaryLanguage = config.languages.find((lang) => lang.primary);
247
+ const defaultDirectory = primaryLanguage ? primaryLanguage.directory : '.';
248
+ const testingDirectories = config.testingFrameworks?.map((framework) => framework.directory) || [];
249
+ const testingDirectory = testingDirectories[0];
250
+ let files = this.options.files || [defaultDirectory, testingDirectory].filter(Boolean);
251
+ if (files.length > 0) {
252
+ console.log(`\nBuilding AI prompt context...\n`);
253
+ }
254
+ for (const file of files) {
255
+ // Skip if the file is not a string
256
+ if (!file)
257
+ continue;
258
+ try {
259
+ const previousCount = Object.keys(this.contextFiles).length;
260
+ this.contextFiles = await (0, prompt_utils_2.addFileOrDirectoryToContext)(file, this.contextFiles, this.options);
261
+ const newCount = Object.keys(this.contextFiles).length;
262
+ const addedCount = newCount - previousCount;
263
+ const totalTokens = this.calculateTotalTokens();
264
+ console.log(colors_1.default.cyan(`📁 Loaded context from: ${colors_1.default.bold(file)}`));
265
+ console.log(colors_1.default.green(` ✅ Added ${addedCount} file(s) to context`));
266
+ console.log(colors_1.default.yellow(` 📊 Total tokens in context: ${totalTokens.toLocaleString()}\n`));
267
+ }
268
+ catch (fileError) {
269
+ console.error(colors_1.default.red(`Error loading context from ${file}:`), fileError);
270
+ }
271
+ }
246
272
  }
247
- for (const file of files) {
248
- // Skip if the file is not a string
249
- if (!file)
250
- continue;
251
- const previousCount = Object.keys(this.contextFiles).length;
252
- this.contextFiles = await (0, prompt_utils_2.addFileOrDirectoryToContext)(file, this.contextFiles, this.options);
253
- const newCount = Object.keys(this.contextFiles).length;
254
- const addedCount = newCount - previousCount;
255
- const totalTokens = this.calculateTotalTokens();
256
- console.log(colors_1.default.cyan(`📁 Loaded context from: ${colors_1.default.bold(file)}`));
257
- console.log(colors_1.default.green(` ✅ Added ${addedCount} file(s) to context`));
258
- console.log(colors_1.default.yellow(` 📊 Total tokens in context: ${totalTokens.toLocaleString()}\n`));
273
+ catch (error) {
274
+ console.error(colors_1.default.red('Error loading context files:'), error);
275
+ throw new Error('Failed to load context files');
259
276
  }
260
277
  }
261
278
  calculateTotalTokens() {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "packageManager": "yarn@1.22.22",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,18 +19,19 @@
19
19
 
20
20
  ## *CURRENT/NEW* Block Format
21
21
 
22
- 1. **Start a block with a one line description of change**
23
- 1. **Then provide three backticks and the language name.**
22
+ 1. **Start a block with a one line description of change followed by three backticks and the language name like this:**
23
+ About to add documentation to the `UserService` class.
24
+
24
25
  ```typescript
25
- 2. **Next line: seven <, CURRENT, and the file path.**
26
+ 2. **Next line: seven <, CURRENT, and the file path like this:**
26
27
  <<<<<<< CURRENT path/to/file.ts
27
- 3. **Include the exact existing code to be changed, this section should always have some code characters in it, not just spaces or tabs. The only exception is when creating a brand new file.**
28
- 4. **Divide with seven =.**
28
+ 3. **Include the exact existing code to be changed, this section should always have some code characters in it, not just spaces or tabs.**
29
+ 4. **Divide with seven = like this:**
29
30
  =======
30
31
  5. **Add the new code.**
31
- 6. **End with seven > and NEW.**
32
+ 6. **End with seven > and NEW like this:**
32
33
  >>>>>>> NEW
33
- 7. **Close the block with three backticks.**
34
+ 7. **Close the block with three backticks like this:**
34
35
  ```
35
36
 
36
37
  ## Don't Invent Code That Isn't Provided in Context
@@ -474,10 +474,18 @@ const checkBlocksApplicability = async (blocks) => {
474
474
  for (const [index, block] of blocks.entries()) {
475
475
  const filePath = path_1.default.resolve(block.filePath);
476
476
  let fileContent = await readFileContent(filePath);
477
- if (fileContent === '') {
477
+ if (block.newContent.trim() === '') {
478
+ allApplicable = false;
479
+ summary.push(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold('cannot be applied: New content is empty')}`);
480
+ }
481
+ else if (fileContent === '') {
478
482
  summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.green.bold('created')}`);
479
483
  }
480
- else if (block.currentContent.trim() !== '') {
484
+ else if (block.currentContent.trim() === '') {
485
+ allApplicable = false;
486
+ summary.push(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold(`cannot be applied: File '${block.filePath}' already exists`)}`);
487
+ }
488
+ else {
481
489
  const { matchingLines } = await analyzeBlockContent(block, fileContent);
482
490
  if (matchingLines.length === 0) {
483
491
  allApplicable = false;
@@ -491,9 +499,6 @@ const checkBlocksApplicability = async (blocks) => {
491
499
  summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.yellow.bold('updated')}`);
492
500
  }
493
501
  }
494
- else {
495
- summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.yellow.bold('updated')}`);
496
- }
497
502
  }
498
503
  return [allApplicable, summary.join('\n')];
499
504
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "packageManager": "yarn@1.22.22",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",