gennady 0.3.0 → 0.5.1-next.1
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 +150 -18
- 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.d.ts +3 -0
- package/index.js +6 -0
- package/llm.md +82 -0
- package/package.json +6 -3
- package/src/ai/ai-core.js +116 -0
- package/src/ai/ai-model.d.ts +37 -0
- package/src/ai/ai-model.js +179 -0
- package/src/ai/ai-model.test.js +113 -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 +42 -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/rc/rc-config.d.ts +30 -0
- package/src/rc/rc-config.js +122 -0
- package/src/rc/rc-config.test.js +61 -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/utils/unguard.d.ts +9 -0
- package/src/utils/unguard.js +57 -0
- 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
package/README.md
CHANGED
|
@@ -3,11 +3,103 @@
|
|
|
3
3
|
**GEN**erate **N**ext-level **A**utomated **D**escription **Y**ntelligence.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
+
# Commit message
|
|
6
7
|
npx gennady
|
|
8
|
+
|
|
9
|
+
# Code review for critical issues
|
|
10
|
+
npx gennady review
|
|
11
|
+
|
|
12
|
+
# Quickly display the contents
|
|
13
|
+
npx gennady cat <path1> <path2> ...
|
|
7
14
|
```
|
|
8
15
|
|
|
9
16
|
---
|
|
10
17
|
|
|
18
|
+
### ✨ Features
|
|
19
|
+
|
|
20
|
+
- 🤖 [**Commit Message**](#-commit-messages): Automatically generate clear, descriptive git commit messages from your staged changes.
|
|
21
|
+
- 📝 [**review**](#-review): Instantly review your staged git changes for critical issues (logic, runtime, security).
|
|
22
|
+
- 🐱 [**cat**](#-cat): Quickly display the contents of multiple files or directories at once, filtered by allowed extensions (default: .js, .ts, .tsx).
|
|
23
|
+
- 🛠️ **TypeScript Support**: Full TypeScript type definitions for seamless integration into TypeScript projects.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🔖 Usage Overview
|
|
28
|
+
|
|
29
|
+
Gennady provides several main CLI commands:
|
|
30
|
+
- `npx gennady` — Generate commit messages from your staged git changes.
|
|
31
|
+
- `npx gennady cat <path1> <path2> ...` — Display the contents of one or more files or directories, filtered by allowed extensions.
|
|
32
|
+
- `npx gennady review` — Review your staged git changes for critical issues.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🤖 Commit Messages
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
# Basic usage
|
|
40
|
+
npx gennady
|
|
41
|
+
|
|
42
|
+
# Generate oneline commit message
|
|
43
|
+
npx gennady --mode=oneline
|
|
44
|
+
|
|
45
|
+
# Generate detailed commit message
|
|
46
|
+
npx gennady --mode=detailed
|
|
47
|
+
|
|
48
|
+
# Generate detailed commit message relative to the target branch
|
|
49
|
+
npx gennady --branch=develop
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Options
|
|
53
|
+
| Option | Alias(es) | Description |
|
|
54
|
+
|-------------------|------------------|----------------------------------------------|
|
|
55
|
+
| `--mode` | `-m` | Set the mode (`auto`, `oneline`, `detailed`) |
|
|
56
|
+
| `--oneline` | `--short`, `-o` | Generate a one-line commit message |
|
|
57
|
+
| `--model` | | Specify the AI model |
|
|
58
|
+
| `--branch` | `-b` | Target branch for diff |
|
|
59
|
+
| `--apply` | | Immediately apply the generated commit message to git |
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
#### What Happens?
|
|
63
|
+
- Gennady analyzes your staged changes.
|
|
64
|
+
- It generates a commit message.
|
|
65
|
+
- If your system language isn't English, it translates the message for you.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## 📝 review
|
|
71
|
+
|
|
72
|
+
Review your staged git changes for critical issues.
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
npx gennady review
|
|
76
|
+
|
|
77
|
+
# Review changes relative to a specific branch
|
|
78
|
+
npx gennady review --branch=develop
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### What Happens?
|
|
82
|
+
- Gennady analyzes your staged changes.
|
|
83
|
+
- It checks only the lines added or modified in your diff for critical issues (logic, runtime, and security errors).
|
|
84
|
+
- If no critical issues are found, it outputs `GOOD`.
|
|
85
|
+
- If issues are found, they are listed in a clear, structured format.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 🐱 cat
|
|
90
|
+
|
|
91
|
+
Display the contents of files or directories (with filtering for allowed extensions).
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
npx gennady cat ./src/
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Output
|
|
98
|
+
- Shows file contents with headers per file.
|
|
99
|
+
- Hints for copying output without color codes.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
11
103
|
## Setup LLM
|
|
12
104
|
|
|
13
105
|
### Local
|
|
@@ -22,32 +114,72 @@ ollama serve
|
|
|
22
114
|
|
|
23
115
|
### External
|
|
24
116
|
|
|
25
|
-
Create `~/.gennadyrc` file:
|
|
117
|
+
Create `~/.gennadyrc` configuration file:
|
|
26
118
|
|
|
27
119
|
```json
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
120
|
+
{
|
|
121
|
+
"models": [
|
|
122
|
+
{
|
|
123
|
+
"model": "gpt-3.5-turbo-0125",
|
|
124
|
+
"url": "https://api.openai.com/v1/chat/completions",
|
|
125
|
+
"key": "...",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"model": "llama3:8b",
|
|
129
|
+
"url": "http://127.0.0.1:11434/api/generate",
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
35
133
|
```
|
|
36
134
|
|
|
37
135
|
---
|
|
38
136
|
|
|
39
|
-
|
|
137
|
+
## 🔌 API
|
|
138
|
+
|
|
139
|
+
Gennady provides a powerful JavaScript/TypeScript API for programmatic usage in your projects.
|
|
140
|
+
|
|
141
|
+
### Installation
|
|
40
142
|
|
|
41
143
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
144
|
+
npm install gennady
|
|
145
|
+
```
|
|
44
146
|
|
|
45
|
-
|
|
46
|
-
npx gennady --mode=oneline
|
|
147
|
+
### Basic Usage
|
|
47
148
|
|
|
48
|
-
|
|
49
|
-
|
|
149
|
+
```typescript
|
|
150
|
+
import { GennadyRc } from 'gennady/src/rc/rc-config';
|
|
151
|
+
import { AiModel } from 'gennady/src/ai/ai-model';
|
|
50
152
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
153
|
+
// Load configuration
|
|
154
|
+
const rc = new GennadyRc();
|
|
155
|
+
|
|
156
|
+
// Get available AI models
|
|
157
|
+
const models = rc.getModels();
|
|
158
|
+
|
|
159
|
+
// Create AI model instance
|
|
160
|
+
const aiModel = new AiModel(models[0]);
|
|
161
|
+
|
|
162
|
+
// Generate text
|
|
163
|
+
const [response, error] = await aiModel.generate('Hello, world!', {
|
|
164
|
+
temperature: 0.7,
|
|
165
|
+
timeout: 10000
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (error) {
|
|
169
|
+
console.error('Error:', error);
|
|
170
|
+
} else {
|
|
171
|
+
console.log('Response:', response);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Available Modules
|
|
176
|
+
|
|
177
|
+
- `AiModel`: Core AI model interaction
|
|
178
|
+
- `GennadyRc`: Configuration management
|
|
179
|
+
- `unguard`: Utility functions for error handling
|
|
180
|
+
|
|
181
|
+
For complete API documentation, check the source code with detailed JSDoc comments.
|
|
182
|
+
|
|
183
|
+
## 🎉 Happy Coding with Gennady!
|
|
184
|
+
|
|
185
|
+
> Made with 🤖 by Konstantin Lebedev
|
package/cli/cmd/cat.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { catGen } from '../../src/cat-gen/cat-gen.js';
|
|
5
|
+
import { style } from '../../src/utils/style.js';
|
|
6
|
+
import { parseArgs } from '../../src/utils/parse-args.js';
|
|
7
|
+
|
|
8
|
+
//
|
|
9
|
+
// 🐱 CAT-GEN
|
|
10
|
+
//
|
|
11
|
+
const args = parseArgs(process.argv);
|
|
12
|
+
|
|
13
|
+
if (args._.length === 0) {
|
|
14
|
+
console.error(style.yellow('Usage: npx gennady cat <path1> <path2> ...'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
catGen(args._).forEach(({ relativePath, content }) => {
|
|
19
|
+
console.log(style.blue(`#### ${relativePath}`));
|
|
20
|
+
console.log(content);
|
|
21
|
+
console.log('');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
console.log(style.green(`^`.repeat(40)));
|
|
25
|
+
console.log(style.italic.gray(`Hint: npx gennady cat ${process.argv.slice(3).join(' ')} --plain | pbcopy`));
|
|
26
|
+
console.log('');
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import { CommitGen } from '../../src/commit-gen/commit-gen.js';
|
|
8
|
+
import { parseArgs } from '../../src/utils/parse-args.js';
|
|
9
|
+
import { style } from '../../src/utils/style.js';
|
|
10
|
+
import { getSysLang } from '../../src/utils/language.js';
|
|
11
|
+
|
|
12
|
+
//
|
|
13
|
+
// 🤖 COMMIT-GEN 💬
|
|
14
|
+
//
|
|
15
|
+
|
|
16
|
+
const CACHE_PATH = path.join(os.homedir(), '.gennady_commit_cache.json');
|
|
17
|
+
const PROJECT_KEY = process.cwd();
|
|
18
|
+
const COMMIT_CACHE = readCache();
|
|
19
|
+
|
|
20
|
+
const params = parseArgs(process.argv, {
|
|
21
|
+
apply: ['apply'],
|
|
22
|
+
mode: ['mode', 'm'],
|
|
23
|
+
oneline: ['short', 'one', 'o'],
|
|
24
|
+
model: ['model'],
|
|
25
|
+
targetBranch: ['branch', 'b'],
|
|
26
|
+
apiUrl: ['api', 'apiUrl'],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const commitGen = new CommitGen(params);
|
|
30
|
+
|
|
31
|
+
console.info(
|
|
32
|
+
'🤖',
|
|
33
|
+
style.whiteBright.bold('GENNADY'),
|
|
34
|
+
`(${style.cyan(commitGen.model)} → ${style.yellow(commitGen.mode)})`,
|
|
35
|
+
'🗯️',
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
console.info(style.gray('-'.repeat(40)));
|
|
39
|
+
console.info(`- url: ${style.blue(commitGen.apiUrl)}`);
|
|
40
|
+
console.info(style.gray('-'.repeat(40)));
|
|
41
|
+
|
|
42
|
+
const commitMessage = params.apply && COMMIT_CACHE[PROJECT_KEY] || await commitGen.generate();
|
|
43
|
+
if (!commitMessage) {
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (params.apply) {
|
|
48
|
+
// APPLY COMMIT
|
|
49
|
+
execSync(
|
|
50
|
+
`git commit -am "${commitMessage.replace(/"/g, '\"')}"`,
|
|
51
|
+
{stdio: 'inherit'},
|
|
52
|
+
);
|
|
53
|
+
saveCache({ [PROJECT_KEY]: undefined });
|
|
54
|
+
} else {
|
|
55
|
+
// GENERATE COMMIT
|
|
56
|
+
console.info('-'.repeat(40), '\n');
|
|
57
|
+
console.info(style.whiteBright(commitMessage), '\n');
|
|
58
|
+
console.info('^'.repeat(40), '\n');
|
|
59
|
+
|
|
60
|
+
const lang = getSysLang();
|
|
61
|
+
if (lang !== 'en') {
|
|
62
|
+
console.info(await commitGen.translate(commitMessage, lang), '\n');
|
|
63
|
+
console.info('^'.repeat(40), '\n');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(style.italic.gray(`Hint: npx gennady ${process.argv.slice(3).join(' ')} --apply`));
|
|
67
|
+
console.log('');
|
|
68
|
+
|
|
69
|
+
saveCache({ [PROJECT_KEY]: commitMessage });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readCache() {
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8'));
|
|
75
|
+
} catch {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function saveCache(patch) {
|
|
81
|
+
const next = { ...COMMIT_CACHE, ...patch };
|
|
82
|
+
fs.writeFileSync(CACHE_PATH, JSON.stringify(next, null, 2));
|
|
83
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { getGitDiffInfo } from '../../src/git/git-core.js';
|
|
4
|
+
import { ReviewGen } from '../../src/review-gen/review-gen.js';
|
|
5
|
+
import { parseArgs } from '../../src/utils/parse-args.js';
|
|
6
|
+
import { style } from '../../src/utils/style.js';
|
|
7
|
+
|
|
8
|
+
//
|
|
9
|
+
// 📝 REVIEW-GEN
|
|
10
|
+
//
|
|
11
|
+
|
|
12
|
+
const params = parseArgs(process.argv, {
|
|
13
|
+
branch: ['branch', 'b'],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const review = new ReviewGen();
|
|
17
|
+
|
|
18
|
+
console.info(
|
|
19
|
+
`🤖`,
|
|
20
|
+
style.whiteBright.bold(`GENNADY`),
|
|
21
|
+
`(${style.cyan(review.ai.model)})`,
|
|
22
|
+
`📝`,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
console.info(style.gray(`-`.repeat(40)));
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
parsedCodeDiff,
|
|
29
|
+
parsedCodeTokens,
|
|
30
|
+
programmingLanguages,
|
|
31
|
+
parsedCodeChunkMaxTokens,
|
|
32
|
+
} = getGitDiffInfo(params.branch);
|
|
33
|
+
|
|
34
|
+
console.info(`- Tokens: ${style.bold.cyanBright(parsedCodeTokens)} ${style.gray(`(max per file: ${parsedCodeChunkMaxTokens})`)}`);
|
|
35
|
+
console.info(`- Languages: ${style.yellow(programmingLanguages)}`);
|
|
36
|
+
|
|
37
|
+
if (parsedCodeDiff.length === 0) {
|
|
38
|
+
console.info(`No changes detected, skipping review.`);
|
|
39
|
+
console.info(style.italic.gray(`Hint: git add`));
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const batches = review.ai.createPromptsBatchesByDiff(parsedCodeDiff);
|
|
44
|
+
|
|
45
|
+
console.info(`- Queue: ${style.bold.cyan(batches.length)}`);
|
|
46
|
+
console.info(style.gray(`-`.repeat(40)));
|
|
47
|
+
|
|
48
|
+
const startGenTime = performance.now();
|
|
49
|
+
const results = await Promise.all(batches.map(async (batch) => {
|
|
50
|
+
console.info(`- Task:`, batch.tokens, batch.languages);
|
|
51
|
+
return await review.generate(batch.diff, batch.languages);
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
console.info(style.gray(`-`.repeat(40)));
|
|
55
|
+
console.info(`- Generation time: ${style.blueBright(((performance.now() - startGenTime) / 1000).toFixed(2))}s`);
|
|
56
|
+
console.info(style.gray(`-`.repeat(40)));
|
|
57
|
+
|
|
58
|
+
console.info(results.join('\n\n'));
|
package/cli/gennady.js
CHANGED
|
@@ -1,48 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
|
|
3
|
+
switch(process.argv[2]) {
|
|
4
|
+
//
|
|
5
|
+
// 🐱 CAT-GEN
|
|
6
|
+
//
|
|
7
|
+
case 'cat':
|
|
8
|
+
import('./cmd/cat.js');
|
|
9
|
+
break;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
//
|
|
12
|
+
// 📝 REVIEW-GEN
|
|
13
|
+
//
|
|
14
|
+
case 'review':
|
|
15
|
+
import('./cmd/review.js');
|
|
16
|
+
break;
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const commit = new CommitGen({
|
|
25
|
-
...params,
|
|
26
|
-
basePromptTemplate: readFileSync(join(PROMPTS_DIR, 'base-prompt.md')).toString(),
|
|
27
|
-
formatOnelinePromptTemplate: readFileSync(join(PROMPTS_DIR, 'format-oneline-prompt.md')).toString(),
|
|
28
|
-
formatDetailedPromptTemplate: readFileSync(join(PROMPTS_DIR, 'format-detailed-prompt.md')).toString(),
|
|
29
|
-
translatePromptTemplate: readFileSync(join(PROMPTS_DIR, 'translate-prompt.md')).toString(),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
console.info(`🤖`, style.whiteBright.bold(`GENNADY`), `(${style.cyan(commit.model)} → ${style.yellow(commit.mode)})`, `🗯️`);
|
|
33
|
-
console.info(style.gray(`-`.repeat(30)));
|
|
34
|
-
console.info(`- url: ${style.blue(commit.apiUrl)}`);
|
|
35
|
-
console.info(style.gray(`-`.repeat(30)));
|
|
36
|
-
|
|
37
|
-
const msg = await commit.generate();
|
|
38
|
-
if (msg) {
|
|
39
|
-
console.info(`-`.repeat(40), '\n');
|
|
40
|
-
console.info(style.whiteBright(msg), '\n');
|
|
41
|
-
console.info(`^`.repeat(40), '\n');
|
|
42
|
-
|
|
43
|
-
const lang = getSysLang();
|
|
44
|
-
if (lang !== 'en') {
|
|
45
|
-
console.info(await commit.translate(msg, lang), '\n');
|
|
46
|
-
console.info(`^`.repeat(40), '\n');
|
|
47
|
-
}
|
|
18
|
+
//
|
|
19
|
+
// 🤖 COMMIT-GEN 💬
|
|
20
|
+
//
|
|
21
|
+
default:
|
|
22
|
+
import('./cmd/commit.js');
|
|
23
|
+
break;
|
|
48
24
|
}
|
package/index.d.ts
ADDED
package/index.js
CHANGED
package/llm.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Gennady API: Using AiModel.generate
|
|
2
|
+
|
|
3
|
+
This guide demonstrates best practices for calling `AiModel.generate` and handling its results, particularly when using TypeScript.
|
|
4
|
+
|
|
5
|
+
## 1. Basic Usage of `AiModel.generate`
|
|
6
|
+
|
|
7
|
+
The `AiModel.generate` method returns a Promise that resolves to a tuple: `[result, error]`. You need to handle both potential outcomes.
|
|
8
|
+
|
|
9
|
+
### ✅ GOOD: Handling the Result Tuple
|
|
10
|
+
|
|
11
|
+
This example assumes `aiModel` is an initialized instance of `AiModel`.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { AiModel } from 'gennady/src/ai/ai-model';
|
|
15
|
+
|
|
16
|
+
async function generateTextWithTupleHandling(aiModel: AiModel, prompt: string) {
|
|
17
|
+
const [response, error] = await aiModel.generate(prompt, {
|
|
18
|
+
temperature: 0.7,
|
|
19
|
+
timeout: 10000, // Optional: timeout in milliseconds
|
|
20
|
+
});
|
|
21
|
+
if (error) {
|
|
22
|
+
throw new Error(`[GENERATE_TEXT_WITH_TUPLE_ERROR] [${aiModel.name}] Generate failed`, {cause: error})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return response;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
**Reasoning:** This approach explicitly checks for an error before attempting to use the response. This is the fundamental way to interact with functions designed with this tuple-based error handling pattern.
|
|
29
|
+
|
|
30
|
+
## 2. Using `AiModel.generate` with `unguardOrThrow` for Cleaner Code
|
|
31
|
+
|
|
32
|
+
For scenarios where you want to simplify error handling and prefer exceptions for the error path, especially in a sequence of operations, `unguardOrThrow` is recommended.
|
|
33
|
+
|
|
34
|
+
### ❌ BAD: Multiple Manual Error Checks in Sequential Operations
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// ... imports and aiModel initialization ...
|
|
38
|
+
async function sequentialGenerationBad(aiModel: AiModel) {
|
|
39
|
+
const [response1, error1] = await aiModel.generate("First prompt");
|
|
40
|
+
if (error1) {
|
|
41
|
+
throw new Error(`[SEQUENTIAL_GENERATION_BAD_ERROR_1] [${aiModel.name}] Generate failed`, {cause: error1})
|
|
42
|
+
}
|
|
43
|
+
console.log("First response:", response1);
|
|
44
|
+
|
|
45
|
+
const [response2, error2] = await aiModel.generate("Second prompt using: " + response1.substring(0, 20));
|
|
46
|
+
if (error2) {
|
|
47
|
+
throw new Error(`[SEQUENTIAL_GENERATION_BAD_ERROR_2] [${aiModel.name}] Generate failed`, {cause: error2})
|
|
48
|
+
}
|
|
49
|
+
console.log("Second response:", response2);
|
|
50
|
+
// ... and so on
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
**Reasoning:** This becomes verbose and repetitive. Each step requires its own error check and handling logic, making the main flow harder to read.
|
|
54
|
+
|
|
55
|
+
### ✅ GOOD: Using `unguardOrThrow` for Concise Sequential Operations
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { AiModel } from 'gennady/src/ai/ai-model';
|
|
59
|
+
import { unguardOrThrow } from 'gennady/src/utils/unguard';
|
|
60
|
+
|
|
61
|
+
async function sequentialGenerationGood(aiModel: AiModel) {
|
|
62
|
+
try {
|
|
63
|
+
console.log("Attempting first generation...");
|
|
64
|
+
const response1 = await unguardOrThrow(aiModel.generate("Write a short poem about coding."));
|
|
65
|
+
console.log("First response:\n", response1);
|
|
66
|
+
|
|
67
|
+
console.log("\nAttempting second generation based on the first...");
|
|
68
|
+
const response2 = await unguardOrThrow(aiModel.generate(`Write a haiku based on this line: "${response1.split('\n')[0]}"`));
|
|
69
|
+
console.log("Second response (haiku):\n", response2);
|
|
70
|
+
|
|
71
|
+
// You can continue the chain of operations here
|
|
72
|
+
// const response3 = await unguardOrThrow(aiModel.generate(...));
|
|
73
|
+
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new Error(`[SEQUENTIAL_GENERATION_GOOD_ERROR] [${aiModel.name}] Generate failed`, {cause: error})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
**Reasoning:** `unguardOrThrow` unwraps the success value or throws the error if present. This allows you to write cleaner, more linear code for the success path and handle all errors in a single `catch` block. This is particularly useful for chained asynchronous operations where an error at any point should halt the entire chain.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
Remember to always initialize `AiModel` with valid configuration, typically loaded via `GennadyRc`. The examples above focus on the `generate` call itself, assuming `aiModel` is ready.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gennady",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1-next.1",
|
|
4
4
|
"author": "Konstantin Lebedev <ibnrubaxa@gmail.com>",
|
|
5
5
|
"description": "Gennady — Generate Next-level Automated Description Yntelligence",
|
|
6
6
|
"keywords": [
|
|
@@ -10,14 +10,17 @@
|
|
|
10
10
|
"local",
|
|
11
11
|
"commit",
|
|
12
12
|
"generator",
|
|
13
|
-
"gennady"
|
|
13
|
+
"gennady",
|
|
14
|
+
"review",
|
|
15
|
+
"code-review"
|
|
14
16
|
],
|
|
15
17
|
"license": "MIT",
|
|
16
18
|
"repository": "https://github.com/rubaxa/gennady",
|
|
17
19
|
"type": "module",
|
|
18
20
|
"bin": "./cli/gennady.js",
|
|
19
21
|
"main": "index.js",
|
|
22
|
+
"types": "index.d.ts",
|
|
20
23
|
"scripts": {
|
|
21
|
-
"test": "
|
|
24
|
+
"test": "node --test"
|
|
22
25
|
}
|
|
23
26
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { AiModel } from './ai-model.js';
|
|
2
|
+
import { GennadyRc } from '../rc/rc-config.js';
|
|
3
|
+
import { unguardOrThrow } from '../utils/unguard.js';
|
|
4
|
+
|
|
5
|
+
/** @deprecated */
|
|
6
|
+
export class AiCore {
|
|
7
|
+
/**
|
|
8
|
+
* List of AI Models
|
|
9
|
+
* @type {AiModel[]} array of #AI_MODEL_CLASS
|
|
10
|
+
*/
|
|
11
|
+
#models = [];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Active AI Model
|
|
15
|
+
* @type {AiModel|null}
|
|
16
|
+
*/
|
|
17
|
+
#activeModel = null;
|
|
18
|
+
|
|
19
|
+
constructor(init) {
|
|
20
|
+
this.init = {
|
|
21
|
+
logger: console,
|
|
22
|
+
timeout: 120,
|
|
23
|
+
maxInputTokens: init.maxInputTokens || 4000,
|
|
24
|
+
...init,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
GennadyRc.getDefaults().forEach((rc) => {
|
|
28
|
+
if (rc.isValid()) {
|
|
29
|
+
this.#models.push(...rc.getModels().map(model => new AiModel(model)));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!this.#models.length) {
|
|
34
|
+
this.#models.push(AiModel.getDefault());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get model() {
|
|
39
|
+
return this.#models[0]?.name;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get apiUrl() {
|
|
43
|
+
return this.#models[0]?.url
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get maxInputTokens() {
|
|
47
|
+
return this.init.maxInputTokens;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get logger() {
|
|
51
|
+
return this.init.logger;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
createPromptsBatchesByDiff(parseDiff) {
|
|
55
|
+
const maxChunkTokens = parseDiff.at(-1)?.tokens || 0
|
|
56
|
+
|
|
57
|
+
if (maxChunkTokens > this.maxInputTokens) {
|
|
58
|
+
this.logger.error(`TODO: Diff is too large`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const batches = parseDiff.reduce((acc, file) => {
|
|
63
|
+
if (!acc[0] || acc[0].tokens + file.tokens > this.maxInputTokens) {
|
|
64
|
+
acc.unshift({tokens: 0, diff: '', languages: []});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const fileDiff = file.diff.hunks.flatMap(h => h.changes).join('\n');
|
|
68
|
+
if (fileDiff.trim()) {
|
|
69
|
+
acc[0].tokens += file.tokens;
|
|
70
|
+
acc[0].diff += `### File **${file.filename}**:\n${fileDiff}\n\n`;
|
|
71
|
+
|
|
72
|
+
if (!acc[0].languages.includes(file.programmingLanguage)) {
|
|
73
|
+
acc[0].languages.push(file.programmingLanguage);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return acc;
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
return batches;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async generate(prompt, context) {
|
|
84
|
+
try {
|
|
85
|
+
const model = await unguardOrThrow(this.#choiceModel());
|
|
86
|
+
const result = await unguardOrThrow(model.generate(prompt, context));
|
|
87
|
+
return result;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
this.logger.error(`[AI_CORE_ERROR_GENERATE] Failed to generate LLM response:`, error);
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Choose active model
|
|
96
|
+
* @anchor AI_CORE_CHOICE_MODEL
|
|
97
|
+
* @returns {Promise<[AiModel, null] | [null, Error]>}
|
|
98
|
+
*/
|
|
99
|
+
async #choiceModel() {
|
|
100
|
+
if (this.#activeModel) {
|
|
101
|
+
return [this.#activeModel, null];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const model of this.#models) {
|
|
105
|
+
const [ok, error] = await model.ping();
|
|
106
|
+
if (ok) {
|
|
107
|
+
this.#activeModel = model;
|
|
108
|
+
return [model, null];
|
|
109
|
+
} else {
|
|
110
|
+
this.logger.warn(`[AI_CORE_ERROR_PING_MODEL_FAIL] [${model.name}] Ping failed:`, error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return [null, new Error(`[AI_CORE_ERROR_PING_FAIL] No available models`)];
|
|
115
|
+
}
|
|
116
|
+
}
|