gennady 0.3.0 → 0.5.0
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 +95 -16
- package/cli/cmd/cat.js +26 -0
- package/cli/cmd/commit.js +83 -0
- package/cli/cmd/review.js +58 -0
- package/cli/gennady.js +19 -43
- package/index.js +2 -0
- package/package.json +5 -3
- package/src/ai/ai-core.js +187 -0
- package/src/cat-gen/cat-gen.js +53 -0
- package/src/commit-gen/commit-gen.js +30 -175
- package/src/git/git-core.js +69 -0
- package/src/git/git-diff.js +36 -13
- package/src/prompts/commit/commit-base-prompt.md +38 -0
- package/src/prompts/commit/commit-format-detailed-prompt.md +29 -0
- package/src/prompts/commit/commit-format-oneline-prompt.md +19 -0
- package/src/prompts/commit/commit-translate-prompt.md +15 -0
- package/src/prompts/index.js +10 -0
- package/src/prompts/review/review-base-prompt.md +46 -0
- package/src/review-gen/__fixture__/review-gen-fixture.md +110 -0
- package/src/review-gen/review-gen.js +119 -0
- package/src/review-gen/review-gen.test.js +79 -0
- package/src/review-gen/specs/js/Function.prototype.json +162 -0
- package/src/review-gen/specs/js/Global.json +219 -0
- package/src/review-gen/specs/js/JSON.json +85 -0
- package/src/review-gen/specs/js/Object.json +626 -0
- package/src/review-gen/specs/js/Object.prototype.json +337 -0
- package/src/review-gen/specs/js/Storage.json +97 -0
- package/src/utils/parse-args.js +4 -2
- package/src/utils/style.js +12 -3
- package/src/git/git-cmd.js +0 -32
- package/src/prompts/base-prompt.md +0 -27
- package/src/prompts/format-detailed-prompt.md +0 -16
- package/src/prompts/format-oneline-prompt.md +0 -12
- package/src/prompts/translate-prompt.md +0 -11
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { parseGitDiff } from '../git/git-diff.js';
|
|
1
|
+
import { AiCore } from '../ai/ai-core.js';
|
|
2
|
+
import { getGitDiffInfo } from '../git/git-core.js';
|
|
3
|
+
import { prompts } from '../prompts/index.js';
|
|
5
4
|
import { style } from '../utils/style.js';
|
|
6
5
|
|
|
7
|
-
const DEFAULT_MODEL = 'llama3:8b';
|
|
8
|
-
const DEFAULT_API_URL = 'http://127.0.0.1:11434/api/generate';
|
|
9
|
-
|
|
10
|
-
const GENNADY_RC_FILENAME = '.gennadyrc';
|
|
11
|
-
|
|
12
6
|
export class CommitGen {
|
|
13
|
-
api;
|
|
14
|
-
apiList = [];
|
|
15
|
-
|
|
16
7
|
constructor(init) {
|
|
17
8
|
this.init = {
|
|
18
9
|
mode: 'auto',
|
|
@@ -20,42 +11,20 @@ export class CommitGen {
|
|
|
20
11
|
targetBranch: undefined,
|
|
21
12
|
|
|
22
13
|
logger: console,
|
|
23
|
-
maxInputTokens: init.maxInputTokens || 4000,
|
|
24
14
|
|
|
25
|
-
basePromptTemplate:
|
|
26
|
-
formatOnelinePromptTemplate:
|
|
27
|
-
formatDetailedPromptTemplate:
|
|
28
|
-
translatePromptTemplate:
|
|
15
|
+
basePromptTemplate: prompts.commit('base'),
|
|
16
|
+
formatOnelinePromptTemplate: prompts.commit('format-oneline'),
|
|
17
|
+
formatDetailedPromptTemplate: prompts.commit('format-detailed'),
|
|
18
|
+
translatePromptTemplate: prompts.commit('translate'),
|
|
29
19
|
|
|
30
20
|
timeout: 120,
|
|
31
21
|
|
|
32
22
|
...init,
|
|
33
23
|
};
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
join(process.env.HOME, GENNADY_RC_FILENAME),
|
|
39
|
-
].find((file) => {
|
|
40
|
-
try {
|
|
41
|
-
if (existsSync(file)) {
|
|
42
|
-
const items = JSON.parse(readFileSync(file).toString());
|
|
43
|
-
if (Array.isArray(items)) {
|
|
44
|
-
this.apiList.push(...items);
|
|
45
|
-
} else {
|
|
46
|
-
this.logger.warn(`Invalid "${file}" config:`, items);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
} catch (err) {
|
|
50
|
-
this.logger.error(`Parse "${file}" error:`, err);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Default API
|
|
55
|
-
this.apiList[this.init.apiUrl ? 'unshift' : 'push']({
|
|
56
|
-
url: this.init.apiUrl || DEFAULT_API_URL,
|
|
57
|
-
key: this.init.apiKey,
|
|
58
|
-
model: this.init.model || DEFAULT_MODEL,
|
|
25
|
+
this.ai = new AiCore({
|
|
26
|
+
logger: this.logger,
|
|
27
|
+
timeout: this.init.timeout,
|
|
59
28
|
});
|
|
60
29
|
}
|
|
61
30
|
|
|
@@ -68,168 +37,53 @@ export class CommitGen {
|
|
|
68
37
|
}
|
|
69
38
|
|
|
70
39
|
get model() {
|
|
71
|
-
return this.
|
|
40
|
+
return this.ai.model
|
|
72
41
|
}
|
|
73
42
|
|
|
74
43
|
get apiUrl() {
|
|
75
|
-
return this.
|
|
44
|
+
return this.ai.apiUrl;
|
|
76
45
|
}
|
|
77
46
|
|
|
78
47
|
get targetBranch() {
|
|
79
48
|
return this.init.targetBranch;
|
|
80
49
|
}
|
|
81
50
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.join('\n\n');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async getApi() {
|
|
89
|
-
if (!this.api) {
|
|
90
|
-
// By default
|
|
91
|
-
this.api = {url: DEFAULT_API_URL, model: DEFAULT_MODEL};
|
|
92
|
-
|
|
93
|
-
for (const api of this.apiList) {
|
|
94
|
-
try {
|
|
95
|
-
const resp = await fetch(api.url, {method: 'HEAD', timeout: 1000});
|
|
96
|
-
if (resp.status >= 200 && resp.status < 500) {
|
|
97
|
-
this.api = api;
|
|
98
|
-
return api;
|
|
99
|
-
}
|
|
100
|
-
} catch {}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return this.api;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async fetchPrompt(prompt, context) {
|
|
108
|
-
try {
|
|
109
|
-
const api = await this.getApi();
|
|
110
|
-
if (api.url.includes('completions')) {
|
|
111
|
-
return await this.callCompletionsApi(api, prompt, context);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return await this.callGenerateApi(api, prompt, context);
|
|
115
|
-
} catch (e) {
|
|
116
|
-
this.logger.error(`Failed to generate LLM response:`, e);
|
|
117
|
-
return '';
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async callGenerateApi(api, prompt, context) {
|
|
122
|
-
const req = await fetch(api.url, {
|
|
123
|
-
method: 'POST',
|
|
124
|
-
headers: {'Content-Type': 'application/json'},
|
|
125
|
-
body: JSON.stringify({
|
|
126
|
-
model: api.model,
|
|
127
|
-
stream: false,
|
|
128
|
-
prompt,
|
|
129
|
-
context,
|
|
130
|
-
}),
|
|
131
|
-
});
|
|
132
|
-
const data = await req.json();
|
|
133
|
-
return data.response || '';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async callCompletionsApi(api, prompt, context) {
|
|
137
|
-
const messages = [];
|
|
138
|
-
|
|
139
|
-
if (context) {
|
|
140
|
-
messages.push({ role: 'system', content: context });
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
messages.push({ role: 'user', content: prompt });
|
|
144
|
-
|
|
145
|
-
const req = await fetch(api.url, {
|
|
146
|
-
method: 'POST',
|
|
147
|
-
headers: {
|
|
148
|
-
'Content-Type': 'application/json',
|
|
149
|
-
'Authorization': `Bearer ${api.key}`,
|
|
150
|
-
},
|
|
151
|
-
body: JSON.stringify({
|
|
152
|
-
model: api.model,
|
|
153
|
-
messages: messages,
|
|
154
|
-
temperature: 0.1,
|
|
155
|
-
stream: false,
|
|
156
|
-
timeout: this.init.timeout,
|
|
157
|
-
}),
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
if (!req.ok) {
|
|
161
|
-
let errorBody = '';
|
|
162
|
-
try {
|
|
163
|
-
errorBody = await req.text();
|
|
164
|
-
} catch (e) { /* ignore */ }
|
|
165
|
-
|
|
166
|
-
throw new Error(`LLM completions request failed with status ${req.status}: ${errorBody}`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const data = await req.json();
|
|
170
|
-
|
|
171
|
-
if (data.choices && data.choices.length > 0 && data.choices[0].message) {
|
|
172
|
-
return data.choices[0].message.content || '';
|
|
173
|
-
} else {
|
|
174
|
-
this.logger.warn(style.yellow('LLM completions response structure unexpected:'), data);
|
|
175
|
-
return '';
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async generateCommitMessage(prompt) {
|
|
180
|
-
const text = await this.fetchPrompt(prompt);
|
|
181
|
-
return text;
|
|
51
|
+
async fetchPrompt(input) {
|
|
52
|
+
const output = await this.ai.generate(input);
|
|
53
|
+
return output;
|
|
182
54
|
}
|
|
183
55
|
|
|
184
56
|
async generate() {
|
|
185
|
-
const {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
57
|
+
const {
|
|
58
|
+
commitCount,
|
|
59
|
+
parsedCodeDiff,
|
|
60
|
+
parsedCodeTokens,
|
|
61
|
+
parsedCodeChunkMaxTokens,
|
|
62
|
+
programmingLanguages,
|
|
63
|
+
} = getGitDiffInfo(this.init.targetBranch);
|
|
190
64
|
|
|
191
|
-
if (
|
|
65
|
+
if (parsedCodeDiff.length === 0) {
|
|
192
66
|
this.logger.warn(`No changes detected, skipping commit message generation.`);
|
|
67
|
+
this.logger.info(style.italic.gray(`Hint: git add .`));
|
|
193
68
|
return;
|
|
194
69
|
}
|
|
195
70
|
|
|
196
|
-
const tokens = codeChanged.reduce((sum, file) => sum + file.tokens, 0);
|
|
197
|
-
const maxTokens = codeChanged[codeChanged.length - 1].tokens;
|
|
198
|
-
const languages = [...new Set(codeChanged.map(f => f.programmingLanguage).filter(Boolean))].join(', ');
|
|
199
71
|
const mode =
|
|
200
72
|
this.init.oneline
|
|
201
73
|
? 'oneline'
|
|
202
74
|
: this.init.mode === 'auto'
|
|
203
75
|
? this.init.targetBranch
|
|
204
76
|
? 'detailed'
|
|
205
|
-
: commitCount > 1 &&
|
|
77
|
+
: commitCount > 1 && parsedCodeDiff.length < 5
|
|
206
78
|
? 'oneline'
|
|
207
79
|
: 'detailed'
|
|
208
80
|
: this.init.mode;
|
|
209
81
|
|
|
210
82
|
this.logger.info(`- Mode: ${style.bold.magentaBright(mode)}`);
|
|
211
|
-
this.logger.info(`- Languages: ${style.yellow(
|
|
212
|
-
this.logger.info(`- Tokens: ${style.bold.cyanBright(
|
|
83
|
+
this.logger.info(`- Languages: ${style.yellow(programmingLanguages)}`);
|
|
84
|
+
this.logger.info(`- Tokens: ${style.bold.cyanBright(parsedCodeTokens)} ${style.gray(`(max per file: ${parsedCodeChunkMaxTokens})`)}`);
|
|
213
85
|
|
|
214
|
-
|
|
215
|
-
this.logger.error(`TODO: Diff is too large, skipping commit message generation.`);
|
|
216
|
-
process.exit(1);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const batches = codeChanged.reduce((acc, file) => {
|
|
220
|
-
if (!acc[0] || acc[0].tokens + file.tokens > maxInputTokens) {
|
|
221
|
-
acc.unshift({tokens: 0, diff: '', languages: []});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
acc[0].tokens += file.tokens;
|
|
225
|
-
acc[0].diff += `File: ${file.filename}\n${file.diff.hunks.flatMap(h => h.changes).join('\n')}\n\n`;
|
|
226
|
-
|
|
227
|
-
if (!acc[0].languages.includes(file.programmingLanguage)) {
|
|
228
|
-
acc[0].languages.push(file.programmingLanguage);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return acc;
|
|
232
|
-
}, []);
|
|
86
|
+
const batches = this.ai.createPromptsBatchesByDiff(parsedCodeDiff);
|
|
233
87
|
|
|
234
88
|
this.logger.info(`- Queue: ${style.bold.cyan(batches.length)}`);
|
|
235
89
|
this.logger.info(`-`.repeat(40));
|
|
@@ -242,7 +96,7 @@ export class CommitGen {
|
|
|
242
96
|
.replaceAll('{languages}', batch.languages.join('/'))
|
|
243
97
|
.replaceAll('{input}', batch.diff);
|
|
244
98
|
|
|
245
|
-
const msg = await this.
|
|
99
|
+
const msg = await this.fetchPrompt(prompt);
|
|
246
100
|
|
|
247
101
|
return msg
|
|
248
102
|
}));
|
|
@@ -255,6 +109,7 @@ export class CommitGen {
|
|
|
255
109
|
mode === 'detailed' ? this.init.formatDetailedPromptTemplate : this.init.formatOnelinePromptTemplate,
|
|
256
110
|
results.join('\n\n'),
|
|
257
111
|
);
|
|
112
|
+
|
|
258
113
|
this.logger.info(`- Formatting time: ${style.blueBright(((performance.now() - startFormatTime) / 1000).toFixed(2))}s`);
|
|
259
114
|
|
|
260
115
|
return formatted.trim();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execSync as nodeExecSync } from 'node:child_process';
|
|
2
|
+
import { parseGitDiff } from './git-diff.js';
|
|
3
|
+
|
|
4
|
+
const execSync = (cmd) => {
|
|
5
|
+
try {
|
|
6
|
+
return nodeExecSync(cmd, { encoding: 'utf-8'});
|
|
7
|
+
} catch (e) {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const detectGitBaseBranch = () => {
|
|
13
|
+
const branchesOutput = execSync('git branch --list 2>/dev/null');
|
|
14
|
+
const match = branchesOutput?.match(/\s*\*?\s*(master|main)$/m);
|
|
15
|
+
return match?.[1] || 'master';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const getGitCommitCount = () => {
|
|
19
|
+
try {
|
|
20
|
+
const output = execSync(`git rev-list --count HEAD ^${detectGitBaseBranch()} 2>/dev/null`);
|
|
21
|
+
return parseInt(output, 10) || 0;
|
|
22
|
+
} catch {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const getGitDiff = (targetBranch = undefined) => {
|
|
28
|
+
if (targetBranch) {
|
|
29
|
+
return execSync(`git diff ${targetBranch}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return execSync('git diff HEAD');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const getGitDiffInfo = (branch = undefined) => {
|
|
36
|
+
const diff = getGitDiff(branch);
|
|
37
|
+
const parsedDiff = parseGitDiff(diff).sort((a, b) => a.tokens - b.tokens);
|
|
38
|
+
const parsedCodeDiff = parsedDiff.filter(f => (
|
|
39
|
+
!f.isDeleted &&
|
|
40
|
+
!f.isRenamed &&
|
|
41
|
+
!/\.(test|spec)s?\./.test(f.filename) &&
|
|
42
|
+
(
|
|
43
|
+
f.category === 'config' ||
|
|
44
|
+
f.programmingLanguage
|
|
45
|
+
)
|
|
46
|
+
));
|
|
47
|
+
|
|
48
|
+
// Если ничего нет, то подмешиваем документацию
|
|
49
|
+
if (!parsedCodeDiff.length) {
|
|
50
|
+
parsedCodeDiff.push(
|
|
51
|
+
...parsedDiff.filter(f => !f.isDeleted && !f.isRenamed && f.category === 'doc')
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parsedCodeTokens = parsedCodeDiff.reduce((sum, file) => sum + file.tokens, 0);
|
|
56
|
+
const parsedCodeChunkMaxTokens = parsedCodeDiff.at(-1)?.tokens || 0;
|
|
57
|
+
|
|
58
|
+
const programmingLanguages = [...new Set(parsedCodeDiff.map(f => f.programmingLanguage).filter(Boolean))];
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
diff,
|
|
62
|
+
parsedDiff,
|
|
63
|
+
parsedCodeDiff,
|
|
64
|
+
parsedCodeTokens,
|
|
65
|
+
parsedCodeChunkMaxTokens,
|
|
66
|
+
programmingLanguages,
|
|
67
|
+
commitCount: getGitCommitCount(),
|
|
68
|
+
};
|
|
69
|
+
}
|
package/src/git/git-diff.js
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
import { getProgrammingLanguage } from "../utils/language.js";
|
|
2
2
|
import { countTokens } from "../utils/tokens.js";
|
|
3
3
|
|
|
4
|
+
const CONFIG_FILE_PATTERNS = [
|
|
5
|
+
'^package\\.json',
|
|
6
|
+
'^tsconfig\\.json',
|
|
7
|
+
'^babel\\.config\\.json',
|
|
8
|
+
'^\\.pnpmfile\\.cjs',
|
|
9
|
+
'^\\.yarnrc(?:\\.(?:yml|yaml))?',
|
|
10
|
+
'^go\\.(?:mod|sum)',
|
|
11
|
+
'^\\.env(?:\\.[\\w]+)?',
|
|
12
|
+
'^\\..+',
|
|
13
|
+
'.*\\.(?:yml|yaml|rc|ini|conf)'
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const LOCKFILE_PATTERNS = [
|
|
17
|
+
'^package-lock\\.json',
|
|
18
|
+
'^npm-shrinkwrap\\.json',
|
|
19
|
+
'^yarn(?:-lock)?\\.(?:yaml|yml|toml)',
|
|
20
|
+
'^pnpm-lock\\.yaml',
|
|
21
|
+
'^composer\\.lock',
|
|
22
|
+
'^podfile\\.lock',
|
|
23
|
+
'^go\\.sum',
|
|
24
|
+
'^gemfile\\.lock'
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const FILE_CATEGORY_REGEX = {
|
|
28
|
+
doc: /\.(md|markdown|txt|rst)$/i,
|
|
29
|
+
cfg: new RegExp(CONFIG_FILE_PATTERNS.join('|'), 'i'),
|
|
30
|
+
img: /\.(png|jpe?g|gif|svg|bmp|tiff|ico)$/i,
|
|
31
|
+
css: /\.(css|less|scss|sass|styl)$/i,
|
|
32
|
+
html: /\.(html?)$/i,
|
|
33
|
+
code: /\.(js|jsx|ts|tsx|java|py|c|cpp|cs|rb|php|go|swift|m|mm|kt)$/i,
|
|
34
|
+
bin: /^(exe|dll|so|bin)\b/i,
|
|
35
|
+
lock: new RegExp(LOCKFILE_PATTERNS.join('|'), 'i'),
|
|
36
|
+
json: /\.json$/i
|
|
37
|
+
};
|
|
38
|
+
|
|
4
39
|
const getCategory = (filename, metadata) => {
|
|
5
40
|
if (metadata && metadata.extra && Array.isArray(metadata.extra)) {
|
|
6
41
|
for (let line of metadata.extra) {
|
|
@@ -10,19 +45,7 @@ const getCategory = (filename, metadata) => {
|
|
|
10
45
|
}
|
|
11
46
|
}
|
|
12
47
|
|
|
13
|
-
const
|
|
14
|
-
bin: /^(exe|dll|so|bin)\b$/i,
|
|
15
|
-
lock: /^(package-lock\.json|yarn\.lock|npm-shrinkwrap\.json|composer\.lock|podfile\.lock|go\.sum|gemfile\.lock)$/i,
|
|
16
|
-
json: /\.json$/i,
|
|
17
|
-
doc: /\.(md|markdown|txt|rst)$/i,
|
|
18
|
-
cfg: /^(\..+|.*\.(yml|yaml|rc|ini|conf))$/i,
|
|
19
|
-
img: /\.(png|jpe?g|gif|svg|bmp|tiff|ico)$/i,
|
|
20
|
-
css: /\.(css|less|scss|sass|styl)$/i,
|
|
21
|
-
html: /\.(html?)$/i,
|
|
22
|
-
code: /\.(js|jsx|ts|tsx|java|py|c|cpp|cs|rb|php|go|swift|m|mm|kt)$/i,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const category = Object.entries(categories).find(
|
|
48
|
+
const category = Object.entries(FILE_CATEGORY_REGEX).find(
|
|
26
49
|
([, regexp]) => regexp.test(filename)
|
|
27
50
|
)?.[0] || 'other';
|
|
28
51
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
You are a {languages} expert tasked with writing a Git commit subject and description based on the provided git diff.
|
|
2
|
+
|
|
3
|
+
# Instructions:
|
|
4
|
+
## Subject:
|
|
5
|
+
- **MUST follow Conventional Commits format: `<type>: <subject> emoji`**.
|
|
6
|
+
- **Choose the best <type> (e.g., `feat`, `fix`, `refactor`, `chore`, `docs`, `style`, `test`, `perf`) based on the *main purpose* of the changes.**
|
|
7
|
+
- The `<subject>` should be concise yet informative.
|
|
8
|
+
- Do not describe changes for each file in the subject.
|
|
9
|
+
- Do not mention stylistic changes or fixed typos in the subject (unless the type is `style` or `chore`).
|
|
10
|
+
- The subject must provide enough context to understand the commit at a glance.
|
|
11
|
+
- End the entire subject line with an emoji instead of a period.
|
|
12
|
+
|
|
13
|
+
### Subject Examples:
|
|
14
|
+
- `feat: added user authentication endpoint 🚀`
|
|
15
|
+
- `fix: calculation error on invoice generation 🐛`
|
|
16
|
+
- `refactor: simplified internal API calls ✨`
|
|
17
|
+
- `docs: updated setup instructions 📝`
|
|
18
|
+
- `chore: configured linting rules ⚙️`
|
|
19
|
+
|
|
20
|
+
## Description:
|
|
21
|
+
- Expand on the changes by listing key modifications as an unordered list.
|
|
22
|
+
- Do not describe changes for each file.
|
|
23
|
+
- Do not mention stylistic changes or fixed typos.
|
|
24
|
+
- Group related changes into single points.
|
|
25
|
+
- Each list item **must** be less than 140 characters and should not end with a period.
|
|
26
|
+
|
|
27
|
+
## Output Format (any deviation from this format is incorrect):**
|
|
28
|
+
<message><type>: <subject> emoji
|
|
29
|
+
- description item 1
|
|
30
|
+
- description item 2</message>
|
|
31
|
+
|
|
32
|
+
## Extremely important:
|
|
33
|
+
- The output must contain only commit message inside `<message>` and `</message>`.
|
|
34
|
+
- Do not add unnecessary words and markup, strictly follow the output format.
|
|
35
|
+
- The subject line MUST start with a valid Conventional Commit type followed by a colon and a space.
|
|
36
|
+
|
|
37
|
+
# Git diff:
|
|
38
|
+
{input}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
You are a {languages} expert tasked with writing a Git commit subject and description based on code changes.
|
|
2
|
+
|
|
3
|
+
# Instructions:
|
|
4
|
+
## Correct Output Format (no deviations allowed):
|
|
5
|
+
<message>
|
|
6
|
+
<type>: <subject> emoji
|
|
7
|
+
- description item 1
|
|
8
|
+
- description item 2
|
|
9
|
+
- description item N
|
|
10
|
+
</message>
|
|
11
|
+
|
|
12
|
+
## Subject:
|
|
13
|
+
- **MUST follow Conventional Commits format: `<type>: <subject> emoji`**.
|
|
14
|
+
- **Choose the best <type> (e.g., `feat`, `fix`, `refactor`, `chore`, `docs`, `style`, `test`, `perf`) based on the *main purpose* of the changes.**
|
|
15
|
+
- The `<subject>` should be concise yet informative, without loss of meaning.
|
|
16
|
+
- The commit subject (including `<type>: ` and emoji) must be no more than 72 characters.
|
|
17
|
+
|
|
18
|
+
## Description:
|
|
19
|
+
- Expand on the changes by listing key modifications as an unordered list.
|
|
20
|
+
- Each list item **must** be brief, informative, and less than 140 characters.
|
|
21
|
+
- Do not end description items with a period.
|
|
22
|
+
|
|
23
|
+
## Extremely Important:
|
|
24
|
+
- The output must contain *only* the commit message inside `<message>` and `</message>`.
|
|
25
|
+
- Do not include extra words, explanations, or markup beyond the specified format.
|
|
26
|
+
- End the commit subject with an emoji instead of a period.
|
|
27
|
+
|
|
28
|
+
# Input:
|
|
29
|
+
{input}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
You are a {languages} expert tasked with writing a Git commit message based on code changes.
|
|
2
|
+
|
|
3
|
+
# Instructions:
|
|
4
|
+
## Correct Output Format (no deviations allowed):
|
|
5
|
+
<message><type>: <subject> emoji</message>
|
|
6
|
+
|
|
7
|
+
## Message:
|
|
8
|
+
- **MUST follow Conventional Commits format: `<type>: <subject> emoji`**.
|
|
9
|
+
- **Choose the best <type> (e.g., `feat`, `fix`, `refactor`, `chore`, `docs`, `style`, `test`, `perf`) based on the *main purpose* of the changes.**
|
|
10
|
+
- The `<subject>` should be concise yet informative, without loss of meaning.
|
|
11
|
+
- The commit message should be no more than 72 characters.
|
|
12
|
+
|
|
13
|
+
## Extremely important:
|
|
14
|
+
- The output must contain only the commit message inside `<message>` and `</message>`.
|
|
15
|
+
- Do not add unnecessary words or markup; strictly follow the output format.
|
|
16
|
+
- End the commit message with an emoji instead of a period.
|
|
17
|
+
|
|
18
|
+
# Input:
|
|
19
|
+
{input}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
You are an expert in translating from English to **{lang}**.
|
|
2
|
+
|
|
3
|
+
# Instructions:
|
|
4
|
+
## Translate:
|
|
5
|
+
Translate text inside `<message>` and `</message>` tags from English to **{lang}**.
|
|
6
|
+
|
|
7
|
+
## Output format:
|
|
8
|
+
<message>Translation result</message>
|
|
9
|
+
|
|
10
|
+
## Extremely important:
|
|
11
|
+
- The translation must be wrapped inside `<message>` and `</message>` tags.
|
|
12
|
+
- Do not add unnecessary words and markup, strictly follow the output format.
|
|
13
|
+
|
|
14
|
+
# Input for translate to **{lang}**:
|
|
15
|
+
{input}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
|
|
5
|
+
const PROMPTS_DIR = typeof __dirname !== 'string' ? dirname(fileURLToPath(import.meta.url)) : __dirname;
|
|
6
|
+
|
|
7
|
+
export const prompts = {
|
|
8
|
+
commit: (name) => readFileSync(join(PROMPTS_DIR, `commit`, `commit-${name}-prompt.md`)).toString(),
|
|
9
|
+
review: (name) => readFileSync(join(PROMPTS_DIR, `review`, `review-${name}-prompt.md`)).toString(),
|
|
10
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
You are a meticulous Code Review Bot focused on identifying **critical errors** in {LANGUAGES} code changes. Your primary goal is to ensure the modified code is **functionally correct, safe, and free of obvious bugs** based *only* on the provided git diff. You must avoid subjective opinions or suggestions for alternative approaches if the code works as intended.
|
|
2
|
+
|
|
3
|
+
# Input:
|
|
4
|
+
{INPUT}
|
|
5
|
+
|
|
6
|
+
# Task:
|
|
7
|
+
1. Analyze **ONLY** the lines starting with `+` lines in the git diff. Ignore surrounding code unless it's directly impacted by the change causing an error.
|
|
8
|
+
2. Identify **only critical issues** based on the definition below.
|
|
9
|
+
3. Provide concise feedback in two sections: `Issues` and `Suggestions`.
|
|
10
|
+
4. If **no critical issues** are found in the changes, output **ONLY one token `GOOD`**.
|
|
11
|
+
|
|
12
|
+
# Definition of a "Critical Issue":
|
|
13
|
+
Focus **exclusively** on:
|
|
14
|
+
- **Logic Errors:** Code produces obviously incorrect results based on the diff.
|
|
15
|
+
- **Runtime Errors:** Code is highly likely to crash (e.g., `null` access, unhandled exceptions on external input).
|
|
16
|
+
- **Security Vulnerabilities:** This includes:
|
|
17
|
+
- Obvious risks like XSS, SQL Injection, hardcoded secrets.
|
|
18
|
+
- **Logging Sensitive Data:** Check any operation that outputs data (to logs, console, files, etc.). If the **name** of a variable or data field being outputted **contains** (case-insensitive) substrings like `'password'`, `'token'`, `'secret'`, `'apiKey'`, or `'credential'`, report this as a critical issue. **If the names being outputted do NOT contain these specific substrings, DO NOT report a logging-related security issue.**
|
|
19
|
+
|
|
20
|
+
**DO NOT Report:**
|
|
21
|
+
- Stylistic preferences (formatting, naming conventions, etc.).
|
|
22
|
+
- Suggestions for using different libraries or frameworks if the current code is functional.
|
|
23
|
+
- Minor performance optimizations unless the change introduces a *significant* and obvious bottleneck.
|
|
24
|
+
- Adding boilerplate (like input validation for simple internal functions) unless its absence *directly* leads to an error identified above based on the diff's context.
|
|
25
|
+
- Suggestions for refactoring code *outside* the direct changes shown in the diff.
|
|
26
|
+
- Comments like `TODO` or similar notes indicating planned work; these are not code errors.
|
|
27
|
+
|
|
28
|
+
{EXTRA_RULES}
|
|
29
|
+
|
|
30
|
+
# Output Format:
|
|
31
|
+
|
|
32
|
+
## If issues are found:
|
|
33
|
+
|
|
34
|
+
### Issues
|
|
35
|
+
For each hunk with critical issues:
|
|
36
|
+
**<file_path>#L<start>-<end>**
|
|
37
|
+
1. <Concise description of the **critical issue**>
|
|
38
|
+
- Hint: <Brief explanation of **why** it's a critical issue>
|
|
39
|
+
2. <Description of another **critical issue**>
|
|
40
|
+
- Hint: <Explanation>
|
|
41
|
+
|
|
42
|
+
### Suggestions
|
|
43
|
+
For each hunk listed in Issues:
|
|
44
|
+
**<file_path>#L<start>-<end>**
|
|
45
|
+
```suggestion
|
|
46
|
+
<Provide a **complete, corrected code snippet** that should replace the original code block corresponding to the **lines indicated by the hunk header (@@ ... @@)**, typically covering the range L<start>-<end>. Apply the **minimal modifications** to resolve **only** the critical issues identified above. Ensure the resulting snippet is functional and internally consistent. The snippet should represent the final state of the entire code block from the hunk after applying the fix.>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
### parsedCodeMaxTokens rename to parsedCodeChunkMaxTokens (OK)
|
|
2
|
+
|
|
3
|
+
#### Diff
|
|
4
|
+
```diff
|
|
5
|
+
### File **src/git/git-core.js**:
|
|
6
|
+
@@ -39,7 +39,7 @@ export const getGitDiffInfo = (branch = undefined) => {
|
|
7
|
+
const commitCount = getGitCommitCount();
|
|
8
|
+
|
|
9
|
+
const parsedCodeTokens = parsedCodeDiff.reduce((sum, file) => sum + file.tokens, 0);
|
|
10
|
+
- const parsedCodeMaxTokens = parsedCodeDiff.at(-1)?.tokens || 0;
|
|
11
|
+
+ const parsedCodeChunkMaxTokens = parsedCodeDiff.at(-1)?.tokens || 0;
|
|
12
|
+
const programmingLanguages = [...new Set(parsedCodeDiff.map(f => f.programmingLanguage).filter(Boolean))];
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
@@ -47,7 +47,7 @@ export const getGitDiffInfo = (branch = undefined) => {
|
|
16
|
+
parsedDiff,
|
|
17
|
+
parsedCodeDiff,
|
|
18
|
+
parsedCodeTokens,
|
|
19
|
+
- parsedCodeMaxTokens,
|
|
20
|
+
+ parsedCodeChunkMaxTokens,
|
|
21
|
+
programmingLanguages,
|
|
22
|
+
commitCount,
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
#### Expected
|
|
27
|
+
- GOOD
|
|
28
|
+
|
|
29
|
+
----
|
|
30
|
+
|
|
31
|
+
### Добавление функции (no issues и suggestions)
|
|
32
|
+
|
|
33
|
+
#### Diff
|
|
34
|
+
```diff
|
|
35
|
+
--- /dev/null
|
|
36
|
+
+++ b/src/utils/logger.ts
|
|
37
|
+
@@ -0,0 +1,3 @@
|
|
38
|
+
+function logUserAction(userId: string, action: string): void {
|
|
39
|
+
+ console.info(`User ${userId} performed ${action}`);
|
|
40
|
+
+}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### Expected
|
|
44
|
+
- GOOD
|
|
45
|
+
|
|
46
|
+
----
|
|
47
|
+
|
|
48
|
+
### Sensitivity data (token)
|
|
49
|
+
|
|
50
|
+
#### Diff
|
|
51
|
+
```diff
|
|
52
|
+
--- a/src/utils/logger.ts
|
|
53
|
+
+++ b/src/utils/logger.ts
|
|
54
|
+
@@ -1,3 +1,3 @@
|
|
55
|
+
-function logUserAction(userId: string, action: string) {
|
|
56
|
+
- console.info(`User ${userId} performed ${action}`);
|
|
57
|
+
+function logUserAction(userId: string, action: string, token: string) {
|
|
58
|
+
+ console.info(`User ${userId} performed ${action} (token: ${token})`);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Expected
|
|
63
|
+
- !(console.+token)
|
|
64
|
+
|
|
65
|
+
----
|
|
66
|
+
|
|
67
|
+
### JSON.parse
|
|
68
|
+
|
|
69
|
+
#### Diff
|
|
70
|
+
```diff
|
|
71
|
+
--- /dev/null
|
|
72
|
+
+++ b/src/utils/parser.js
|
|
73
|
+
@@ -0,0 +1,3 @@
|
|
74
|
+
+function parseUserData(raw) {
|
|
75
|
+
+ return JSON.parse(raw);
|
|
76
|
+
+}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Expected
|
|
80
|
+
- try
|
|
81
|
+
- catch
|
|
82
|
+
- console
|
|
83
|
+
|
|
84
|
+
----
|
|
85
|
+
|
|
86
|
+
### Sensitivity data after JSON.parse
|
|
87
|
+
|
|
88
|
+
#### Diff
|
|
89
|
+
```diff
|
|
90
|
+
--- /dev/null
|
|
91
|
+
+++ b/src/parser.js
|
|
92
|
+
@@ -0,0 +1,9 @@
|
|
93
|
+
+function parseUser(raw) {
|
|
94
|
+
+ try {
|
|
95
|
+
+ const user = JSON.parse(raw);
|
|
96
|
+
+ console.log(`User data: id=${user.id}, token=${user.token}`);
|
|
97
|
+
+ return user;
|
|
98
|
+
+ } catch (e) {
|
|
99
|
+
+ // TODO: Log parsing error
|
|
100
|
+
+ return null;
|
|
101
|
+
+ }
|
|
102
|
+
+}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Expected
|
|
106
|
+
- try
|
|
107
|
+
- catch
|
|
108
|
+
- console
|
|
109
|
+
|
|
110
|
+
----
|