gennady 0.2.2 → 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 +107 -10
  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 -40
  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 +38 -71
  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 +7 -3
  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 -11
  34. package/src/prompts/translate-prompt.md +0 -11
@@ -1,5 +1,6 @@
1
- import { getGitCommitCount, getGitDiff } from '../git/git-cmd.js';
2
- 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';
3
4
  import { style } from '../utils/style.js';
4
5
 
5
6
  export class CommitGen {
@@ -7,20 +8,24 @@ export class CommitGen {
7
8
  this.init = {
8
9
  mode: 'auto',
9
10
  oneline: false,
10
- reviewerModel: 'llama3:8b',
11
11
  targetBranch: undefined,
12
12
 
13
- maxInputTokens: init.maxInputTokens || 4000,
14
- ollamaUrl: init.ollamaUrl || 'http://127.0.0.1:11434/api/generate',
15
13
  logger: console,
16
14
 
17
- basePromptTemplate: undefined,
18
- formatOnelinePromptTemplate: undefined,
19
- formatDetailedPromptTemplate: undefined,
20
- translatePromptTemplate: undefined,
15
+ basePromptTemplate: prompts.commit('base'),
16
+ formatOnelinePromptTemplate: prompts.commit('format-oneline'),
17
+ formatDetailedPromptTemplate: prompts.commit('format-detailed'),
18
+ translatePromptTemplate: prompts.commit('translate'),
19
+
20
+ timeout: 120,
21
21
 
22
22
  ...init,
23
23
  };
24
+
25
+ this.ai = new AiCore({
26
+ logger: this.logger,
27
+ timeout: this.init.timeout,
28
+ });
24
29
  }
25
30
 
26
31
  get logger() {
@@ -31,93 +36,54 @@ export class CommitGen {
31
36
  return this.init.mode;
32
37
  }
33
38
 
34
- get reviewerModel() {
35
- return this.init.reviewerModel;
39
+ get model() {
40
+ return this.ai.model
41
+ }
42
+
43
+ get apiUrl() {
44
+ return this.ai.apiUrl;
36
45
  }
37
46
 
38
47
  get targetBranch() {
39
48
  return this.init.targetBranch;
40
49
  }
41
50
 
42
- generateDiffText(files) {
43
- return files
44
- .map((file) => `File: ${file.filename}\n${file.diff.join('\n')}`)
45
- .join('\n\n');
46
- }
47
-
48
- async fetchPrompt(prompt, context) {
49
- try {
50
- const req = await fetch(this.init.ollamaUrl, {
51
- method: 'POST',
52
- headers: {'Content-Type': 'application/json'},
53
- body: JSON.stringify({
54
- model: this.init.reviewerModel,
55
- stream: false,
56
- prompt,
57
- context,
58
- }),
59
- });
60
- const data = await req.json();
61
- return data.response || '';
62
- } catch (e) {
63
- this.logger.error(`Failed to generate ollama response:`, e);
64
- return '';
65
- }
66
- }
67
- async generateCommitMessage(prompt) {
68
- const text = await this.fetchPrompt(prompt);
69
- return text;
51
+ async fetchPrompt(input) {
52
+ const output = await this.ai.generate(input);
53
+ return output;
70
54
  }
71
55
 
72
56
  async generate() {
73
- const {maxInputTokens} = this.init
74
- const commitCount = getGitCommitCount();
75
- const diffStr = getGitDiff(this.init.targetBranch);
76
- const parsedDiff = parseGitDiff(diffStr).sort((a, b) => a.tokens - b.tokens);
77
- 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);
78
64
 
79
- if (codeChanged.length === 0) {
65
+ if (parsedCodeDiff.length === 0) {
80
66
  this.logger.warn(`No changes detected, skipping commit message generation.`);
67
+ this.logger.info(style.italic.gray(`Hint: git add .`));
81
68
  return;
82
69
  }
83
70
 
84
- const tokens = codeChanged.reduce((sum, file) => sum + file.tokens, 0);
85
- const maxTokens = codeChanged[codeChanged.length - 1].tokens;
86
- const languages = [...new Set(codeChanged.map(f => f.programmingLanguage).filter(Boolean))].join(', ');
87
71
  const mode =
88
72
  this.init.oneline
89
73
  ? 'oneline'
90
74
  : this.init.mode === 'auto'
91
75
  ? this.init.targetBranch
92
76
  ? 'detailed'
93
- : commitCount > 1 && codeChanged.length < 5
77
+ : commitCount > 1 && parsedCodeDiff.length < 5
94
78
  ? 'oneline'
95
79
  : 'detailed'
96
80
  : this.init.mode;
97
81
 
98
82
  this.logger.info(`- Mode: ${style.bold.magentaBright(mode)}`);
99
- this.logger.info(`- Languages: ${style.yellow(languages)}`);
100
- this.logger.info(`- Tokens: ${style.bold.cyanBright(tokens)} ${style.gray(`(max per file: ${maxTokens})`)}`);
101
-
102
- if (maxTokens > maxInputTokens) {
103
- this.logger.error(`TODO: Diff is too large, skipping commit message generation.`);
104
- process.exit(1);
105
- }
83
+ this.logger.info(`- Languages: ${style.yellow(programmingLanguages)}`);
84
+ this.logger.info(`- Tokens: ${style.bold.cyanBright(parsedCodeTokens)} ${style.gray(`(max per file: ${parsedCodeChunkMaxTokens})`)}`);
106
85
 
107
- const batches = codeChanged.reduce((acc, file) => {
108
- if (!acc[0] || acc[0].tokens + file.tokens > maxInputTokens) {
109
- acc.unshift({tokens: 0, diff: '', languages: []});
110
- }
111
-
112
- acc[0].tokens += file.tokens;
113
- acc[0].diff += `File: ${file.filename}\n${file.diff.hunks.flatMap(h => h.changes).join('\n')}\n\n`;
114
-
115
- if (!acc[0].languages.includes(file.programmingLanguage)) {
116
- acc[0].languages.push(file.programmingLanguage);
117
- }
118
-
119
- return acc;
120
- }, []);
86
+ const batches = this.ai.createPromptsBatchesByDiff(parsedCodeDiff);
121
87
 
122
88
  this.logger.info(`- Queue: ${style.bold.cyan(batches.length)}`);
123
89
  this.logger.info(`-`.repeat(40));
@@ -130,7 +96,7 @@ export class CommitGen {
130
96
  .replaceAll('{languages}', batch.languages.join('/'))
131
97
  .replaceAll('{input}', batch.diff);
132
98
 
133
- const msg = await this.generateCommitMessage(prompt);
99
+ const msg = await this.fetchPrompt(prompt);
134
100
 
135
101
  return msg
136
102
  }));
@@ -143,6 +109,7 @@ export class CommitGen {
143
109
  mode === 'detailed' ? this.init.formatDetailedPromptTemplate : this.init.formatOnelinePromptTemplate,
144
110
  results.join('\n\n'),
145
111
  );
112
+
146
113
  this.logger.info(`- Formatting time: ${style.blueBright(((performance.now() - startFormatTime) / 1000).toFixed(2))}s`);
147
114
 
148
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
+ ----