cloving 0.3.18 → 0.3.19
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/.DS_Store +0 -0
- package/.aider.chat.history.md +16978 -0
- package/.aider.input.history +15 -0
- 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 +77 -0
- package/.github/workflows/auto-release.yml +83 -0
- package/.github/workflows/ci.yml +32 -0
- package/.github/workflows/npm-publish.yml +34 -0
- package/.gitignore +67 -0
- package/.husky/_/.gitignore +1 -0
- package/.husky/_/applypatch-msg +2 -0
- package/.husky/_/commit-msg +2 -0
- package/.husky/_/h +22 -0
- package/.husky/_/husky.sh +9 -0
- package/.husky/_/post-applypatch +2 -0
- package/.husky/_/post-checkout +2 -0
- package/.husky/_/post-commit +2 -0
- package/.husky/_/post-merge +2 -0
- package/.husky/_/post-rewrite +2 -0
- package/.husky/_/pre-applypatch +2 -0
- package/.husky/_/pre-auto-gc +2 -0
- package/.husky/_/pre-commit +2 -0
- package/.husky/_/pre-merge-commit +2 -0
- package/.husky/_/pre-push +2 -0
- package/.husky/_/pre-rebase +2 -0
- package/.husky/_/prepare-commit-msg +2 -0
- package/.husky/pre-commit +1 -0
- package/.husky/pre-push +1 -0
- package/.prettierrc.json +7 -0
- package/.tool-versions +2 -0
- package/SECURITY.md +15 -0
- package/cloving.json +67 -0
- package/dist/managers/CommitManager.js +8 -2
- package/dist/package.json +1 -1
- package/jest.config.js +11 -0
- package/package.json +1 -1
- package/src/cloving_gpt/adapters/azureopenai.ts +83 -0
- package/src/cloving_gpt/adapters/claude.ts +140 -0
- package/src/cloving_gpt/adapters/gemini.ts +146 -0
- package/src/cloving_gpt/adapters/index.ts +43 -0
- package/src/cloving_gpt/adapters/mistral.ts +142 -0
- package/src/cloving_gpt/adapters/ollama.ts +136 -0
- package/src/cloving_gpt/adapters/openai.ts +113 -0
- package/src/cloving_gpt/index.ts +229 -0
- package/src/commands/chat.ts +12 -0
- package/src/commands/config.ts +192 -0
- package/src/commands/generate/code.ts +18 -0
- package/src/commands/generate/commit.ts +9 -0
- package/src/commands/generate/context.ts +93 -0
- package/src/commands/generate/docs.ts +9 -0
- package/src/commands/generate/review.ts +9 -0
- package/src/commands/generate/shell.ts +9 -0
- package/src/commands/generate/unit_tests.ts +9 -0
- package/src/commands/init.ts +9 -0
- package/src/commands/models.ts +21 -0
- package/src/commands/proxy.ts +9 -0
- package/src/commands/tokens.ts +82 -0
- package/src/index.ts +197 -0
- package/src/managers/BlockManager.ts +126 -0
- package/src/managers/ChatManager.ts +755 -0
- package/src/managers/ChunkManager.ts +31 -0
- package/src/managers/CodeManager.ts +226 -0
- package/src/managers/CommitManager.ts +71 -0
- package/src/managers/DocsManager.ts +101 -0
- package/src/managers/InitManager.ts +263 -0
- package/src/managers/ProxyManager.ts +240 -0
- package/src/managers/ReviewManager.ts +154 -0
- package/src/managers/ShellManager.ts +172 -0
- package/src/managers/StreamManager.ts +353 -0
- package/src/managers/UnitTestManager.ts +293 -0
- package/src/utils/config_utils.ts +271 -0
- package/src/utils/git_utils.ts +230 -0
- package/src/utils/model_utils.ts +3 -0
- package/src/utils/prompt_utils.ts +375 -0
- package/src/utils/prompts/CODEGEN_COULDNT_APPLY.md +5 -0
- package/src/utils/prompts/CODEGEN_EXAMPLES.md +82 -0
- package/src/utils/prompts/CODEGEN_INSTRUCTIONS.md +43 -0
- package/src/utils/prompts/DOCS_INSTRUCTIONS.md +9 -0
- package/src/utils/prompts/INIT_INSTRUCTIONS.md +132 -0
- package/src/utils/prompts/REVIEW_INSTRUCTIONS.md +49 -0
- package/src/utils/prompts/SHELL_INSTRUCTIONS.md +15 -0
- package/src/utils/prompts/SPECIAL_FILES.md +66 -0
- package/src/utils/prompts/index.ts +37 -0
- package/src/utils/string_utils.ts +552 -0
- package/src/utils/types.ts +127 -0
- package/tests/commands/generate/unit_tests.test.ts +27 -0
- package/tests/utils/git_utils.test.ts +116 -0
- package/tests/utils/model_utils.test.ts +27 -0
- package/tests/utils/prompt_utils.test.ts +33 -0
- package/tests/utils/string_utils.test.ts +560 -0
- package/tsconfig.json +14 -0
- package/yarn.lock +5823 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Configuration utilities for Cloving
|
|
3
|
+
* @module config_utils
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import os from 'os'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import fs from 'fs'
|
|
9
|
+
import ini from 'ini'
|
|
10
|
+
import { execFileSync } from 'child_process'
|
|
11
|
+
|
|
12
|
+
import ClovingGPT from '../cloving_gpt'
|
|
13
|
+
import type {
|
|
14
|
+
ClovingConfig,
|
|
15
|
+
ClovingGPTOptions,
|
|
16
|
+
ClovingModelConfig,
|
|
17
|
+
ClovingfileConfig,
|
|
18
|
+
ProjectConfig,
|
|
19
|
+
} from '../utils/types'
|
|
20
|
+
|
|
21
|
+
/** Path to the global Cloving configuration file */
|
|
22
|
+
export const CLOVING_CONFIG_PATH = path.join(os.homedir(), '.clovingconfig')
|
|
23
|
+
/** Name of the local Cloving configuration file */
|
|
24
|
+
export const CLOVINGFILE_PATH = 'cloving.json'
|
|
25
|
+
/** Path to the Cloving projects directory */
|
|
26
|
+
export const CLOVINGPROJECT_PATH = '.clovingprojects'
|
|
27
|
+
|
|
28
|
+
/** Cached Cloving configuration */
|
|
29
|
+
let clovingConfig: ClovingfileConfig
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Retrieves the Cloving configuration
|
|
33
|
+
* @param {ClovingGPTOptions} options - Options for ClovingGPT
|
|
34
|
+
* @returns {ClovingConfig} The Cloving configuration
|
|
35
|
+
*/
|
|
36
|
+
export const getConfig = (options: ClovingGPTOptions): ClovingConfig => {
|
|
37
|
+
try {
|
|
38
|
+
if (fs.existsSync(CLOVING_CONFIG_PATH)) {
|
|
39
|
+
const rawConfig = fs.readFileSync(CLOVING_CONFIG_PATH, 'utf-8')
|
|
40
|
+
const config = ini.parse(rawConfig) as ClovingConfig
|
|
41
|
+
if (options.silent) {
|
|
42
|
+
config.globalSilent = true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Parse temperature to float if it exists in the config
|
|
46
|
+
for (const provider in config.models) {
|
|
47
|
+
for (const model in config.models[provider]) {
|
|
48
|
+
if (config.models[provider][model].temperature !== undefined) {
|
|
49
|
+
const temp = config.models[provider][model].temperature
|
|
50
|
+
config.models[provider][model].temperature =
|
|
51
|
+
typeof temp === 'string' ? parseFloat(temp) : temp
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return config
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('Error reading configuration:', err)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fallback to environment variables if config file doesn't exist
|
|
63
|
+
if (process.env.CLOVING_MODEL) {
|
|
64
|
+
const [provider, ...modelParts] = process.env.CLOVING_MODEL.split(':')
|
|
65
|
+
const model = modelParts.join(':')
|
|
66
|
+
return {
|
|
67
|
+
models: {
|
|
68
|
+
[provider]: {
|
|
69
|
+
[model]: {
|
|
70
|
+
apiKey: process.env.CLOVING_API_KEY ?? '',
|
|
71
|
+
primary: true,
|
|
72
|
+
priority: 10,
|
|
73
|
+
silent: options.silent || false,
|
|
74
|
+
trust: false,
|
|
75
|
+
temperature: options.temperature || 0.2,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
globalSilent: options.silent || false,
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
return { models: {}, globalSilent: options.silent || false }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Retrieves the local Cloving configuration
|
|
88
|
+
* @returns {ClovingfileConfig} The local Cloving configuration
|
|
89
|
+
* @throws {Error} If the cloving.json file is not found
|
|
90
|
+
*/
|
|
91
|
+
export const getClovingConfig = (): ClovingfileConfig => {
|
|
92
|
+
if (clovingConfig) {
|
|
93
|
+
return clovingConfig
|
|
94
|
+
} else if (fs.existsSync(CLOVINGFILE_PATH)) {
|
|
95
|
+
clovingConfig = getClovingConfigAtPath(CLOVINGFILE_PATH)
|
|
96
|
+
return clovingConfig
|
|
97
|
+
} else {
|
|
98
|
+
console.log(`${CLOVINGFILE_PATH} file not found, please run: cloving init`)
|
|
99
|
+
process.exit(1)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const getClovingConfigAtPath = (fullPath: string): ClovingfileConfig => {
|
|
104
|
+
const configFile = fs.readFileSync(fullPath, 'utf-8')
|
|
105
|
+
return JSON.parse(configFile) as ClovingfileConfig
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const getNearestClovingConfig = (): { config: ClovingfileConfig; path: string } => {
|
|
109
|
+
let runPath = process.cwd()
|
|
110
|
+
const parts = runPath.split(path.sep)
|
|
111
|
+
while (parts.length > 0) {
|
|
112
|
+
const configPath = path.join(parts.join(path.sep), CLOVINGFILE_PATH)
|
|
113
|
+
if (fs.existsSync(configPath)) {
|
|
114
|
+
clovingConfig = getClovingConfigAtPath(configPath)
|
|
115
|
+
return { config: clovingConfig, path: configPath }
|
|
116
|
+
}
|
|
117
|
+
parts.pop()
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`No ${CLOVINGFILE_PATH} file found in ${runPath} or any parent directory`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Saves the Cloving configuration to the config file
|
|
124
|
+
* @param {ClovingConfig} config - The configuration to save
|
|
125
|
+
*/
|
|
126
|
+
export const saveConfig = (config: ClovingConfig): void => {
|
|
127
|
+
const iniString = ini.stringify(config)
|
|
128
|
+
fs.writeFileSync(CLOVING_CONFIG_PATH, iniString)
|
|
129
|
+
console.log(`Configuration saved to ${CLOVING_CONFIG_PATH}`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Retrieves the primary model configuration
|
|
134
|
+
* @param {string} [partialModel] - Partial model name to filter by
|
|
135
|
+
* @returns {Object | null} The primary model configuration or null if not found
|
|
136
|
+
*/
|
|
137
|
+
export const getPrimaryModel = (
|
|
138
|
+
partialModel?: string,
|
|
139
|
+
): { provider: string; model: string; config: ClovingModelConfig } | null => {
|
|
140
|
+
try {
|
|
141
|
+
const config = getConfig({})
|
|
142
|
+
const models: { provider: string; model: string; config: ClovingModelConfig }[] = []
|
|
143
|
+
|
|
144
|
+
for (const provider in config.models) {
|
|
145
|
+
for (const model in config.models[provider]) {
|
|
146
|
+
if (!partialModel || `${provider}:${model}`.startsWith(partialModel)) {
|
|
147
|
+
models.push({
|
|
148
|
+
provider,
|
|
149
|
+
model,
|
|
150
|
+
config: config.models[provider][model],
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
models.sort(
|
|
157
|
+
(a, b) => parseInt(`${b.config.priority}`, 10) - parseInt(`${a.config.priority}`, 10),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return models.length > 0 ? models[0] : null
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error('Error reading model from configuration:', err)
|
|
163
|
+
return null
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Retrieves the configuration for a specific project
|
|
169
|
+
* @param {string} name - The name of the project
|
|
170
|
+
* @returns {ProjectConfig} The project configuration
|
|
171
|
+
* @throws {Error} If the project configuration file is not found
|
|
172
|
+
*/
|
|
173
|
+
export const getProjectConfig = (name: string): ProjectConfig => {
|
|
174
|
+
const projectConfigPath = path.join(CLOVINGPROJECT_PATH, name, 'config.ini')
|
|
175
|
+
if (fs.existsSync(projectConfigPath)) {
|
|
176
|
+
const configFile = fs.readFileSync(projectConfigPath, 'utf-8')
|
|
177
|
+
return ini.parse(configFile) as ProjectConfig
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error(`${projectConfigPath} file not found`)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Saves the configuration for a specific project
|
|
185
|
+
* @param {string} name - The name of the project
|
|
186
|
+
* @param {ProjectConfig} config - The project configuration to save
|
|
187
|
+
*/
|
|
188
|
+
export const saveProjectConfig = (name: string, config: ProjectConfig): void => {
|
|
189
|
+
const projectConfigPath = path.join(CLOVINGPROJECT_PATH, name, 'config.ini')
|
|
190
|
+
const projectConfigDir = path.dirname(projectConfigPath)
|
|
191
|
+
|
|
192
|
+
// Create directory if it doesn't exist
|
|
193
|
+
if (!fs.existsSync(projectConfigDir)) {
|
|
194
|
+
fs.mkdirSync(projectConfigDir, { recursive: true })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const iniString = ini.stringify(config)
|
|
198
|
+
fs.writeFileSync(projectConfigPath, iniString)
|
|
199
|
+
console.log(`Configuration saved to ${projectConfigPath}`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Removes the configuration file for a specific project
|
|
204
|
+
* @param {string} name - The name of the project
|
|
205
|
+
*/
|
|
206
|
+
export const removeProjectConfig = (name: string): void => {
|
|
207
|
+
const projectConfigPath = path.join(CLOVINGPROJECT_PATH, name, 'config.ini')
|
|
208
|
+
if (fs.existsSync(projectConfigPath)) {
|
|
209
|
+
fs.unlinkSync(projectConfigPath)
|
|
210
|
+
console.log(`Project completed and file removed: ${projectConfigPath}`)
|
|
211
|
+
} else {
|
|
212
|
+
console.error(`Configuration not found: ${projectConfigPath}`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const setupTestingPrompt = async (gpt: ClovingGPT, config: ClovingfileConfig) => {
|
|
217
|
+
const prompt = `I have the following cloving.json configuration file:
|
|
218
|
+
|
|
219
|
+
==== begin cloving.json ====
|
|
220
|
+
${JSON.stringify(config, null, 2)}
|
|
221
|
+
==== end cloving.json ====
|
|
222
|
+
|
|
223
|
+
Please provide instructions on how to set up testing for this project.`
|
|
224
|
+
|
|
225
|
+
const setupInstructions = await gpt.generateText({ prompt })
|
|
226
|
+
|
|
227
|
+
console.log(setupInstructions)
|
|
228
|
+
process.exit(0)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const getAllFiles = async (
|
|
232
|
+
options: ClovingGPTOptions,
|
|
233
|
+
forTesting: boolean,
|
|
234
|
+
): Promise<string[]> => {
|
|
235
|
+
const gpt = new ClovingGPT(options)
|
|
236
|
+
|
|
237
|
+
// Read the cloving.json file
|
|
238
|
+
const { config } = getNearestClovingConfig()
|
|
239
|
+
const testingDirectory = getTestingDirectory()
|
|
240
|
+
|
|
241
|
+
let allSrcFiles: string[] = []
|
|
242
|
+
|
|
243
|
+
if (forTesting && !testingDirectory) {
|
|
244
|
+
await setupTestingPrompt(gpt, config)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Collect srcFiles for each language with specified directory and extension
|
|
248
|
+
for (const language of config.languages) {
|
|
249
|
+
if (language.directory && language.extension) {
|
|
250
|
+
const srcFiles = execFileSync('find', [
|
|
251
|
+
language.directory,
|
|
252
|
+
'-type',
|
|
253
|
+
'f',
|
|
254
|
+
'-name',
|
|
255
|
+
`*${language.extension}`,
|
|
256
|
+
])
|
|
257
|
+
.toString()
|
|
258
|
+
.trim()
|
|
259
|
+
.split('\n')
|
|
260
|
+
allSrcFiles = allSrcFiles.concat(srcFiles)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Ensure allSrcFiles is unique
|
|
265
|
+
return Array.from(new Set(allSrcFiles))
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const getTestingDirectory = (): string | undefined => {
|
|
269
|
+
const config = getClovingConfig()
|
|
270
|
+
return config.testingFrameworks?.find((framework: any) => framework.directory)?.directory
|
|
271
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module git_utils
|
|
3
|
+
* @description Provides utility functions for Git operations and commit message generation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from 'child_process'
|
|
7
|
+
import readline from 'readline'
|
|
8
|
+
import fs from 'fs'
|
|
9
|
+
import path from 'path'
|
|
10
|
+
|
|
11
|
+
const COMMITLINT_CONFIG_FILES = [
|
|
12
|
+
'.commitlintrc',
|
|
13
|
+
'.commitlintrc.json',
|
|
14
|
+
'.commitlintrc.yaml',
|
|
15
|
+
'.commitlintrc.yml',
|
|
16
|
+
'.commitlintrc.js',
|
|
17
|
+
'.commitlintrc.cjs',
|
|
18
|
+
'.commitlintrc.mjs',
|
|
19
|
+
'.commitlintrc.ts',
|
|
20
|
+
'.commitlintrc.cts',
|
|
21
|
+
'commitlint.config.js',
|
|
22
|
+
'commitlint.config.cjs',
|
|
23
|
+
'commitlint.config.mjs',
|
|
24
|
+
'commitlint.config.ts',
|
|
25
|
+
'commitlint.config.cts',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Prompts the user with a question and returns their answer.
|
|
30
|
+
* @param {string} question - The question to ask the user.
|
|
31
|
+
* @returns {Promise<string>} A promise that resolves with the user's answer.
|
|
32
|
+
*/
|
|
33
|
+
const askQuestion = (question: string): Promise<string> => {
|
|
34
|
+
const rl = readline.createInterface({
|
|
35
|
+
input: process.stdin,
|
|
36
|
+
output: process.stdout,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return new Promise((resolve) =>
|
|
40
|
+
rl.question(question, (answer) => {
|
|
41
|
+
rl.close()
|
|
42
|
+
resolve(answer)
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Retrieves the Git diff between the current branch and the default branch.
|
|
49
|
+
* @returns {Promise<string>} A promise that resolves with the indented Git diff.
|
|
50
|
+
* @throws {Error} If there's an error getting the Git diff.
|
|
51
|
+
*/
|
|
52
|
+
export const getGitDiff = async (): Promise<string> => {
|
|
53
|
+
try {
|
|
54
|
+
const defaultBranchName = await getDefaultBranchName()
|
|
55
|
+
|
|
56
|
+
const gitDiff = execSync(`git diff ${defaultBranchName} --`).toString().trim()
|
|
57
|
+
|
|
58
|
+
// Indent each line by four spaces
|
|
59
|
+
const indentedDiff = gitDiff
|
|
60
|
+
.split('\n')
|
|
61
|
+
.map((line) => ` ${line}`)
|
|
62
|
+
.join('\n')
|
|
63
|
+
|
|
64
|
+
return indentedDiff
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error getting git diff:', (error as Error).message)
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Determines the default branch name for the Git repository.
|
|
73
|
+
* @returns {Promise<string>} A promise that resolves with the default branch name.
|
|
74
|
+
* @throws {Error} If the default branch cannot be determined.
|
|
75
|
+
*/
|
|
76
|
+
export const getDefaultBranchName = async (): Promise<string> => {
|
|
77
|
+
let defaultBranchName = execSync(
|
|
78
|
+
"git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'",
|
|
79
|
+
)
|
|
80
|
+
.toString()
|
|
81
|
+
.trim()
|
|
82
|
+
|
|
83
|
+
if (defaultBranchName === '') {
|
|
84
|
+
console.error('Could not determine the default branch.')
|
|
85
|
+
|
|
86
|
+
const answer = await askQuestion('Do you want to run `git remote set-head origin -a`? [Y/n] ')
|
|
87
|
+
|
|
88
|
+
if (answer.toLowerCase() === 'y' || answer === '') {
|
|
89
|
+
execSync('git remote set-head origin -a')
|
|
90
|
+
defaultBranchName = await getDefaultBranchName()
|
|
91
|
+
|
|
92
|
+
if (defaultBranchName === '') {
|
|
93
|
+
throw new Error('Error: Could not determine the default branch (main or master)')
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
throw new Error('Operation aborted by user.')
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return defaultBranchName
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Retrieves the name of the current Git branch.
|
|
105
|
+
* @returns {string} The name of the current Git branch.
|
|
106
|
+
* @throws {Error} If there's an error getting the current branch name.
|
|
107
|
+
*/
|
|
108
|
+
export const getCurrentBranchName = (): string => {
|
|
109
|
+
try {
|
|
110
|
+
return execSync('git rev-parse --abbrev-ref HEAD').toString().trim()
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Error getting current branch name:', (error as Error).message)
|
|
113
|
+
process.exit(1)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Checks if a commitlint configuration file exists in the project.
|
|
119
|
+
* @returns {string | null} The path to the commitlint config file if found, null otherwise.
|
|
120
|
+
*/
|
|
121
|
+
export const findCommitlintConfig = (): string | null => {
|
|
122
|
+
for (const configFile of COMMITLINT_CONFIG_FILES) {
|
|
123
|
+
if (fs.existsSync(configFile)) {
|
|
124
|
+
return configFile
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Reads and parses the commitlint configuration file.
|
|
132
|
+
* @param {string} configPath - Path to the commitlint config file.
|
|
133
|
+
* @returns {object | null} The parsed configuration object or null if unable to parse.
|
|
134
|
+
*/
|
|
135
|
+
export const readCommitlintConfig = (configPath: string): object | null => {
|
|
136
|
+
try {
|
|
137
|
+
const ext = path.extname(configPath)
|
|
138
|
+
|
|
139
|
+
if (['.js', '.cjs', '.mjs', '.ts', '.cts'].includes(ext)) {
|
|
140
|
+
// For JavaScript/TypeScript config files, we can't safely require them
|
|
141
|
+
// as they might have dependencies or complex configurations
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (['.json', '', '.yaml', '.yml'].includes(ext)) {
|
|
146
|
+
const content = fs.readFileSync(configPath, 'utf-8')
|
|
147
|
+
|
|
148
|
+
if (ext === '.json' || ext === '') {
|
|
149
|
+
return JSON.parse(content)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// For YAML files, we'll just return the raw content as a string
|
|
153
|
+
return { config: content }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Error reading commitlint config:', (error as Error).message)
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generates a prompt for creating a commit message based on a Git diff.
|
|
165
|
+
* @param {string} diff - The Git diff to base the commit message on.
|
|
166
|
+
* @returns {string} A prompt string for generating a commit message.
|
|
167
|
+
*/
|
|
168
|
+
export const generateCommitMessagePrompt = (diff: string): string => {
|
|
169
|
+
// Check if commitlint is configured
|
|
170
|
+
const commitlintConfig = findCommitlintConfig()
|
|
171
|
+
let configInfo = ''
|
|
172
|
+
|
|
173
|
+
if (commitlintConfig) {
|
|
174
|
+
const config = readCommitlintConfig(commitlintConfig)
|
|
175
|
+
if (config) {
|
|
176
|
+
configInfo = `\nFound commitlint configuration at ${commitlintConfig}:
|
|
177
|
+
\`\`\`json
|
|
178
|
+
${JSON.stringify(config, null, 2)}
|
|
179
|
+
\`\`\`
|
|
180
|
+
Please ensure the commit message follows any additional rules specified in this configuration.
|
|
181
|
+
`
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return `Generate a conventional commit message following the Conventional Commits specification (www.conventionalcommits.org).${configInfo}
|
|
186
|
+
|
|
187
|
+
The commit message should follow this format:
|
|
188
|
+
<type>[optional scope]: <description>
|
|
189
|
+
|
|
190
|
+
[optional body]
|
|
191
|
+
|
|
192
|
+
[optional footer(s)]
|
|
193
|
+
|
|
194
|
+
Types must be one of:
|
|
195
|
+
1. revert: Reverts a previous commit
|
|
196
|
+
2. feat: A new feature
|
|
197
|
+
3. fix: A bug fix
|
|
198
|
+
4. refactor: A code change that neither fixes a bug nor adds a feature
|
|
199
|
+
5. perf: A code change that improves performance
|
|
200
|
+
6. build: Changes that affect the build system or external dependencies or updates to versions of dependencies or version bumps
|
|
201
|
+
7. test: Adding missing tests or correcting existing tests
|
|
202
|
+
8. docs: Documentation only changes
|
|
203
|
+
9. style: Changes that do not affect the meaning of the code
|
|
204
|
+
10. ci: Changes to our CI configuration files and scripts
|
|
205
|
+
11. chore: Other changes that don't modify src or test files
|
|
206
|
+
|
|
207
|
+
Use the order of types as an order of precedence for the commit message if there are multiple types in one commit.
|
|
208
|
+
|
|
209
|
+
The scope should be the part of the codebase affected.
|
|
210
|
+
The description should be imperative, present tense, and not capitalized.
|
|
211
|
+
Breaking changes should be indicated by "!" before the ":" or by "BREAKING CHANGE:" in the footer.
|
|
212
|
+
|
|
213
|
+
Do not add any commentary or context to the message other than the commit message itself.
|
|
214
|
+
|
|
215
|
+
Here is an example of a valid conventional commit message:
|
|
216
|
+
|
|
217
|
+
\`\`\`plaintext
|
|
218
|
+
feat(api): add new endpoint for user authentication
|
|
219
|
+
|
|
220
|
+
- Implement JWT token generation
|
|
221
|
+
- Add password hashing with bcrypt
|
|
222
|
+
- Create user validation middleware
|
|
223
|
+
|
|
224
|
+
BREAKING CHANGE: 'auth' endpoint now requires API key in headers
|
|
225
|
+
\`\`\`
|
|
226
|
+
|
|
227
|
+
Here is the diff to help you write the commit message:
|
|
228
|
+
|
|
229
|
+
${diff}`
|
|
230
|
+
}
|