cloving 0.3.5 → 0.3.6
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 +8 -2
- package/dist/cloving_gpt/index.js +1 -1
- package/dist/commands/config.js +2 -2
- package/dist/commands/generate/context.js +3 -3
- package/dist/commands/init.js +3 -218
- package/dist/commands/tokens.js +2 -2
- package/dist/index.js +2 -2
- package/dist/managers/BlockManager.js +121 -0
- package/dist/managers/ChatManager.js +136 -20
- package/dist/managers/ChunkManager.js +12 -0
- package/dist/managers/CodeManager.js +5 -5
- package/dist/managers/InitManager.js +216 -0
- package/dist/managers/ProxyManager.js +11 -3
- package/dist/managers/ReviewManager.js +14 -2
- package/dist/managers/ShellManager.js +2 -2
- package/dist/managers/UnitTestManager.js +3 -3
- package/dist/utils/prompt_utils.js +515 -0
- package/package.json +1 -1
|
@@ -9,13 +9,16 @@ const child_process_1 = require("child_process");
|
|
|
9
9
|
const colors_1 = __importDefault(require("colors"));
|
|
10
10
|
const axios_1 = __importDefault(require("axios"));
|
|
11
11
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
12
14
|
const CommitManager_1 = __importDefault(require("./CommitManager"));
|
|
13
15
|
const ChunkManager_1 = __importDefault(require("./ChunkManager"));
|
|
16
|
+
const BlockManager_1 = __importDefault(require("./BlockManager"));
|
|
14
17
|
const ReviewManager_1 = __importDefault(require("./ReviewManager"));
|
|
15
18
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
16
19
|
const string_utils_1 = require("../utils/string_utils");
|
|
17
20
|
const config_utils_1 = require("../utils/config_utils");
|
|
18
|
-
const
|
|
21
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
19
22
|
const specialCommands = [
|
|
20
23
|
'save',
|
|
21
24
|
'commit',
|
|
@@ -56,6 +59,7 @@ class ChatManager {
|
|
|
56
59
|
historySize: 1000,
|
|
57
60
|
});
|
|
58
61
|
this.chunkManager = new ChunkManager_1.default();
|
|
62
|
+
this.blockManager = new BlockManager_1.default();
|
|
59
63
|
}
|
|
60
64
|
/**
|
|
61
65
|
* Initializes the ChatManager by loading context files, displaying a welcome message,
|
|
@@ -96,7 +100,7 @@ class ChatManager {
|
|
|
96
100
|
try {
|
|
97
101
|
const response = await axios_1.default.get('https://api.github.com/repos/cloving-ai/cloving-cli/releases/latest');
|
|
98
102
|
const latestVersion = response.data.tag_name;
|
|
99
|
-
const currentVersion = `v${(0,
|
|
103
|
+
const currentVersion = `v${(0, prompt_utils_1.getPackageVersion)()}`;
|
|
100
104
|
if (latestVersion !== currentVersion) {
|
|
101
105
|
console.log(colors_1.default.bgWhite.black(`\n 🚀 A new version of Cloving is available: ${latestVersion} `));
|
|
102
106
|
console.log(colors_1.default.bgWhite.black(` You are currently using version: ${currentVersion} `));
|
|
@@ -136,7 +140,7 @@ class ChatManager {
|
|
|
136
140
|
if (!file)
|
|
137
141
|
continue;
|
|
138
142
|
const previousCount = Object.keys(this.contextFiles).length;
|
|
139
|
-
this.contextFiles = await (0,
|
|
143
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(file, this.contextFiles, this.options);
|
|
140
144
|
const newCount = Object.keys(this.contextFiles).length;
|
|
141
145
|
const addedCount = newCount - previousCount;
|
|
142
146
|
const totalTokens = this.calculateTotalTokens();
|
|
@@ -299,7 +303,7 @@ class ChatManager {
|
|
|
299
303
|
}
|
|
300
304
|
else {
|
|
301
305
|
for (const filePath of foundFiles) {
|
|
302
|
-
this.contextFiles = await (0,
|
|
306
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(filePath, this.contextFiles, this.options);
|
|
303
307
|
const content = this.contextFiles[filePath];
|
|
304
308
|
const tokenEstimate = this.estimateTokens(content);
|
|
305
309
|
console.log(`\nAdded ${colors_1.default.bold(colors_1.default.green(filePath))} to this chat session's context (${colors_1.default.yellow(`~${tokenEstimate.toLocaleString()} tokens`)})`);
|
|
@@ -346,7 +350,7 @@ class ChatManager {
|
|
|
346
350
|
const filePath = command.slice(4).trim();
|
|
347
351
|
if (filePath) {
|
|
348
352
|
try {
|
|
349
|
-
this.contextFiles = await (0,
|
|
353
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(filePath, this.contextFiles, this.options);
|
|
350
354
|
console.log(`\nAdded ${colors_1.default.bold(colors_1.default.green(filePath))} to this chat session's context`);
|
|
351
355
|
this.refreshContext();
|
|
352
356
|
}
|
|
@@ -359,11 +363,23 @@ class ChatManager {
|
|
|
359
363
|
}
|
|
360
364
|
this.rl.prompt();
|
|
361
365
|
}
|
|
362
|
-
refreshContext() {
|
|
363
|
-
|
|
366
|
+
async refreshContext() {
|
|
367
|
+
await this.reloadContextFiles();
|
|
368
|
+
const updatedSystemPrompt = (0, prompt_utils_1.generateCodegenPrompt)(this.contextFiles);
|
|
364
369
|
this.chatHistory[0] = { role: 'user', content: updatedSystemPrompt };
|
|
365
370
|
this.chatHistory[1] = { role: 'assistant', content: 'What would you like to do?' };
|
|
366
371
|
}
|
|
372
|
+
async reloadContextFiles() {
|
|
373
|
+
for (const filePath in this.contextFiles) {
|
|
374
|
+
try {
|
|
375
|
+
const content = await fs_1.default.promises.readFile(path_1.default.resolve(filePath), 'utf8');
|
|
376
|
+
this.contextFiles[filePath] = content;
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
console.error(`Error reading file ${filePath}:`, error);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
367
383
|
isRemoveCommand(command) {
|
|
368
384
|
return command.startsWith('rm ');
|
|
369
385
|
}
|
|
@@ -505,7 +521,7 @@ class ChatManager {
|
|
|
505
521
|
this.isProcessing = true;
|
|
506
522
|
this.chunkManager = new ChunkManager_1.default();
|
|
507
523
|
try {
|
|
508
|
-
this.refreshContext();
|
|
524
|
+
await this.refreshContext();
|
|
509
525
|
this.prompt = this.generatePrompt(input);
|
|
510
526
|
this.addChatHistory(this.prompt);
|
|
511
527
|
if (!this.isSilent) {
|
|
@@ -572,25 +588,69 @@ class ChatManager {
|
|
|
572
588
|
addChatHistory(input) {
|
|
573
589
|
this.chatHistory.push({ role: 'user', content: input });
|
|
574
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* Handles the streaming response from the AI model.
|
|
593
|
+
* This method processes the incoming data chunks, converts them to readable output,
|
|
594
|
+
* and manages the accumulated content.
|
|
595
|
+
*
|
|
596
|
+
* @param responseStream - The stream of response data from the AI model.
|
|
597
|
+
* @param accumulatedContent - A string to accumulate the complete response content.
|
|
598
|
+
*/
|
|
575
599
|
handleResponseStream(responseStream, accumulatedContent) {
|
|
600
|
+
this.blockManager.on('content', (content) => {
|
|
601
|
+
process.stdout.write('\n------ beginning of blockManager content ------\n');
|
|
602
|
+
process.stdout.write(content);
|
|
603
|
+
process.stdout.write('\n------ end of blockManager content ------\n');
|
|
604
|
+
accumulatedContent += content;
|
|
605
|
+
});
|
|
606
|
+
this.blockManager.on('codeBlock', (codeBlock) => {
|
|
607
|
+
process.stdout.write(codeBlock.language);
|
|
608
|
+
process.stdout.write('\n');
|
|
609
|
+
process.stdout.write(codeBlock.currentStart);
|
|
610
|
+
process.stdout.write('\n');
|
|
611
|
+
process.stdout.write((0, cli_highlight_1.default)(codeBlock.currentCode, { language: codeBlock.language }));
|
|
612
|
+
process.stdout.write('\n=======\n');
|
|
613
|
+
process.stdout.write((0, cli_highlight_1.default)(codeBlock.newCode, { language: codeBlock.language }));
|
|
614
|
+
process.stdout.write('\n');
|
|
615
|
+
process.stdout.write(codeBlock.newEnd);
|
|
616
|
+
accumulatedContent += codeBlock.raw;
|
|
617
|
+
});
|
|
618
|
+
// Handle content chunks from the ChunkManager
|
|
576
619
|
this.chunkManager.on('content', (buffer) => {
|
|
577
620
|
let convertedStream = this.gpt.convertStream(buffer);
|
|
578
621
|
while (convertedStream !== null) {
|
|
579
622
|
const { output, lastChar } = convertedStream;
|
|
623
|
+
process.stdout.write('\n------ beginning of chunk ------\n');
|
|
580
624
|
process.stdout.write(output);
|
|
581
|
-
|
|
625
|
+
process.stdout.write(`lastChar: ${lastChar}\n`);
|
|
626
|
+
process.stdout.write('\n------ end of chunk ------\n');
|
|
627
|
+
// buffer the output to the BlockManager
|
|
628
|
+
this.blockManager.addContent(output);
|
|
629
|
+
// clear the buffer from the ChunkManager
|
|
582
630
|
this.chunkManager.clearBuffer(lastChar);
|
|
631
|
+
// Try to convert the rest of the buffer to a string
|
|
583
632
|
buffer = buffer.slice(lastChar);
|
|
584
633
|
convertedStream = this.gpt.convertStream(buffer);
|
|
585
634
|
}
|
|
586
635
|
});
|
|
636
|
+
// Handle incoming data chunks from the response stream
|
|
587
637
|
responseStream.data.on('data', (chunk) => {
|
|
638
|
+
// Convert the chunk to a string and add it to the ChunkManager
|
|
639
|
+
if (!chunk)
|
|
640
|
+
return;
|
|
641
|
+
// a chunk is a JSON object with different keys depending on the AI service provider
|
|
588
642
|
const chunkString = chunk.toString();
|
|
643
|
+
process.stdout.write('\n------ beginning of chunk string ------\n');
|
|
644
|
+
process.stdout.write(chunkString);
|
|
645
|
+
process.stdout.write('\n------ end of chunk string ------\n');
|
|
589
646
|
this.chunkManager.addChunk(chunkString);
|
|
590
647
|
});
|
|
648
|
+
// Handle the end of the response stream
|
|
591
649
|
responseStream.data.on('end', () => {
|
|
650
|
+
// Finalize the response when the stream ends
|
|
592
651
|
this.finalizeResponse(accumulatedContent);
|
|
593
652
|
});
|
|
653
|
+
// Handle any errors in the response stream
|
|
594
654
|
responseStream.data.on('error', (error) => {
|
|
595
655
|
console.error('Error streaming response:', error);
|
|
596
656
|
this.isProcessing = false;
|
|
@@ -598,6 +658,69 @@ class ChatManager {
|
|
|
598
658
|
this.rl.prompt();
|
|
599
659
|
});
|
|
600
660
|
}
|
|
661
|
+
processOutput(output, accumulatedContent, codeBuffer, isBufferingCode) {
|
|
662
|
+
const codeBlockMarker = '\n```';
|
|
663
|
+
let currentIndex = 0;
|
|
664
|
+
while (currentIndex < output.length) {
|
|
665
|
+
let markerIndex = output.indexOf(codeBlockMarker, currentIndex);
|
|
666
|
+
if (output.startsWith('```'))
|
|
667
|
+
markerIndex = 0;
|
|
668
|
+
// Check if the code block marker is found
|
|
669
|
+
if (markerIndex !== -1) {
|
|
670
|
+
if (isBufferingCode) {
|
|
671
|
+
// End of code block
|
|
672
|
+
codeBuffer += output.slice(currentIndex, markerIndex + codeBlockMarker.length);
|
|
673
|
+
const { language, currentStart, newEnd, current: currentCode, new: newCode } = this.parseCodeBuffer(codeBuffer);
|
|
674
|
+
if (language)
|
|
675
|
+
process.stdout.write(language);
|
|
676
|
+
if (currentStart)
|
|
677
|
+
process.stdout.write(currentStart);
|
|
678
|
+
if (currentCode)
|
|
679
|
+
process.stdout.write((0, cli_highlight_1.default)(currentCode, { language }));
|
|
680
|
+
process.stdout.write('=======');
|
|
681
|
+
if (newCode)
|
|
682
|
+
process.stdout.write((0, cli_highlight_1.default)(newCode, { language }));
|
|
683
|
+
if (newEnd)
|
|
684
|
+
process.stdout.write(newEnd);
|
|
685
|
+
accumulatedContent += codeBuffer;
|
|
686
|
+
codeBuffer = '';
|
|
687
|
+
isBufferingCode = false;
|
|
688
|
+
currentIndex = markerIndex + codeBlockMarker.length;
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
// Start of code block
|
|
692
|
+
process.stdout.write(output.slice(currentIndex, markerIndex + codeBlockMarker.length));
|
|
693
|
+
accumulatedContent += output.slice(currentIndex, markerIndex + codeBlockMarker.length);
|
|
694
|
+
codeBuffer = '';
|
|
695
|
+
isBufferingCode = true;
|
|
696
|
+
currentIndex = markerIndex + codeBlockMarker.length;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
// No code block marker found
|
|
701
|
+
if (isBufferingCode) {
|
|
702
|
+
// buffering code
|
|
703
|
+
codeBuffer += output.slice(currentIndex);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
process.stdout.write(output.slice(currentIndex));
|
|
707
|
+
accumulatedContent += output.slice(currentIndex);
|
|
708
|
+
}
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return [isBufferingCode, codeBuffer, accumulatedContent];
|
|
713
|
+
}
|
|
714
|
+
parseCodeBuffer(codeBuffer) {
|
|
715
|
+
const lines = codeBuffer.split('\n');
|
|
716
|
+
const language = lines[0].trim();
|
|
717
|
+
const currentStartIndex = lines.findIndex(line => line.startsWith('<<<<<<< CURRENT'));
|
|
718
|
+
const dividerIndex = lines.findIndex(line => line === '=======');
|
|
719
|
+
const newEndIndex = lines.findIndex(line => line.startsWith('>>>>>>> NEW'));
|
|
720
|
+
const current = lines.slice(currentStartIndex + 1, dividerIndex).join('\n');
|
|
721
|
+
const newCode = lines.slice(dividerIndex + 1, newEndIndex).join('\n');
|
|
722
|
+
return { language, currentStart: lines[currentStartIndex], newEnd: lines[newEndIndex], current, new: newCode };
|
|
723
|
+
}
|
|
601
724
|
async finalizeResponse(accumulatedContent) {
|
|
602
725
|
this.chatHistory.push({ role: 'assistant', content: accumulatedContent.trim() });
|
|
603
726
|
this.isProcessing = false;
|
|
@@ -605,19 +728,12 @@ class ChatManager {
|
|
|
605
728
|
const currentNewBlocks = (0, string_utils_1.extractCurrentNewBlocks)(accumulatedContent);
|
|
606
729
|
const [canApply, summary] = await (0, string_utils_1.checkBlocksApplicability)(currentNewBlocks);
|
|
607
730
|
if (!canApply) {
|
|
608
|
-
console.log(`\n\n${summary}\n\n${colors_1.default.
|
|
609
|
-
|
|
610
|
-
this.rl.question(colors_1.default.green.bold('cloving> '), (answer) => {
|
|
611
|
-
resolve(answer.trim().toLowerCase());
|
|
612
|
-
});
|
|
613
|
-
});
|
|
614
|
-
if (reviewPrompt.startsWith('y') || reviewPrompt === '') {
|
|
615
|
-
this.processUserInput(`Some of the provided code blocks could not be applied,
|
|
731
|
+
console.log(`\n\n${summary}\n\n${colors_1.default.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied, prompting the AI to try again with more detail.\n`);
|
|
732
|
+
this.processUserInput(`Some of the provided code blocks could not be applied,
|
|
616
733
|
please match the existing code with a few more lines of context and make sure it is a character for character exact match.
|
|
617
734
|
|
|
618
735
|
${summary}`);
|
|
619
|
-
|
|
620
|
-
}
|
|
736
|
+
return;
|
|
621
737
|
}
|
|
622
738
|
}
|
|
623
739
|
process.stdout.write(`
|
|
@@ -655,7 +771,7 @@ You can follow up with another request or:
|
|
|
655
771
|
}
|
|
656
772
|
const promptTokens = Math.ceil(this.prompt.length / 4).toLocaleString();
|
|
657
773
|
const statusMessage = error.response?.data?.statusMessage;
|
|
658
|
-
console.error(colors_1.default.red(`\nError (${errorNumber})
|
|
774
|
+
console.error(colors_1.default.red(`\nError (${errorNumber}) while submitting the prompt to the AI API\n\n${statusMessage}\n`));
|
|
659
775
|
this.isProcessing = false;
|
|
660
776
|
this.rl.prompt();
|
|
661
777
|
}
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const events_1 = require("events");
|
|
4
|
+
// A chunk is a JSON object with different keys depending on the AI service provider
|
|
5
|
+
//
|
|
6
|
+
// Some providers produce single response in a single chunk
|
|
7
|
+
//
|
|
8
|
+
// Others produce multiple responses in a single chunk
|
|
9
|
+
//
|
|
10
|
+
// This Chunk Manager's job is to buffer the chunks and emit them as a single string
|
|
11
|
+
// without automatically clearing the buffer until clearBuffer is called.
|
|
12
|
+
// That way, the consumer of the chunk can process it and clear the buffer after
|
|
13
|
+
// they have received enough data.
|
|
4
14
|
class ChunkManager extends events_1.EventEmitter {
|
|
5
15
|
constructor() {
|
|
6
16
|
super(...arguments);
|
|
7
17
|
this.buffer = '';
|
|
8
18
|
}
|
|
9
19
|
addChunk(chunk) {
|
|
20
|
+
if (!chunk)
|
|
21
|
+
return;
|
|
10
22
|
this.buffer += chunk;
|
|
11
23
|
this.emit('content', this.buffer);
|
|
12
24
|
}
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const prompts_1 = require("@inquirer/prompts");
|
|
7
7
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
8
8
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
9
|
-
const
|
|
9
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
10
10
|
const string_utils_1 = require("../utils/string_utils");
|
|
11
11
|
class CodeManager {
|
|
12
12
|
constructor(options) {
|
|
@@ -19,7 +19,7 @@ class CodeManager {
|
|
|
19
19
|
try {
|
|
20
20
|
if (this.options.files) {
|
|
21
21
|
for (const file of this.options.files) {
|
|
22
|
-
this.contextFiles = await (0,
|
|
22
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(file, this.contextFiles, this.options);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
@@ -29,7 +29,7 @@ class CodeManager {
|
|
|
29
29
|
message: `Enter the relative path of a file or directory you would like to include as context (or press enter to continue):`,
|
|
30
30
|
});
|
|
31
31
|
if (contextFile) {
|
|
32
|
-
this.contextFiles = await (0,
|
|
32
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(contextFile, this.contextFiles, this.options);
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
35
|
includeMoreFiles = false;
|
|
@@ -70,7 +70,7 @@ ${summary}`);
|
|
|
70
70
|
}
|
|
71
71
|
async generateCode(userPrompt) {
|
|
72
72
|
if (this.chatHistory.length === 0) {
|
|
73
|
-
const systemPrompt = (0,
|
|
73
|
+
const systemPrompt = (0, prompt_utils_1.generateCodegenPrompt)(this.contextFiles);
|
|
74
74
|
this.chatHistory.push({ role: 'user', content: systemPrompt });
|
|
75
75
|
this.chatHistory.push({ role: 'assistant', content: 'What would you like to do?' });
|
|
76
76
|
}
|
|
@@ -127,7 +127,7 @@ ${summary}`);
|
|
|
127
127
|
message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
128
128
|
});
|
|
129
129
|
if (contextFile) {
|
|
130
|
-
this.contextFiles = await (0,
|
|
130
|
+
this.contextFiles = await (0, prompt_utils_1.addFileOrDirectoryToContext)(contextFile, this.contextFiles, this.options);
|
|
131
131
|
}
|
|
132
132
|
else {
|
|
133
133
|
includeMoreFiles = false;
|
|
@@ -0,0 +1,216 @@
|
|
|
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 colors_1 = __importDefault(require("colors"));
|
|
10
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
11
|
+
const fs_2 = require("fs");
|
|
12
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
13
|
+
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
14
|
+
const string_utils_1 = require("../utils/string_utils");
|
|
15
|
+
const config_utils_1 = require("../utils/config_utils");
|
|
16
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
17
|
+
class InitManager {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.options = options;
|
|
20
|
+
this.chatHistory = [];
|
|
21
|
+
this.options.silent = (0, config_utils_1.getConfig)(this.options).globalSilent || false;
|
|
22
|
+
this.gpt = new cloving_gpt_1.default(this.options);
|
|
23
|
+
this.chatHistory.push({
|
|
24
|
+
role: 'system',
|
|
25
|
+
content: prompt_utils_1.INIT_INSTRUCTIONS,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initializes the InitManager by collecting special file contents, checking for dependencies,
|
|
30
|
+
* and processing the AI response for the project details.
|
|
31
|
+
*
|
|
32
|
+
* @return {Promise<void>} A promise that resolves when the initialization is complete.
|
|
33
|
+
*/
|
|
34
|
+
async initialize() {
|
|
35
|
+
const specialFileContents = (0, prompt_utils_1.collectSpecialFileContents)();
|
|
36
|
+
const specialFileNames = Object.keys(specialFileContents).map((file) => ' - ' + file);
|
|
37
|
+
this.logInitializationMessage(specialFileNames);
|
|
38
|
+
const config = (0, config_utils_1.getConfig)(this.options);
|
|
39
|
+
if (!config || !config?.models) {
|
|
40
|
+
console.error('No cloving configuration found. Please run `cloving config`');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!(0, prompt_utils_1.checkForSpecialFiles)()) {
|
|
44
|
+
console.error('No dependencies files detected. Please add a dependency file (e.g. package.json, Gemfile, requirements.txt, etc.) to your project and run `cloving init` again.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `describe_${Date.now()}.tmp`);
|
|
48
|
+
try {
|
|
49
|
+
const limitedFileList = await this.getFilteredFileList();
|
|
50
|
+
const projectDetails = {
|
|
51
|
+
files: limitedFileList,
|
|
52
|
+
specialFiles: specialFileContents,
|
|
53
|
+
};
|
|
54
|
+
const [success, errorMessage] = await this.processAIResponse(projectDetails, tempFilePath);
|
|
55
|
+
if (!success) {
|
|
56
|
+
console.log(errorMessage);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await this.promptUserForReview();
|
|
60
|
+
// Clean up
|
|
61
|
+
await fs_2.promises.unlink(tempFilePath);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error('Error describing the project:', error.message);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Checks if a directory exists at the specified path.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} directory - The path of the directory to check.
|
|
71
|
+
* @return {Promise<boolean>} A promise that resolves to true if the directory exists, false otherwise.
|
|
72
|
+
*/
|
|
73
|
+
async directoryExists(directory) {
|
|
74
|
+
try {
|
|
75
|
+
const stats = await fs_2.promises.stat(directory);
|
|
76
|
+
return stats.isDirectory();
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Returns a filtered list of files, excluding those ignored by the .gitignore file.
|
|
84
|
+
*
|
|
85
|
+
* @return {string[]} A list of file paths, limited to the first 100 files.
|
|
86
|
+
*/
|
|
87
|
+
async getFilteredFileList() {
|
|
88
|
+
const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
|
|
89
|
+
const ig = (0, ignore_1.default)();
|
|
90
|
+
if (fs_1.default.existsSync(gitignorePath)) {
|
|
91
|
+
const gitignoreContent = fs_1.default.readFileSync(gitignorePath, 'utf-8');
|
|
92
|
+
ig.add(gitignoreContent);
|
|
93
|
+
}
|
|
94
|
+
const fileList = await (0, prompt_utils_1.generateFileList)();
|
|
95
|
+
const filteredFileList = fileList.filter((file) => {
|
|
96
|
+
try {
|
|
97
|
+
return !ig.ignores(file);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return filteredFileList.slice(0, 100);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Logs the initialization message based on the special file names and silent mode.
|
|
107
|
+
*
|
|
108
|
+
* @param {string[]} specialFileNames - A list of special file names to be analyzed.
|
|
109
|
+
* @return {void} No return value.
|
|
110
|
+
*/
|
|
111
|
+
logInitializationMessage(specialFileNames) {
|
|
112
|
+
if (!this.options.silent) {
|
|
113
|
+
if (specialFileNames.length > 0) {
|
|
114
|
+
console.log(`Cloving will analyze the list of files and the contents of the following files:
|
|
115
|
+
|
|
116
|
+
${specialFileNames.join('\n')}
|
|
117
|
+
|
|
118
|
+
Cloving will send AI a request to summarize the technologies used in this project.
|
|
119
|
+
|
|
120
|
+
This will provide better context for future Cloving requests.`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(`
|
|
124
|
+
This script will analyze the list of files in the current directory using GPT to summarize the
|
|
125
|
+
technologies used. This will provide better context for future Cloving requests.
|
|
126
|
+
`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Processes the AI response by generating a prompt, sending it to the AI,
|
|
132
|
+
* and saving the response to a temporary file and cloving.json.
|
|
133
|
+
*
|
|
134
|
+
* @param {any} projectDetails - A JSON object describing the project.
|
|
135
|
+
* @param {string} tempFilePath - The path to the temporary file.
|
|
136
|
+
* @param {string} errorMessage - An error message if there was an error.
|
|
137
|
+
* @return {[boolean, string]} A promise that resolves with an array containing a boolean
|
|
138
|
+
* indicating whether the AI response was successfully processed,
|
|
139
|
+
* and a message explaining any errors.
|
|
140
|
+
*/
|
|
141
|
+
async processAIResponse(projectDetails, tempFilePath, errorMessage = '') {
|
|
142
|
+
const prompt = errorMessage !== ''
|
|
143
|
+
? errorMessage
|
|
144
|
+
: `Here is a JSON object describing my project:
|
|
145
|
+
${JSON.stringify(projectDetails, null, 2)}`;
|
|
146
|
+
this.chatHistory.push({ role: 'user', content: prompt });
|
|
147
|
+
const aiChatResponse = await this.gpt.generateText({ prompt, messages: this.chatHistory });
|
|
148
|
+
this.chatHistory.push({ role: 'assistant', content: aiChatResponse });
|
|
149
|
+
let cleanAiChatResponse = (0, string_utils_1.extractJsonMetadata)(aiChatResponse);
|
|
150
|
+
const [validSyntax, syntaxErrorMessage] = this.checkClovingJsonSyntax(cleanAiChatResponse);
|
|
151
|
+
if (!validSyntax) {
|
|
152
|
+
console.log(syntaxErrorMessage);
|
|
153
|
+
console.log('Retrying...');
|
|
154
|
+
return this.processAIResponse(projectDetails, tempFilePath, syntaxErrorMessage);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
await fs_2.promises.writeFile(tempFilePath, cleanAiChatResponse);
|
|
158
|
+
// Save the AI chat response to cloving.json
|
|
159
|
+
await fs_2.promises.writeFile('cloving.json', cleanAiChatResponse);
|
|
160
|
+
console.log(`Project data saved to ${colors_1.default.green.bold('cloving.json')}`);
|
|
161
|
+
return [true, ''];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Checks the syntax of the generated cloving.json file.
|
|
166
|
+
*
|
|
167
|
+
* @param {string} cleanAiChatResponse - The cleaned AI chat response.
|
|
168
|
+
* @return {[boolean, string]} An array where the first element is a boolean indicating
|
|
169
|
+
* whether the syntax was valid, and the second element is a
|
|
170
|
+
* message explaining any errors.
|
|
171
|
+
*/
|
|
172
|
+
checkClovingJsonSyntax(cleanAiChatResponse) {
|
|
173
|
+
try {
|
|
174
|
+
JSON.parse(cleanAiChatResponse);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
return [
|
|
178
|
+
false,
|
|
179
|
+
`${colors_1.default.red.bold('ERROR:')} parsing cloving.json: ${colors_1.default.red.bold(error.message)}`,
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
const clovingJson = JSON.parse(cleanAiChatResponse);
|
|
183
|
+
const directoriesToCheck = [
|
|
184
|
+
...clovingJson.languages.map((language) => language.directory),
|
|
185
|
+
...clovingJson.frameworks.map((framework) => framework.directory),
|
|
186
|
+
...clovingJson.testingFrameworks.map((testingFramework) => testingFramework.directory),
|
|
187
|
+
];
|
|
188
|
+
const missingDirectories = directoriesToCheck.filter((directory) => !fs_1.default.existsSync(directory));
|
|
189
|
+
if (missingDirectories.length > 0) {
|
|
190
|
+
return [
|
|
191
|
+
false,
|
|
192
|
+
`${colors_1.default.red.bold('ERROR:')} The following directories were not found: ${colors_1.default.red.bold(missingDirectories.join(', '))}`,
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
return [true, ''];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Prompts the user to review the generated data if the `silent` option is not set.
|
|
199
|
+
* If the user confirms, reads the content of 'cloving.json' and logs it to the console.
|
|
200
|
+
*
|
|
201
|
+
* @return {Promise<void>} A promise that resolves when the review is complete.
|
|
202
|
+
*/
|
|
203
|
+
async promptUserForReview() {
|
|
204
|
+
if (!this.options.silent) {
|
|
205
|
+
const reviewAnswer = await (0, prompts_1.confirm)({
|
|
206
|
+
message: 'Do you want to review the generated data?',
|
|
207
|
+
default: true,
|
|
208
|
+
});
|
|
209
|
+
if (reviewAnswer) {
|
|
210
|
+
const clovingJsonContent = await fs_2.promises.readFile('cloving.json', 'utf-8');
|
|
211
|
+
console.log(clovingJsonContent);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.default = InitManager;
|
|
@@ -10,8 +10,17 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
11
11
|
const ChunkManager_1 = __importDefault(require("./ChunkManager"));
|
|
12
12
|
const config_utils_1 = require("../utils/config_utils");
|
|
13
|
-
const
|
|
13
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
14
14
|
class ProxyManager {
|
|
15
|
+
/**
|
|
16
|
+
* Creates an instance of ProxyManager.
|
|
17
|
+
* @param {ClovingGPTOptions} options - Configuration options for the ProxyManager.
|
|
18
|
+
* @description
|
|
19
|
+
* Initializes the ProxyManager with the given options. It sets up:
|
|
20
|
+
* - A ClovingGPT instance with silent and stream options enabled
|
|
21
|
+
* - An Express application with JSON body parsing
|
|
22
|
+
* - A ChunkManager for handling streaming responses
|
|
23
|
+
*/
|
|
15
24
|
constructor(options) {
|
|
16
25
|
this.options = options;
|
|
17
26
|
this.contextFiles = {};
|
|
@@ -42,7 +51,7 @@ class ProxyManager {
|
|
|
42
51
|
.stat(filePath)
|
|
43
52
|
.then((stat) => stat.isDirectory())
|
|
44
53
|
.catch(() => false)) {
|
|
45
|
-
const dirFiles = await (0,
|
|
54
|
+
const dirFiles = await (0, prompt_utils_1.getAllFilesInDirectory)(filePath);
|
|
46
55
|
expandedFiles = expandedFiles.concat(dirFiles.map((f) => path_1.default.relative(process.cwd(), f)));
|
|
47
56
|
}
|
|
48
57
|
else {
|
|
@@ -121,7 +130,6 @@ ${content}`;
|
|
|
121
130
|
res.write(`${dataToSend}\n\n`);
|
|
122
131
|
convertedStreams = [];
|
|
123
132
|
}
|
|
124
|
-
this.chunkManager.clearBuffer(lastChar);
|
|
125
133
|
buffer = buffer.slice(lastChar);
|
|
126
134
|
convertedStream = this.gpt.convertStream(buffer);
|
|
127
135
|
}
|
|
@@ -11,7 +11,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
11
11
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
12
12
|
const git_utils_1 = require("../utils/git_utils");
|
|
13
13
|
const string_utils_1 = require("../utils/string_utils");
|
|
14
|
-
const
|
|
14
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
15
15
|
const config_utils_1 = require("../utils/config_utils");
|
|
16
16
|
class ReviewManager {
|
|
17
17
|
constructor(options) {
|
|
@@ -21,6 +21,11 @@ class ReviewManager {
|
|
|
21
21
|
this.options = options;
|
|
22
22
|
this.gpt = new cloving_gpt_1.default(options);
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Loads the context files specified in the options. If directories are provided,
|
|
26
|
+
* it expands them to include all files within. The contents of each file are
|
|
27
|
+
* stored in the `contextFiles` object for later use.
|
|
28
|
+
*/
|
|
24
29
|
async loadContextFiles() {
|
|
25
30
|
if (this.options.files) {
|
|
26
31
|
let expandedFiles = [];
|
|
@@ -30,7 +35,7 @@ class ReviewManager {
|
|
|
30
35
|
.stat(filePath)
|
|
31
36
|
.then((stat) => stat.isDirectory())
|
|
32
37
|
.catch(() => false)) {
|
|
33
|
-
const dirFiles = await (0,
|
|
38
|
+
const dirFiles = await (0, prompt_utils_1.getAllFilesInDirectory)(filePath);
|
|
34
39
|
expandedFiles = expandedFiles.concat(dirFiles.map((f) => path_1.default.relative(process.cwd(), f)));
|
|
35
40
|
}
|
|
36
41
|
else {
|
|
@@ -50,6 +55,13 @@ class ReviewManager {
|
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Generates a review prompt based on the current chat history and options.
|
|
60
|
+
* If no chat history exists, it initializes it with a system prompt and a request
|
|
61
|
+
* for user input. The prompt includes a structured example response for code review.
|
|
62
|
+
*
|
|
63
|
+
* @returns {string} The generated review prompt.
|
|
64
|
+
*/
|
|
53
65
|
generateReviewPrompt() {
|
|
54
66
|
if (this.chatHistory.length === 0) {
|
|
55
67
|
let systemPrompt = `### Example of a well-structured response
|
|
@@ -8,7 +8,7 @@ const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
9
|
const prompts_1 = require("@inquirer/prompts");
|
|
10
10
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
11
|
-
const
|
|
11
|
+
const prompt_utils_1 = require("../utils/prompt_utils");
|
|
12
12
|
const string_utils_1 = require("../utils/string_utils");
|
|
13
13
|
const config_utils_1 = require("../utils/config_utils");
|
|
14
14
|
class ShellManager {
|
|
@@ -27,7 +27,7 @@ Please briefly explain how this shell script works.`;
|
|
|
27
27
|
}
|
|
28
28
|
async generateShell(prompt) {
|
|
29
29
|
if (this.chatHistory.length === 0) {
|
|
30
|
-
const systemPrompt = (0,
|
|
30
|
+
const systemPrompt = (0, prompt_utils_1.generateShellPrompt)();
|
|
31
31
|
this.chatHistory.push({ role: 'user', content: systemPrompt });
|
|
32
32
|
this.chatHistory.push({ role: 'assistant', content: 'What would you like to do?' });
|
|
33
33
|
}
|