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.
Files changed (34) hide show
  1. package/README.md +95 -16
  2. package/cli/cmd/cat.js +26 -0
  3. package/cli/cmd/commit.js +83 -0
  4. package/cli/cmd/review.js +58 -0
  5. package/cli/gennady.js +19 -43
  6. package/index.js +2 -0
  7. package/package.json +5 -3
  8. package/src/ai/ai-core.js +187 -0
  9. package/src/cat-gen/cat-gen.js +53 -0
  10. package/src/commit-gen/commit-gen.js +30 -175
  11. package/src/git/git-core.js +69 -0
  12. package/src/git/git-diff.js +36 -13
  13. package/src/prompts/commit/commit-base-prompt.md +38 -0
  14. package/src/prompts/commit/commit-format-detailed-prompt.md +29 -0
  15. package/src/prompts/commit/commit-format-oneline-prompt.md +19 -0
  16. package/src/prompts/commit/commit-translate-prompt.md +15 -0
  17. package/src/prompts/index.js +10 -0
  18. package/src/prompts/review/review-base-prompt.md +46 -0
  19. package/src/review-gen/__fixture__/review-gen-fixture.md +110 -0
  20. package/src/review-gen/review-gen.js +119 -0
  21. package/src/review-gen/review-gen.test.js +79 -0
  22. package/src/review-gen/specs/js/Function.prototype.json +162 -0
  23. package/src/review-gen/specs/js/Global.json +219 -0
  24. package/src/review-gen/specs/js/JSON.json +85 -0
  25. package/src/review-gen/specs/js/Object.json +626 -0
  26. package/src/review-gen/specs/js/Object.prototype.json +337 -0
  27. package/src/review-gen/specs/js/Storage.json +97 -0
  28. package/src/utils/parse-args.js +4 -2
  29. package/src/utils/style.js +12 -3
  30. package/src/git/git-cmd.js +0 -32
  31. package/src/prompts/base-prompt.md +0 -27
  32. package/src/prompts/format-detailed-prompt.md +0 -16
  33. package/src/prompts/format-oneline-prompt.md +0 -12
  34. package/src/prompts/translate-prompt.md +0 -11
@@ -1,18 +1,9 @@
1
- import { readFileSync, existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { getGitCommitCount, getGitDiff } from '../git/git-cmd.js';
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: undefined,
26
- formatOnelinePromptTemplate: undefined,
27
- formatDetailedPromptTemplate: undefined,
28
- translatePromptTemplate: undefined,
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
- // Try parse rc files
36
- [
37
- join(process.cwd(), GENNADY_RC_FILENAME),
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.api?.model || this.apiList[0].model;
40
+ return this.ai.model
72
41
  }
73
42
 
74
43
  get apiUrl() {
75
- return this.api?.url || this.apiList[0].url;
44
+ return this.ai.apiUrl;
76
45
  }
77
46
 
78
47
  get targetBranch() {
79
48
  return this.init.targetBranch;
80
49
  }
81
50
 
82
- generateDiffText(files) {
83
- return files
84
- .map((file) => `File: ${file.filename}\n${file.diff.join('\n')}`)
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 {maxInputTokens} = this.init
186
- const commitCount = getGitCommitCount();
187
- const diffStr = getGitDiff(this.init.targetBranch);
188
- const parsedDiff = parseGitDiff(diffStr).sort((a, b) => a.tokens - b.tokens);
189
- const codeChanged = parsedDiff.filter(f => !f.isDeleted && !f.isRenamed && f.programmingLanguage);
57
+ const {
58
+ commitCount,
59
+ parsedCodeDiff,
60
+ parsedCodeTokens,
61
+ parsedCodeChunkMaxTokens,
62
+ programmingLanguages,
63
+ } = getGitDiffInfo(this.init.targetBranch);
190
64
 
191
- if (codeChanged.length === 0) {
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 && codeChanged.length < 5
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(languages)}`);
212
- this.logger.info(`- Tokens: ${style.bold.cyanBright(tokens)} ${style.gray(`(max per file: ${maxTokens})`)}`);
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
- if (maxTokens > maxInputTokens) {
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.generateCommitMessage(prompt);
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
+ }
@@ -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 categories = {
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
+ ----