cloving 0.3.19 → 0.3.20
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/dist/index.js +2 -0
- package/dist/managers/CommitManager.js +30 -16
- package/dist/package.json +1 -1
- package/package.json +1 -1
- package/.DS_Store +0 -0
- package/.aider.chat.history.md +0 -16978
- package/.aider.input.history +0 -15
- package/.aider.tags.cache.v3/cache.db +0 -0
- package/.aider.tags.cache.v3/cache.db-shm +0 -0
- package/.aider.tags.cache.v3/cache.db-wal +0 -0
- package/.cursorrules +0 -77
- package/.github/workflows/auto-release.yml +0 -83
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/npm-publish.yml +0 -34
- package/.gitignore +0 -67
- package/.husky/_/.gitignore +0 -1
- package/.husky/_/applypatch-msg +0 -2
- package/.husky/_/commit-msg +0 -2
- package/.husky/_/h +0 -22
- package/.husky/_/husky.sh +0 -9
- package/.husky/_/post-applypatch +0 -2
- package/.husky/_/post-checkout +0 -2
- package/.husky/_/post-commit +0 -2
- package/.husky/_/post-merge +0 -2
- package/.husky/_/post-rewrite +0 -2
- package/.husky/_/pre-applypatch +0 -2
- package/.husky/_/pre-auto-gc +0 -2
- package/.husky/_/pre-commit +0 -2
- package/.husky/_/pre-merge-commit +0 -2
- package/.husky/_/pre-push +0 -2
- package/.husky/_/pre-rebase +0 -2
- package/.husky/_/prepare-commit-msg +0 -2
- package/.husky/pre-commit +0 -1
- package/.husky/pre-push +0 -1
- package/.prettierrc.json +0 -7
- package/.tool-versions +0 -2
- package/SECURITY.md +0 -15
- package/cloving.json +0 -67
- package/jest.config.js +0 -11
- package/src/cloving_gpt/adapters/azureopenai.ts +0 -83
- package/src/cloving_gpt/adapters/claude.ts +0 -140
- package/src/cloving_gpt/adapters/gemini.ts +0 -146
- package/src/cloving_gpt/adapters/index.ts +0 -43
- package/src/cloving_gpt/adapters/mistral.ts +0 -142
- package/src/cloving_gpt/adapters/ollama.ts +0 -136
- package/src/cloving_gpt/adapters/openai.ts +0 -113
- package/src/cloving_gpt/index.ts +0 -229
- package/src/commands/chat.ts +0 -12
- package/src/commands/config.ts +0 -192
- package/src/commands/generate/code.ts +0 -18
- package/src/commands/generate/commit.ts +0 -9
- package/src/commands/generate/context.ts +0 -93
- package/src/commands/generate/docs.ts +0 -9
- package/src/commands/generate/review.ts +0 -9
- package/src/commands/generate/shell.ts +0 -9
- package/src/commands/generate/unit_tests.ts +0 -9
- package/src/commands/init.ts +0 -9
- package/src/commands/models.ts +0 -21
- package/src/commands/proxy.ts +0 -9
- package/src/commands/tokens.ts +0 -82
- package/src/index.ts +0 -197
- package/src/managers/BlockManager.ts +0 -126
- package/src/managers/ChatManager.ts +0 -755
- package/src/managers/ChunkManager.ts +0 -31
- package/src/managers/CodeManager.ts +0 -226
- package/src/managers/CommitManager.ts +0 -71
- package/src/managers/DocsManager.ts +0 -101
- package/src/managers/InitManager.ts +0 -263
- package/src/managers/ProxyManager.ts +0 -240
- package/src/managers/ReviewManager.ts +0 -154
- package/src/managers/ShellManager.ts +0 -172
- package/src/managers/StreamManager.ts +0 -353
- package/src/managers/UnitTestManager.ts +0 -293
- package/src/utils/config_utils.ts +0 -271
- package/src/utils/git_utils.ts +0 -230
- package/src/utils/model_utils.ts +0 -3
- package/src/utils/prompt_utils.ts +0 -375
- package/src/utils/prompts/CODEGEN_COULDNT_APPLY.md +0 -5
- package/src/utils/prompts/CODEGEN_EXAMPLES.md +0 -82
- package/src/utils/prompts/CODEGEN_INSTRUCTIONS.md +0 -43
- package/src/utils/prompts/DOCS_INSTRUCTIONS.md +0 -9
- package/src/utils/prompts/INIT_INSTRUCTIONS.md +0 -132
- package/src/utils/prompts/REVIEW_INSTRUCTIONS.md +0 -49
- package/src/utils/prompts/SHELL_INSTRUCTIONS.md +0 -15
- package/src/utils/prompts/SPECIAL_FILES.md +0 -66
- package/src/utils/prompts/index.ts +0 -37
- package/src/utils/string_utils.ts +0 -552
- package/src/utils/types.ts +0 -127
- package/tests/commands/generate/unit_tests.test.ts +0 -27
- package/tests/utils/git_utils.test.ts +0 -116
- package/tests/utils/model_utils.test.ts +0 -27
- package/tests/utils/prompt_utils.test.ts +0 -33
- package/tests/utils/string_utils.test.ts +0 -560
- package/tsconfig.json +0 -14
- package/yarn.lock +0 -5823
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'events'
|
|
2
|
-
|
|
3
|
-
// A chunk is a JSON object with different keys depending on the AI service provider
|
|
4
|
-
//
|
|
5
|
-
// Some providers produce single response in a single chunk
|
|
6
|
-
//
|
|
7
|
-
// Others produce multiple responses in a single chunk
|
|
8
|
-
//
|
|
9
|
-
// This Chunk Manager's job is to buffer the chunks and emit them as a single string
|
|
10
|
-
// without automatically clearing the buffer until clearBuffer is called.
|
|
11
|
-
// That way, the consumer of the chunk can process it and clear the buffer after
|
|
12
|
-
// they have received enough data.
|
|
13
|
-
class ChunkManager extends EventEmitter {
|
|
14
|
-
private buffer: string = ''
|
|
15
|
-
|
|
16
|
-
addChunk(chunk: string | undefined) {
|
|
17
|
-
if (!chunk) return
|
|
18
|
-
this.buffer += chunk
|
|
19
|
-
this.emit('content', this.buffer)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
clearBuffer(lastChar: number) {
|
|
23
|
-
this.buffer = this.buffer.substring(lastChar)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
getBuffer() {
|
|
27
|
-
return this.buffer
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export default ChunkManager
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { select, input, confirm } from '@inquirer/prompts'
|
|
2
|
-
import { AxiosError } from 'axios'
|
|
3
|
-
import highlight from 'cli-highlight'
|
|
4
|
-
import colors from 'colors'
|
|
5
|
-
|
|
6
|
-
import ClovingGPT from '../cloving_gpt'
|
|
7
|
-
|
|
8
|
-
import { getConfig } from '../utils/config_utils'
|
|
9
|
-
import { generateCodegenPrompt, addFileOrDirectoryToContext } from '../utils/prompt_utils'
|
|
10
|
-
import {
|
|
11
|
-
checkBlocksApplicability,
|
|
12
|
-
extractCurrentNewBlocks,
|
|
13
|
-
applyAndSaveCurrentNewBlocks,
|
|
14
|
-
} from '../utils/string_utils'
|
|
15
|
-
import { CODEGEN_COULDNT_APPLY } from '../utils/prompts'
|
|
16
|
-
import type { ClovingGPTOptions } from '../utils/types'
|
|
17
|
-
import StreamManager from './StreamManager'
|
|
18
|
-
|
|
19
|
-
class CodeManager extends StreamManager {
|
|
20
|
-
constructor(options: ClovingGPTOptions) {
|
|
21
|
-
super(options)
|
|
22
|
-
options.silent = getConfig(options).globalSilent || false
|
|
23
|
-
options.stream = true
|
|
24
|
-
this.gpt = new ClovingGPT(options)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public async initialize(): Promise<void> {
|
|
28
|
-
try {
|
|
29
|
-
if (this.options.files) {
|
|
30
|
-
for (const file of this.options.files) {
|
|
31
|
-
this.contextFiles = await addFileOrDirectoryToContext(
|
|
32
|
-
file,
|
|
33
|
-
this.contextFiles,
|
|
34
|
-
this.options,
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
} else {
|
|
38
|
-
let includeMoreFiles = true
|
|
39
|
-
|
|
40
|
-
while (includeMoreFiles) {
|
|
41
|
-
const contextFile = await input({
|
|
42
|
-
message: `Enter the relative path of a file or directory you would like to include as context (or press enter to continue): `,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
if (contextFile) {
|
|
46
|
-
this.contextFiles = await addFileOrDirectoryToContext(
|
|
47
|
-
contextFile,
|
|
48
|
-
this.contextFiles,
|
|
49
|
-
this.options,
|
|
50
|
-
)
|
|
51
|
-
} else {
|
|
52
|
-
includeMoreFiles = false
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (!this.options.prompt) {
|
|
58
|
-
const userPrompt = await input({
|
|
59
|
-
message: 'What would you like the code to do:',
|
|
60
|
-
})
|
|
61
|
-
this.options.prompt = userPrompt
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
await this.generateCode(this.options.prompt)
|
|
65
|
-
} catch (err) {
|
|
66
|
-
const error = err as AxiosError
|
|
67
|
-
console.error('Could not generate code', error.message)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
public async generateCode(userPrompt: string): Promise<void> {
|
|
72
|
-
await this.checkForLatestVersion()
|
|
73
|
-
await this.loadContextFiles()
|
|
74
|
-
|
|
75
|
-
if (this.chatHistory.length === 0) {
|
|
76
|
-
this.addUserPrompt(generateCodegenPrompt(this.contextFiles))
|
|
77
|
-
this.addAssistantResponse('What would you like to do?')
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.addUserPrompt(userPrompt)
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const responseStream = await this.gpt.streamText({
|
|
84
|
-
prompt: userPrompt,
|
|
85
|
-
messages: this.chatHistory,
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
this.handleResponseStream(responseStream)
|
|
89
|
-
} catch (err) {
|
|
90
|
-
const error = err as AxiosError
|
|
91
|
-
console.error('Error streaming response:', error.message)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
private async handleUserAction(response: string): Promise<void> {
|
|
96
|
-
const currentNewBlocks = extractCurrentNewBlocks(response)
|
|
97
|
-
const files = Array.from(new Set(currentNewBlocks.map((block) => block.filePath)))
|
|
98
|
-
|
|
99
|
-
if (this.options.save) {
|
|
100
|
-
await applyAndSaveCurrentNewBlocks(currentNewBlocks)
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const action = await select({
|
|
105
|
-
message: 'What would you like to do?',
|
|
106
|
-
choices: [
|
|
107
|
-
{ name: 'Revise', value: 'revise' },
|
|
108
|
-
{ name: 'Explain', value: 'explain' },
|
|
109
|
-
{ name: 'Save a Source Code File', value: 'save' },
|
|
110
|
-
{ name: 'Save All Source Code Files', value: 'saveAll' },
|
|
111
|
-
{ name: 'Copy Source Code to Clipboard', value: 'copySource' },
|
|
112
|
-
{ name: 'Copy Entire Response to Clipboard', value: 'copyAll' },
|
|
113
|
-
{ name: 'Done', value: 'done' },
|
|
114
|
-
],
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
switch (action) {
|
|
118
|
-
case 'revise':
|
|
119
|
-
const newPrompt = await input({
|
|
120
|
-
message: 'How would you like to modify the output:',
|
|
121
|
-
})
|
|
122
|
-
let includeMoreFiles = true
|
|
123
|
-
while (includeMoreFiles) {
|
|
124
|
-
const contextFile = await input({
|
|
125
|
-
message:
|
|
126
|
-
'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
if (contextFile) {
|
|
130
|
-
this.contextFiles = await addFileOrDirectoryToContext(
|
|
131
|
-
contextFile,
|
|
132
|
-
this.contextFiles,
|
|
133
|
-
this.options,
|
|
134
|
-
)
|
|
135
|
-
} else {
|
|
136
|
-
includeMoreFiles = false
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
await this.generateCode(newPrompt)
|
|
140
|
-
break
|
|
141
|
-
case 'explain':
|
|
142
|
-
const explainPrompt = this.generateExplainCodePrompt(response)
|
|
143
|
-
this.chatHistory.push({ role: 'user', content: explainPrompt })
|
|
144
|
-
const explainCodeCommand = await this.gpt.generateText({
|
|
145
|
-
prompt: explainPrompt,
|
|
146
|
-
messages: this.chatHistory,
|
|
147
|
-
})
|
|
148
|
-
console.log(highlight(explainCodeCommand, { language: 'markdown' }))
|
|
149
|
-
break
|
|
150
|
-
case 'save':
|
|
151
|
-
let saveAnother = true
|
|
152
|
-
while (saveAnother) {
|
|
153
|
-
const fileToSave = await select({
|
|
154
|
-
message: 'Which file do you want to save?',
|
|
155
|
-
choices: files.map((file) => ({ name: file, value: file })),
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
await applyAndSaveCurrentNewBlocks(
|
|
159
|
-
currentNewBlocks.filter((block) => block.filePath === fileToSave),
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
const saveMore = await confirm({
|
|
163
|
-
message: 'Do you want to save another file?',
|
|
164
|
-
default: true,
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
saveAnother = saveMore
|
|
168
|
-
}
|
|
169
|
-
break
|
|
170
|
-
case 'saveAll':
|
|
171
|
-
await applyAndSaveCurrentNewBlocks(currentNewBlocks)
|
|
172
|
-
console.log(colors.green('\nAll files have been saved.'))
|
|
173
|
-
break
|
|
174
|
-
case 'done':
|
|
175
|
-
break
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private generateExplainCodePrompt(prompt: string): string {
|
|
180
|
-
return `${prompt}
|
|
181
|
-
|
|
182
|
-
# Task
|
|
183
|
-
|
|
184
|
-
Please briefly explain how the code works in this.`
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Finalizes the response processing.
|
|
189
|
-
* This method should be overridden to provide specific finalization behavior.
|
|
190
|
-
* @protected
|
|
191
|
-
*/
|
|
192
|
-
protected async finalizeResponse(): Promise<void> {
|
|
193
|
-
this.addAssistantResponse(this.responseString)
|
|
194
|
-
this.isProcessing = false
|
|
195
|
-
|
|
196
|
-
if (this.responseString !== '') {
|
|
197
|
-
const currentNewBlocks = extractCurrentNewBlocks(this.responseString)
|
|
198
|
-
const [canApply, summary] = await checkBlocksApplicability(currentNewBlocks)
|
|
199
|
-
if (!canApply) {
|
|
200
|
-
await this.handleUnapplicableBlocks(summary)
|
|
201
|
-
} else {
|
|
202
|
-
this.retryCount = 0 // Reset the retry count on successful application
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
await this.handleUserAction(this.responseString)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private async handleUnapplicableBlocks(summary: string): Promise<void> {
|
|
210
|
-
if (this.retryCount < this.maxRetries) {
|
|
211
|
-
console.log(
|
|
212
|
-
`\n\n${summary}\n\n${colors.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied. Retrying (Attempt ${this.retryCount + 1}/${this.maxRetries})...\n`,
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
this.retryCount++
|
|
216
|
-
await this.generateCode(`${CODEGEN_COULDNT_APPLY}\n\n${summary}`)
|
|
217
|
-
} else {
|
|
218
|
-
console.log(
|
|
219
|
-
`\n\n${colors.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`,
|
|
220
|
-
)
|
|
221
|
-
this.retryCount = 0 // Reset the retry count for future attempts
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export default CodeManager
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { execFileSync, execSync } from 'child_process'
|
|
4
|
-
import { extractMarkdown } from '../utils/string_utils'
|
|
5
|
-
import { generateCommitMessagePrompt } from '../utils/git_utils'
|
|
6
|
-
import { getConfig } from '../utils/config_utils'
|
|
7
|
-
import ClovingGPT from '../cloving_gpt'
|
|
8
|
-
import type { ClovingGPTOptions } from '../utils/types'
|
|
9
|
-
import type { AxiosError } from 'axios'
|
|
10
|
-
|
|
11
|
-
class CommitManager {
|
|
12
|
-
private gpt: ClovingGPT
|
|
13
|
-
|
|
14
|
-
constructor(options: ClovingGPTOptions) {
|
|
15
|
-
options.silent = getConfig(options).globalSilent || false
|
|
16
|
-
this.gpt = new ClovingGPT(options)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
private cleanCommitMessage(message: string): string {
|
|
20
|
-
// Remove markdown code block formatting if present, including any text before and after
|
|
21
|
-
const codeBlockRegex = /.*?```.*\n(.*?)\n```.*/m
|
|
22
|
-
const match = message.match(codeBlockRegex)
|
|
23
|
-
return match ? match[1].trim() : message.trim()
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public async generateCommit(): Promise<void> {
|
|
27
|
-
try {
|
|
28
|
-
// Get the git diff
|
|
29
|
-
const diff = execSync('git diff HEAD').toString().trim()
|
|
30
|
-
|
|
31
|
-
// Check if the diff is blank
|
|
32
|
-
if (!diff) {
|
|
33
|
-
console.error('No changes to commit.')
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Generate the prompt for commit message
|
|
38
|
-
const prompt = generateCommitMessagePrompt(diff)
|
|
39
|
-
|
|
40
|
-
// Instantiate ClovingGPT and get the commit message
|
|
41
|
-
const rawCommitMessage = await this.gpt.generateText({ prompt })
|
|
42
|
-
|
|
43
|
-
// Clean the commit message using extractMarkdown and remove code blocks
|
|
44
|
-
const commitMessage = this.cleanCommitMessage(extractMarkdown(rawCommitMessage))
|
|
45
|
-
|
|
46
|
-
// Write the commit message to a temporary file
|
|
47
|
-
const tempCommitFilePath = path.join('.git', 'SUGGESTED_COMMIT_EDITMSG')
|
|
48
|
-
fs.writeFileSync(tempCommitFilePath, commitMessage)
|
|
49
|
-
|
|
50
|
-
// Commit the changes using the generated commit message
|
|
51
|
-
try {
|
|
52
|
-
execFileSync('git', ['commit', '-a', '--edit', '--file', tempCommitFilePath], {
|
|
53
|
-
stdio: 'inherit',
|
|
54
|
-
})
|
|
55
|
-
} catch (commitError) {
|
|
56
|
-
// If commit is canceled (non-zero exit), handle it here
|
|
57
|
-
console.log('Commit was canceled or failed.')
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Remove the temporary file using fs
|
|
61
|
-
fs.unlink(tempCommitFilePath, (err) => {
|
|
62
|
-
if (err) throw err
|
|
63
|
-
})
|
|
64
|
-
} catch (err) {
|
|
65
|
-
const error = err as AxiosError
|
|
66
|
-
console.error('Could not generate commit message:', error.message)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export default CommitManager
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { confirm } from '@inquirer/prompts'
|
|
2
|
-
import colors from 'colors'
|
|
3
|
-
|
|
4
|
-
import ClovingGPT from '../cloving_gpt'
|
|
5
|
-
import { getConfig } from '../utils/config_utils'
|
|
6
|
-
import { CODEGEN_COULDNT_APPLY } from '../utils/prompts'
|
|
7
|
-
import { generateDocsPrompt } from '../utils/prompt_utils'
|
|
8
|
-
import {
|
|
9
|
-
applyAndSaveCurrentNewBlocks,
|
|
10
|
-
extractCurrentNewBlocks,
|
|
11
|
-
checkBlocksApplicability,
|
|
12
|
-
} from '../utils/string_utils'
|
|
13
|
-
import type { ClovingGPTOptions } from '../utils/types'
|
|
14
|
-
import StreamManager from './StreamManager'
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Manages the generation of documentation for specified files.
|
|
18
|
-
*/
|
|
19
|
-
class DocsManager extends StreamManager {
|
|
20
|
-
/**
|
|
21
|
-
* Creates a new DocumentationManager instance.
|
|
22
|
-
* @param {ClovingGPTOptions} options - Configuration options for the DocumentationManager.
|
|
23
|
-
*/
|
|
24
|
-
constructor(options: ClovingGPTOptions) {
|
|
25
|
-
super(options)
|
|
26
|
-
options.silent = getConfig(options).globalSilent || false
|
|
27
|
-
options.stream = true
|
|
28
|
-
this.gpt = new ClovingGPT(options)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Generates documentation for the specified files.
|
|
33
|
-
* @returns {Promise<void>}
|
|
34
|
-
*/
|
|
35
|
-
public async generateDocs(extraPrompt?: string): Promise<void> {
|
|
36
|
-
try {
|
|
37
|
-
this.isProcessing = true
|
|
38
|
-
if (this.retryCount === 0) {
|
|
39
|
-
await this.checkForLatestVersion()
|
|
40
|
-
}
|
|
41
|
-
await this.loadContextFiles()
|
|
42
|
-
this.addUserPrompt(`${generateDocsPrompt(this.contextFiles)}\n\n${extraPrompt || ''}`)
|
|
43
|
-
|
|
44
|
-
const responseStream = await this.gpt.streamText({
|
|
45
|
-
prompt: this.prompt,
|
|
46
|
-
messages: this.chatHistory,
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
this.handleResponseStream(responseStream)
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error(colors.red('Error generating documentation:'), error)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Finalizes the response processing.
|
|
57
|
-
* This method should be overridden to provide specific finalization behavior.
|
|
58
|
-
* @protected
|
|
59
|
-
*/
|
|
60
|
-
protected async finalizeResponse(): Promise<void> {
|
|
61
|
-
this.addAssistantResponse(this.responseString)
|
|
62
|
-
this.isProcessing = false
|
|
63
|
-
|
|
64
|
-
if (this.responseString !== '') {
|
|
65
|
-
const currentNewBlocks = extractCurrentNewBlocks(this.responseString)
|
|
66
|
-
const [canApply, summary] = await checkBlocksApplicability(currentNewBlocks)
|
|
67
|
-
if (canApply && currentNewBlocks.length > 0) {
|
|
68
|
-
const shouldSave = await confirm({
|
|
69
|
-
message: '\n\nDo you want to save the generated documentation?',
|
|
70
|
-
default: true,
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
if (shouldSave) {
|
|
74
|
-
await applyAndSaveCurrentNewBlocks(currentNewBlocks)
|
|
75
|
-
console.log(colors.green('\nDocumentation has been saved to the respective files.'))
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
if (this.retryCount < this.maxRetries) {
|
|
79
|
-
console.log(
|
|
80
|
-
`\n\n${summary}\n\n${colors.yellow.bold('WARNING')} Some of the provided code blocks could not be automatically applied. Retrying (Attempt ${this.retryCount + 1}/${this.maxRetries})...\n`,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
this.retryCount++
|
|
84
|
-
this.generateDocs(`\n\n${CODEGEN_COULDNT_APPLY}\n\n${summary}`)
|
|
85
|
-
|
|
86
|
-
return
|
|
87
|
-
} else {
|
|
88
|
-
console.log(
|
|
89
|
-
`\n\n${colors.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`,
|
|
90
|
-
)
|
|
91
|
-
this.retryCount = 0 // Reset the retry count for future attempts
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Reset responseString after processing
|
|
97
|
-
this.responseString = ''
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export default DocsManager
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import os from 'os'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import colors from 'colors'
|
|
5
|
-
import ignore from 'ignore'
|
|
6
|
-
import { promises as fsPromises } from 'fs'
|
|
7
|
-
import { confirm } from '@inquirer/prompts'
|
|
8
|
-
|
|
9
|
-
import ClovingGPT from '../cloving_gpt'
|
|
10
|
-
import { extractJsonMetadata } from '../utils/string_utils'
|
|
11
|
-
import { getConfig } from '../utils/config_utils'
|
|
12
|
-
import {
|
|
13
|
-
generateFileList,
|
|
14
|
-
collectSpecialFileContents,
|
|
15
|
-
checkForSpecialFiles,
|
|
16
|
-
} from '../utils/prompt_utils'
|
|
17
|
-
import { INIT_INSTRUCTIONS } from '../utils/prompts'
|
|
18
|
-
import type { ClovingGPTOptions, ChatMessage } from '../utils/types'
|
|
19
|
-
|
|
20
|
-
class InitManager {
|
|
21
|
-
private gpt: ClovingGPT
|
|
22
|
-
private chatHistory: ChatMessage[] = []
|
|
23
|
-
|
|
24
|
-
constructor(private options: ClovingGPTOptions) {
|
|
25
|
-
this.options.silent = getConfig(this.options).globalSilent || false
|
|
26
|
-
this.gpt = new ClovingGPT(this.options)
|
|
27
|
-
this.chatHistory.push({
|
|
28
|
-
role: 'system',
|
|
29
|
-
content: INIT_INSTRUCTIONS,
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Initializes the InitManager by collecting special file contents, checking for dependencies,
|
|
35
|
-
* and processing the AI response for the project details.
|
|
36
|
-
*
|
|
37
|
-
* @return {Promise<void>} A promise that resolves when the initialization is complete.
|
|
38
|
-
*/
|
|
39
|
-
public async initialize(): Promise<void> {
|
|
40
|
-
const specialFileContents = collectSpecialFileContents()
|
|
41
|
-
const specialFileNames = Object.keys(specialFileContents).map((file) => ' - ' + file)
|
|
42
|
-
|
|
43
|
-
this.logInitializationMessage(specialFileNames)
|
|
44
|
-
|
|
45
|
-
const config = getConfig(this.options)
|
|
46
|
-
if (!config || !config?.models) {
|
|
47
|
-
console.error('No cloving configuration found. Please run `cloving config`')
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!checkForSpecialFiles()) {
|
|
52
|
-
console.error(
|
|
53
|
-
'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.',
|
|
54
|
-
)
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const tempFilePath = path.join(os.tmpdir(), `describe_${Date.now()}.tmp`)
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const limitedFileList = await this.getFilteredFileList()
|
|
62
|
-
|
|
63
|
-
const projectDetails = {
|
|
64
|
-
files: limitedFileList,
|
|
65
|
-
specialFiles: specialFileContents,
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const [success, errorMessage] = await this.processAIResponse(projectDetails, tempFilePath)
|
|
69
|
-
|
|
70
|
-
if (!success) {
|
|
71
|
-
console.log(errorMessage)
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
await this.promptUserForReview()
|
|
76
|
-
|
|
77
|
-
// Clean up
|
|
78
|
-
await fsPromises.unlink(tempFilePath)
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('Error describing the project:', (error as Error).message)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Checks if a directory exists at the specified path.
|
|
86
|
-
*
|
|
87
|
-
* @param {string} directory - The path of the directory to check.
|
|
88
|
-
* @return {Promise<boolean>} A promise that resolves to true if the directory exists, false otherwise.
|
|
89
|
-
*/
|
|
90
|
-
private async directoryExists(directory: string): Promise<boolean> {
|
|
91
|
-
try {
|
|
92
|
-
const stats = await fsPromises.stat(directory)
|
|
93
|
-
return stats.isDirectory()
|
|
94
|
-
} catch (error) {
|
|
95
|
-
return false
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Returns a filtered list of files, excluding those ignored by the .gitignore file.
|
|
101
|
-
*
|
|
102
|
-
* @return {string[]} A list of file paths, limited to the first 100 files.
|
|
103
|
-
*/
|
|
104
|
-
private async getFilteredFileList(): Promise<string[]> {
|
|
105
|
-
const gitignorePath = path.join(process.cwd(), '.gitignore')
|
|
106
|
-
const ig = ignore()
|
|
107
|
-
if (fs.existsSync(gitignorePath)) {
|
|
108
|
-
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8')
|
|
109
|
-
ig.add(gitignoreContent)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const fileList = await generateFileList()
|
|
113
|
-
const filteredFileList = fileList.filter((file) => {
|
|
114
|
-
try {
|
|
115
|
-
return !ig.ignores(file)
|
|
116
|
-
} catch (error) {
|
|
117
|
-
return false
|
|
118
|
-
}
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
return filteredFileList.slice(0, 100)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Logs the initialization message based on the special file names and silent mode.
|
|
126
|
-
*
|
|
127
|
-
* @param {string[]} specialFileNames - A list of special file names to be analyzed.
|
|
128
|
-
* @return {void} No return value.
|
|
129
|
-
*/
|
|
130
|
-
private logInitializationMessage(specialFileNames: string[]): void {
|
|
131
|
-
if (!this.options.silent) {
|
|
132
|
-
if (specialFileNames.length > 0) {
|
|
133
|
-
console.log(`Cloving will analyze the list of files and the contents of the following files:
|
|
134
|
-
|
|
135
|
-
${specialFileNames.join('\n')}
|
|
136
|
-
|
|
137
|
-
Cloving will send AI a request to summarize the technologies used in this project.
|
|
138
|
-
|
|
139
|
-
This will provide better context for future Cloving requests.`)
|
|
140
|
-
} else {
|
|
141
|
-
console.log(`
|
|
142
|
-
This script will analyze the list of files in the current directory using GPT to summarize the
|
|
143
|
-
technologies used. This will provide better context for future Cloving requests.
|
|
144
|
-
`)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Processes the AI response by generating a prompt, sending it to the AI,
|
|
151
|
-
* and saving the response to a temporary file and cloving.json.
|
|
152
|
-
*
|
|
153
|
-
* @param {any} projectDetails - A JSON object describing the project.
|
|
154
|
-
* @param {string} tempFilePath - The path to the temporary file.
|
|
155
|
-
* @param {string} errorMessage - An error message if there was an error.
|
|
156
|
-
* @return {[boolean, string]} A promise that resolves with an array containing a boolean
|
|
157
|
-
* indicating whether the AI response was successfully processed,
|
|
158
|
-
* and a message explaining any errors.
|
|
159
|
-
*/
|
|
160
|
-
private async processAIResponse(
|
|
161
|
-
projectDetails: any,
|
|
162
|
-
tempFilePath: string,
|
|
163
|
-
errorMessage: string = '',
|
|
164
|
-
): Promise<[boolean, string]> {
|
|
165
|
-
const prompt =
|
|
166
|
-
errorMessage !== ''
|
|
167
|
-
? errorMessage
|
|
168
|
-
: `Here is a JSON object describing my project:
|
|
169
|
-
${JSON.stringify(projectDetails, null, 2)}`
|
|
170
|
-
this.chatHistory.push({ role: 'user', content: prompt })
|
|
171
|
-
const aiChatResponse = await this.gpt.generateText({ prompt, messages: this.chatHistory })
|
|
172
|
-
this.chatHistory.push({ role: 'assistant', content: aiChatResponse })
|
|
173
|
-
let cleanAiChatResponse = extractJsonMetadata(aiChatResponse)
|
|
174
|
-
const [validSyntax, syntaxErrorMessage] = this.checkClovingJsonSyntax(cleanAiChatResponse)
|
|
175
|
-
|
|
176
|
-
if (!validSyntax) {
|
|
177
|
-
console.log(syntaxErrorMessage)
|
|
178
|
-
console.log('Retrying...')
|
|
179
|
-
return this.processAIResponse(projectDetails, tempFilePath, syntaxErrorMessage)
|
|
180
|
-
} else {
|
|
181
|
-
await fsPromises.writeFile(tempFilePath, cleanAiChatResponse)
|
|
182
|
-
|
|
183
|
-
// Save the AI chat response to cloving.json
|
|
184
|
-
await fsPromises.writeFile('cloving.json', cleanAiChatResponse)
|
|
185
|
-
console.log(`Project data saved to ${colors.green.bold('cloving.json')}`)
|
|
186
|
-
return [true, '']
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Checks the syntax of the generated cloving.json file.
|
|
192
|
-
*
|
|
193
|
-
* @param {string} cleanAiChatResponse - The cleaned AI chat response.
|
|
194
|
-
* @return {[boolean, string]} An array where the first element is a boolean indicating
|
|
195
|
-
* whether the syntax was valid, and the second element is a
|
|
196
|
-
* message explaining any errors.
|
|
197
|
-
*/
|
|
198
|
-
private checkClovingJsonSyntax(cleanAiChatResponse: string): [boolean, string] {
|
|
199
|
-
try {
|
|
200
|
-
JSON.parse(cleanAiChatResponse)
|
|
201
|
-
} catch (error) {
|
|
202
|
-
return [
|
|
203
|
-
false,
|
|
204
|
-
`${colors.red.bold('ERROR:')} parsing cloving.json: ${colors.red.bold((error as Error).message)}`,
|
|
205
|
-
]
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const clovingJson = JSON.parse(cleanAiChatResponse)
|
|
209
|
-
|
|
210
|
-
const directoriesToCheck = [
|
|
211
|
-
...clovingJson.languages.map((language: any) => ({
|
|
212
|
-
type: 'Language',
|
|
213
|
-
name: language.name,
|
|
214
|
-
directory: language.directory,
|
|
215
|
-
})),
|
|
216
|
-
...clovingJson.frameworks.map((framework: any) => ({
|
|
217
|
-
type: 'Framework',
|
|
218
|
-
name: framework.name,
|
|
219
|
-
directory: framework.directory,
|
|
220
|
-
})),
|
|
221
|
-
...clovingJson.testingFrameworks.map((testingFramework: any) => ({
|
|
222
|
-
type: 'Testing Framework',
|
|
223
|
-
name: testingFramework.name,
|
|
224
|
-
directory: testingFramework.directory,
|
|
225
|
-
})),
|
|
226
|
-
]
|
|
227
|
-
|
|
228
|
-
const missingDirectories = directoriesToCheck.filter((item) => !fs.existsSync(item.directory))
|
|
229
|
-
|
|
230
|
-
if (missingDirectories.length > 0) {
|
|
231
|
-
const missingDirMessages = missingDirectories.map(
|
|
232
|
-
(item) => `${item.type} ${colors.bold(item.name)}: ${colors.red.bold(item.directory)}`,
|
|
233
|
-
)
|
|
234
|
-
return [
|
|
235
|
-
false,
|
|
236
|
-
`${colors.red.bold('ERROR:')} The following directories were not found:\n${missingDirMessages.join('\n')}`,
|
|
237
|
-
]
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return [true, '']
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Prompts the user to review the generated data if the `silent` option is not set.
|
|
245
|
-
* If the user confirms, reads the content of 'cloving.json' and logs it to the console.
|
|
246
|
-
*
|
|
247
|
-
* @return {Promise<void>} A promise that resolves when the review is complete.
|
|
248
|
-
*/
|
|
249
|
-
private async promptUserForReview(): Promise<void> {
|
|
250
|
-
if (!this.options.silent) {
|
|
251
|
-
const reviewAnswer = await confirm({
|
|
252
|
-
message: 'Do you want to review the generated data?',
|
|
253
|
-
default: true,
|
|
254
|
-
})
|
|
255
|
-
if (reviewAnswer) {
|
|
256
|
-
const clovingJsonContent = await fsPromises.readFile('cloving.json', 'utf-8')
|
|
257
|
-
console.log(clovingJsonContent)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export default InitManager
|