@eldrforge/ai-service 0.1.13 → 0.1.14
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 +1112 -226
- package/dist/index.js +84 -55
- package/dist/index.js.map +1 -1
- package/examples/01-simple-commit.ts +80 -0
- package/examples/02-release-notes.ts +124 -0
- package/examples/03-interactive-commit.ts +150 -0
- package/examples/04-custom-storage.ts +162 -0
- package/examples/05-custom-tools.ts +243 -0
- package/examples/README.md +320 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
# @eldrforge/ai-service
|
|
2
2
|
|
|
3
|
-
AI-powered content generation for
|
|
3
|
+
AI-powered content generation library with agentic capabilities for commit messages, release notes, and code reviews.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Commit messages** (traditional and agentic with tool-calling)
|
|
9
|
-
- **Release notes** (traditional and agentic with tool-calling)
|
|
10
|
-
- **Code review analyses**
|
|
7
|
+
`@eldrforge/ai-service` is a TypeScript library that provides intelligent content generation powered by OpenAI's GPT models. It was extracted from the [kodrdriv](https://github.com/calenvarek/kodrdriv) automation tool to enable standalone use in any Node.js application.
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
### Key Features
|
|
10
|
+
|
|
11
|
+
- **🤖 Agentic AI Mode**: Advanced tool-calling capabilities that allow the AI to investigate codebases, analyze history, and understand context deeply before generating content
|
|
12
|
+
- **📝 Traditional Mode**: Direct prompt-based generation for faster, simpler use cases
|
|
13
|
+
- **🔧 Extensible Tool System**: 13+ specialized tools for codebase investigation and analysis
|
|
14
|
+
- **💬 Interactive Features**: Built-in user prompts, editor integration, and feedback loops
|
|
15
|
+
- **🎯 Structured Prompts**: Leverages [@riotprompt/riotprompt](https://www.npmjs.com/package/@riotprompt/riotprompt) for consistent, high-quality prompt engineering
|
|
16
|
+
- **📊 Detailed Metrics**: Track tool usage, iterations, and AI reasoning effectiveness
|
|
17
|
+
- **🔌 Flexible Integration**: Adapter-based design for easy integration with any storage or logging system
|
|
18
|
+
|
|
19
|
+
### Use Cases
|
|
20
|
+
|
|
21
|
+
- Generate commit messages from staged changes
|
|
22
|
+
- Create comprehensive release notes from version diffs
|
|
23
|
+
- Perform code review analysis
|
|
24
|
+
- Automate documentation generation
|
|
25
|
+
- Integrate AI-powered code understanding into your tools
|
|
13
26
|
|
|
14
27
|
## Installation
|
|
15
28
|
|
|
@@ -17,337 +30,1210 @@ Extracted from the kodrdriv project for reusability and broader ecosystem use.
|
|
|
17
30
|
npm install @eldrforge/ai-service
|
|
18
31
|
```
|
|
19
32
|
|
|
20
|
-
|
|
33
|
+
### Required Dependencies
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install openai @riotprompt/riotprompt @eldrforge/git-tools
|
|
37
|
+
```
|
|
21
38
|
|
|
22
|
-
|
|
23
|
-
- `@riotprompt/riotprompt` - Structured prompt builder
|
|
24
|
-
- `@eldrforge/git-tools` - Utility functions
|
|
39
|
+
### Optional Dependencies
|
|
25
40
|
|
|
26
|
-
|
|
41
|
+
```bash
|
|
42
|
+
npm install winston # For logging support
|
|
43
|
+
```
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
- Structured prompt generation for commits, releases, and reviews
|
|
30
|
-
- OpenAI API integration with retry logic
|
|
31
|
-
- Interactive user feedback and editing
|
|
32
|
-
- Audio transcription support
|
|
45
|
+
## Quick Start
|
|
33
46
|
|
|
34
|
-
###
|
|
35
|
-
- **Tool-calling capabilities** for AI-powered investigation
|
|
36
|
-
- **13 specialized tools** for release analysis
|
|
37
|
-
- **8 tools** for commit analysis
|
|
38
|
-
- **Self-reflection** reports with tool effectiveness metrics
|
|
39
|
-
- **Iterative refinement** with configurable limits
|
|
47
|
+
### 1. Setup OpenAI API Key
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
```typescript
|
|
50
|
+
import { createCompletion } from '@eldrforge/ai-service';
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
// Set your OpenAI API key
|
|
53
|
+
process.env.OPENAI_API_KEY = 'sk-...';
|
|
54
|
+
```
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
### 2. Generate a Commit Message (Traditional Mode)
|
|
46
57
|
|
|
47
58
|
```typescript
|
|
48
|
-
import {
|
|
59
|
+
import { createCommitPrompt, createCompletionWithRetry } from '@eldrforge/ai-service';
|
|
60
|
+
import { execSync } from 'child_process';
|
|
61
|
+
|
|
62
|
+
// Get staged diff
|
|
63
|
+
const diffContent = execSync('git diff --staged', { encoding: 'utf8' });
|
|
49
64
|
|
|
50
65
|
// Create prompt
|
|
51
|
-
const { prompt
|
|
66
|
+
const { prompt } = await createCommitPrompt(
|
|
52
67
|
{ overridePaths: [], overrides: true },
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
diffContent: 'git diff output',
|
|
56
|
-
releaseFocus: 'Performance improvements',
|
|
57
|
-
milestoneIssues: 'Issue #1: Bug fix',
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
context: 'Major release',
|
|
61
|
-
directories: ['src'],
|
|
62
|
-
}
|
|
68
|
+
{ diffContent },
|
|
69
|
+
{ context: 'Feature development', directories: ['src'] }
|
|
63
70
|
);
|
|
64
71
|
|
|
65
|
-
// Generate
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
responseFormat: { type: 'json_object' },
|
|
72
|
-
}
|
|
73
|
-
);
|
|
72
|
+
// Generate commit message
|
|
73
|
+
const response = await createCompletionWithRetry(prompt.messages, {
|
|
74
|
+
model: 'gpt-4o-mini',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
console.log('Suggested commit message:', response.choices[0].message.content);
|
|
74
78
|
```
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
### 3. Generate Release Notes (Agentic Mode)
|
|
77
81
|
|
|
78
82
|
```typescript
|
|
79
83
|
import { runAgenticRelease } from '@eldrforge/ai-service';
|
|
84
|
+
import { execSync } from 'child_process';
|
|
80
85
|
|
|
86
|
+
// Get git log and diff between versions
|
|
87
|
+
const logContent = execSync('git log v1.0.0..HEAD --oneline', { encoding: 'utf8' });
|
|
88
|
+
const diffContent = execSync('git diff v1.0.0..HEAD --stat', { encoding: 'utf8' });
|
|
89
|
+
|
|
90
|
+
// Run agentic release notes generation
|
|
81
91
|
const result = await runAgenticRelease({
|
|
82
92
|
fromRef: 'v1.0.0',
|
|
83
93
|
toRef: 'HEAD',
|
|
84
|
-
logContent
|
|
85
|
-
diffContent
|
|
86
|
-
|
|
87
|
-
releaseFocus: 'Performance improvements',
|
|
88
|
-
userContext: 'Major release',
|
|
94
|
+
logContent,
|
|
95
|
+
diffContent,
|
|
96
|
+
releaseFocus: 'Performance improvements and bug fixes',
|
|
89
97
|
model: 'gpt-4o',
|
|
90
|
-
maxIterations: 30,
|
|
91
|
-
storage: storageAdapter,
|
|
92
|
-
logger: loggerAdapter,
|
|
98
|
+
maxIterations: 30,
|
|
93
99
|
});
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
console.log('Title:', result.releaseNotes.title);
|
|
102
|
+
console.log('Body:', result.releaseNotes.body);
|
|
103
|
+
console.log('Iterations:', result.iterations);
|
|
104
|
+
console.log('Tools used:', result.toolCallsExecuted);
|
|
99
105
|
```
|
|
100
106
|
|
|
101
|
-
|
|
107
|
+
## Complete Examples
|
|
102
108
|
|
|
103
|
-
|
|
109
|
+
### Example 1: Standalone Commit Message Generator
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
import { createCommitPrompt, createCompletionWithRetry } from '@eldrforge/ai-service';
|
|
111
|
+
Create a complete CLI tool for generating commit messages:
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
```typescript
|
|
114
|
+
#!/usr/bin/env node
|
|
115
|
+
import { runAgenticCommit } from '@eldrforge/ai-service';
|
|
116
|
+
import { execSync } from 'child_process';
|
|
117
|
+
import * as fs from 'fs/promises';
|
|
118
|
+
import * as path from 'path';
|
|
119
|
+
|
|
120
|
+
// Simple storage adapter that writes to ./output
|
|
121
|
+
const storageAdapter = {
|
|
122
|
+
async writeOutput(fileName: string, content: string): Promise<void> {
|
|
123
|
+
const dir = path.join(process.cwd(), 'output');
|
|
124
|
+
await fs.mkdir(dir, { recursive: true });
|
|
125
|
+
await fs.writeFile(path.join(dir, fileName), content, 'utf8');
|
|
114
126
|
},
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
async readTemp(fileName: string): Promise<string> {
|
|
128
|
+
return fs.readFile(path.join('/tmp', fileName), 'utf8');
|
|
129
|
+
},
|
|
130
|
+
async writeTemp(fileName: string, content: string): Promise<void> {
|
|
131
|
+
await fs.writeFile(path.join('/tmp', fileName), content, 'utf8');
|
|
132
|
+
},
|
|
133
|
+
async readFile(fileName: string): Promise<string> {
|
|
134
|
+
return fs.readFile(fileName, 'utf8');
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Simple console logger
|
|
139
|
+
const logger = {
|
|
140
|
+
info: (msg: string) => console.log(`[INFO] ${msg}`),
|
|
141
|
+
error: (msg: string) => console.error(`[ERROR] ${msg}`),
|
|
142
|
+
warn: (msg: string) => console.warn(`[WARN] ${msg}`),
|
|
143
|
+
debug: (msg: string) => console.debug(`[DEBUG] ${msg}`),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
async function main() {
|
|
147
|
+
try {
|
|
148
|
+
// Get staged changes
|
|
149
|
+
const status = execSync('git status --porcelain', { encoding: 'utf8' });
|
|
150
|
+
const changedFiles = status
|
|
151
|
+
.split('\n')
|
|
152
|
+
.filter(line => line.startsWith('M ') || line.startsWith('A '))
|
|
153
|
+
.map(line => line.substring(3));
|
|
154
|
+
|
|
155
|
+
if (changedFiles.length === 0) {
|
|
156
|
+
console.log('No staged changes found. Stage some changes first with: git add <files>');
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Get the diff
|
|
161
|
+
const diffContent = execSync('git diff --staged', { encoding: 'utf8' });
|
|
162
|
+
|
|
163
|
+
// Get recent commits for context
|
|
164
|
+
const logContext = execSync('git log --oneline -5', { encoding: 'utf8' });
|
|
165
|
+
|
|
166
|
+
console.log('🤖 Generating commit message...\n');
|
|
167
|
+
|
|
168
|
+
// Run agentic commit generation
|
|
169
|
+
const result = await runAgenticCommit({
|
|
170
|
+
changedFiles,
|
|
171
|
+
diffContent,
|
|
172
|
+
logContext,
|
|
173
|
+
model: 'gpt-4o-mini',
|
|
174
|
+
maxIterations: 10,
|
|
175
|
+
storage: storageAdapter,
|
|
176
|
+
logger,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.log('\n✨ Suggested Commit Message:\n');
|
|
180
|
+
console.log(result.commitMessage);
|
|
181
|
+
console.log('\n---');
|
|
182
|
+
console.log(`📊 Used ${result.toolCallsExecuted} tool calls in ${result.iterations} iterations`);
|
|
183
|
+
|
|
184
|
+
// Show suggested splits if any
|
|
185
|
+
if (result.suggestedSplits.length > 0) {
|
|
186
|
+
console.log('\n💡 Suggested Commit Splits:\n');
|
|
187
|
+
result.suggestedSplits.forEach((split, idx) => {
|
|
188
|
+
console.log(`\nSplit ${idx + 1}:`);
|
|
189
|
+
console.log(`Files: ${split.files.join(', ')}`);
|
|
190
|
+
console.log(`Rationale: ${split.rationale}`);
|
|
191
|
+
console.log(`Message: ${split.message}`);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Optionally save the message to a file
|
|
196
|
+
await storageAdapter.writeOutput('commit-message.txt', result.commitMessage);
|
|
197
|
+
console.log('\n💾 Commit message saved to: output/commit-message.txt');
|
|
198
|
+
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error('Error:', error instanceof Error ? error.message : String(error));
|
|
201
|
+
process.exit(1);
|
|
118
202
|
}
|
|
119
|
-
|
|
203
|
+
}
|
|
120
204
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
205
|
+
main();
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Save as `generate-commit.ts` and run with:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npx tsx generate-commit.ts
|
|
125
212
|
```
|
|
126
213
|
|
|
127
|
-
|
|
214
|
+
### Example 2: Release Notes Generator for GitHub Releases
|
|
215
|
+
|
|
216
|
+
Automatically generate release notes and create GitHub releases:
|
|
128
217
|
|
|
129
218
|
```typescript
|
|
130
|
-
import {
|
|
219
|
+
import { runAgenticRelease } from '@eldrforge/ai-service';
|
|
220
|
+
import { execSync } from 'child_process';
|
|
221
|
+
import { Octokit } from '@octokit/rest';
|
|
222
|
+
import * as fs from 'fs/promises';
|
|
223
|
+
|
|
224
|
+
interface ReleaseConfig {
|
|
225
|
+
owner: string;
|
|
226
|
+
repo: string;
|
|
227
|
+
fromTag: string;
|
|
228
|
+
toTag?: string;
|
|
229
|
+
githubToken: string;
|
|
230
|
+
}
|
|
131
231
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
232
|
+
async function createGitHubRelease(config: ReleaseConfig) {
|
|
233
|
+
const { owner, repo, fromTag, toTag = 'HEAD', githubToken } = config;
|
|
234
|
+
|
|
235
|
+
// Simple storage adapter
|
|
236
|
+
const storage = {
|
|
237
|
+
writeOutput: async (fileName: string, content: string) => {
|
|
238
|
+
await fs.mkdir('output', { recursive: true });
|
|
239
|
+
await fs.writeFile(`output/${fileName}`, content);
|
|
240
|
+
},
|
|
241
|
+
readTemp: async (fileName: string) => fs.readFile(`/tmp/${fileName}`, 'utf8'),
|
|
242
|
+
writeTemp: async (fileName: string, content: string) =>
|
|
243
|
+
fs.writeFile(`/tmp/${fileName}`, content),
|
|
244
|
+
readFile: async (fileName: string) => fs.readFile(fileName, 'utf8'),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const logger = {
|
|
248
|
+
info: console.log,
|
|
249
|
+
error: console.error,
|
|
250
|
+
warn: console.warn,
|
|
251
|
+
debug: console.debug,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
// Get git information
|
|
256
|
+
const logContent = execSync(
|
|
257
|
+
`git log ${fromTag}..${toTag} --pretty=format:"%h %s (%an)" --abbrev-commit`,
|
|
258
|
+
{ encoding: 'utf8' }
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const diffContent = execSync(
|
|
262
|
+
`git diff ${fromTag}..${toTag} --stat`,
|
|
263
|
+
{ encoding: 'utf8' }
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
console.log(`🔍 Analyzing changes from ${fromTag} to ${toTag}...\n`);
|
|
267
|
+
|
|
268
|
+
// Generate release notes using agentic mode
|
|
269
|
+
const result = await runAgenticRelease({
|
|
270
|
+
fromRef: fromTag,
|
|
271
|
+
toRef: toTag,
|
|
272
|
+
logContent,
|
|
273
|
+
diffContent,
|
|
274
|
+
model: 'gpt-4o',
|
|
275
|
+
maxIterations: 30,
|
|
276
|
+
storage,
|
|
277
|
+
logger,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
console.log('✅ Release notes generated!\n');
|
|
281
|
+
console.log(`Title: ${result.releaseNotes.title}`);
|
|
282
|
+
console.log(`\nBody:\n${result.releaseNotes.body}`);
|
|
283
|
+
console.log(`\n📊 Metrics: ${result.toolCallsExecuted} tool calls, ${result.iterations} iterations`);
|
|
284
|
+
|
|
285
|
+
// Save to file
|
|
286
|
+
await storage.writeOutput('release-notes.md', result.releaseNotes.body);
|
|
287
|
+
await storage.writeOutput('release-title.txt', result.releaseNotes.title);
|
|
288
|
+
|
|
289
|
+
// Create GitHub release
|
|
290
|
+
const octokit = new Octokit({ auth: githubToken });
|
|
291
|
+
|
|
292
|
+
const release = await octokit.repos.createRelease({
|
|
293
|
+
owner,
|
|
294
|
+
repo,
|
|
295
|
+
tag_name: toTag === 'HEAD' ? 'v1.0.0' : toTag, // Adjust as needed
|
|
296
|
+
name: result.releaseNotes.title,
|
|
297
|
+
body: result.releaseNotes.body,
|
|
298
|
+
draft: true, // Create as draft for review
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
console.log(`\n🎉 Draft release created: ${release.data.html_url}`);
|
|
302
|
+
|
|
303
|
+
return result;
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error('❌ Error:', error);
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Usage
|
|
311
|
+
createGitHubRelease({
|
|
312
|
+
owner: 'yourorg',
|
|
313
|
+
repo: 'yourrepo',
|
|
314
|
+
fromTag: 'v0.1.0',
|
|
315
|
+
toTag: 'v0.2.0',
|
|
316
|
+
githubToken: process.env.GITHUB_TOKEN || '',
|
|
141
317
|
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Example 3: Custom Tool Integration
|
|
321
|
+
|
|
322
|
+
Create custom tools for domain-specific analysis:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import {
|
|
326
|
+
createToolRegistry,
|
|
327
|
+
type Tool,
|
|
328
|
+
type ToolContext,
|
|
329
|
+
runAgentic
|
|
330
|
+
} from '@eldrforge/ai-service';
|
|
331
|
+
|
|
332
|
+
// Create a custom tool
|
|
333
|
+
const checkTestCoverage: Tool = {
|
|
334
|
+
name: 'check_test_coverage',
|
|
335
|
+
description: 'Check test coverage for specific files or entire project',
|
|
336
|
+
parameters: {
|
|
337
|
+
type: 'object',
|
|
338
|
+
properties: {
|
|
339
|
+
filePath: {
|
|
340
|
+
type: 'string',
|
|
341
|
+
description: 'Optional file path to check coverage for. If omitted, checks entire project.',
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
execute: async (params: { filePath?: string }, context?: ToolContext) => {
|
|
346
|
+
const { execSync } = await import('child_process');
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
// Run coverage check
|
|
350
|
+
const cmd = params.filePath
|
|
351
|
+
? `npm test -- --coverage --testPathPattern="${params.filePath}"`
|
|
352
|
+
: 'npm test -- --coverage';
|
|
353
|
+
|
|
354
|
+
const output = execSync(cmd, {
|
|
355
|
+
encoding: 'utf8',
|
|
356
|
+
cwd: context?.workingDirectory || process.cwd(),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Parse coverage output
|
|
360
|
+
const coverageMatch = output.match(/All files.*?(\d+\.?\d*)\s*\|/);
|
|
361
|
+
const coverage = coverageMatch ? parseFloat(coverageMatch[1]) : 0;
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
success: true,
|
|
365
|
+
coverage,
|
|
366
|
+
message: `Coverage: ${coverage}%`,
|
|
367
|
+
details: output,
|
|
368
|
+
};
|
|
369
|
+
} catch (error) {
|
|
370
|
+
return {
|
|
371
|
+
success: false,
|
|
372
|
+
error: error instanceof Error ? error.message : String(error),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// Use custom tool in agentic workflow
|
|
379
|
+
async function analyzeWithCustomTools() {
|
|
380
|
+
const registry = createToolRegistry({
|
|
381
|
+
workingDirectory: process.cwd(),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Register custom tool
|
|
385
|
+
registry.register(checkTestCoverage);
|
|
386
|
+
|
|
387
|
+
// You can also register built-in tools
|
|
388
|
+
const { createCommitTools } = await import('@eldrforge/ai-service');
|
|
389
|
+
const commitTools = createCommitTools();
|
|
390
|
+
registry.registerAll(commitTools);
|
|
391
|
+
|
|
392
|
+
// Run agentic analysis with custom tools
|
|
393
|
+
const messages = [
|
|
394
|
+
{
|
|
395
|
+
role: 'system' as const,
|
|
396
|
+
content: 'You are a code quality analyst. Use available tools to assess code changes.',
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
role: 'user' as const,
|
|
400
|
+
content: 'Analyze the current changes and check if test coverage is adequate.',
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
const result = await runAgentic({
|
|
405
|
+
messages,
|
|
406
|
+
tools: registry,
|
|
407
|
+
model: 'gpt-4o',
|
|
408
|
+
maxIterations: 15,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
console.log('Analysis Result:', result.finalMessage);
|
|
412
|
+
console.log('Tools used:', result.toolCallsExecuted);
|
|
142
413
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// result.toolMetrics = detailed metrics
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
146
416
|
```
|
|
147
417
|
|
|
148
|
-
|
|
418
|
+
### Example 4: Interactive Commit Flow
|
|
149
419
|
|
|
150
|
-
|
|
420
|
+
Create an interactive commit workflow with user feedback:
|
|
151
421
|
|
|
152
|
-
|
|
422
|
+
```typescript
|
|
423
|
+
import {
|
|
424
|
+
runAgenticCommit,
|
|
425
|
+
getUserChoice,
|
|
426
|
+
editContentInEditor,
|
|
427
|
+
STANDARD_CHOICES
|
|
428
|
+
} from '@eldrforge/ai-service';
|
|
429
|
+
import { execSync } from 'child_process';
|
|
430
|
+
|
|
431
|
+
async function interactiveCommit() {
|
|
432
|
+
// Get staged changes
|
|
433
|
+
const diffContent = execSync('git diff --staged', { encoding: 'utf8' });
|
|
434
|
+
const statusOutput = execSync('git status --porcelain', { encoding: 'utf8' });
|
|
435
|
+
const changedFiles = statusOutput
|
|
436
|
+
.split('\n')
|
|
437
|
+
.filter(line => line.trim())
|
|
438
|
+
.map(line => line.substring(3));
|
|
439
|
+
|
|
440
|
+
if (changedFiles.length === 0) {
|
|
441
|
+
console.log('No staged changes. Run: git add <files>');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
console.log('📝 Changed files:', changedFiles.join(', '), '\n');
|
|
446
|
+
|
|
447
|
+
// Generate initial commit message
|
|
448
|
+
const result = await runAgenticCommit({
|
|
449
|
+
changedFiles,
|
|
450
|
+
diffContent,
|
|
451
|
+
model: 'gpt-4o-mini',
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
console.log('\n✨ Generated Commit Message:\n');
|
|
455
|
+
console.log(result.commitMessage);
|
|
456
|
+
console.log('\n---\n');
|
|
457
|
+
|
|
458
|
+
// Interactive loop
|
|
459
|
+
let finalMessage = result.commitMessage;
|
|
460
|
+
let shouldContinue = true;
|
|
461
|
+
|
|
462
|
+
while (shouldContinue) {
|
|
463
|
+
const choice = await getUserChoice(
|
|
464
|
+
'What would you like to do?',
|
|
465
|
+
[
|
|
466
|
+
STANDARD_CHOICES.CONFIRM,
|
|
467
|
+
STANDARD_CHOICES.EDIT,
|
|
468
|
+
STANDARD_CHOICES.SKIP,
|
|
469
|
+
]
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
switch (choice) {
|
|
473
|
+
case 'c': // Confirm
|
|
474
|
+
// Create the commit
|
|
475
|
+
execSync(`git commit -m "${finalMessage.replace(/"/g, '\\"')}"`, {
|
|
476
|
+
stdio: 'inherit',
|
|
477
|
+
});
|
|
478
|
+
console.log('✅ Commit created successfully!');
|
|
479
|
+
shouldContinue = false;
|
|
480
|
+
break;
|
|
481
|
+
|
|
482
|
+
case 'e': // Edit
|
|
483
|
+
const edited = await editContentInEditor(
|
|
484
|
+
finalMessage,
|
|
485
|
+
['# Edit your commit message below', '# Lines starting with # will be removed'],
|
|
486
|
+
'.txt'
|
|
487
|
+
);
|
|
488
|
+
finalMessage = edited.content;
|
|
489
|
+
console.log('\n📝 Updated commit message:\n');
|
|
490
|
+
console.log(finalMessage);
|
|
491
|
+
console.log('\n---\n');
|
|
492
|
+
break;
|
|
493
|
+
|
|
494
|
+
case 's': // Skip
|
|
495
|
+
console.log('⏭️ Commit cancelled.');
|
|
496
|
+
shouldContinue = false;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
interactiveCommit();
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## Core Concepts
|
|
506
|
+
|
|
507
|
+
### Agentic vs Traditional Mode
|
|
508
|
+
|
|
509
|
+
**Traditional Mode**:
|
|
510
|
+
- Direct prompt-based generation
|
|
511
|
+
- Fast and straightforward
|
|
512
|
+
- Good for simple use cases
|
|
513
|
+
- Lower token usage
|
|
514
|
+
|
|
515
|
+
**Agentic Mode**:
|
|
516
|
+
- AI uses tools to investigate the codebase
|
|
517
|
+
- More thorough and context-aware
|
|
518
|
+
- Better for complex changes
|
|
519
|
+
- Higher token usage but more accurate results
|
|
520
|
+
|
|
521
|
+
### Tool System
|
|
522
|
+
|
|
523
|
+
The library provides built-in tools for code analysis:
|
|
524
|
+
|
|
525
|
+
#### Commit Tools (8 tools)
|
|
153
526
|
1. `get_file_history` - View commit history for files
|
|
154
527
|
2. `get_file_content` - Read full file contents
|
|
155
528
|
3. `search_codebase` - Search for patterns
|
|
156
529
|
4. `get_related_tests` - Find related test files
|
|
157
|
-
5. `get_file_dependencies` - Analyze dependencies
|
|
158
|
-
6. `analyze_diff_section` - Get expanded context
|
|
159
|
-
7. `get_recent_commits` - See recent
|
|
530
|
+
5. `get_file_dependencies` - Analyze import dependencies
|
|
531
|
+
6. `analyze_diff_section` - Get expanded diff context
|
|
532
|
+
7. `get_recent_commits` - See recent commit messages
|
|
160
533
|
8. `group_files_by_concern` - Identify logical groupings
|
|
161
534
|
|
|
162
|
-
|
|
535
|
+
#### Release Tools (13 tools)
|
|
536
|
+
- All commit tools plus:
|
|
163
537
|
9. `get_tag_history` - View previous release tags
|
|
164
538
|
10. `compare_previous_release` - Compare with previous versions
|
|
165
539
|
11. `get_release_stats` - Get comprehensive statistics
|
|
166
540
|
12. `get_breaking_changes` - Identify breaking changes
|
|
167
541
|
13. `analyze_commit_patterns` - Find themes and patterns
|
|
168
542
|
|
|
169
|
-
###
|
|
543
|
+
### Adapters
|
|
170
544
|
|
|
171
|
-
|
|
545
|
+
The library uses adapters to remain flexible and framework-agnostic:
|
|
172
546
|
|
|
173
|
-
|
|
547
|
+
#### Storage Adapter
|
|
174
548
|
|
|
175
549
|
```typescript
|
|
176
|
-
|
|
550
|
+
interface StorageAdapter {
|
|
551
|
+
writeOutput(fileName: string, content: string): Promise<void>;
|
|
552
|
+
readTemp(fileName: string): Promise<string>;
|
|
553
|
+
writeTemp(fileName: string, content: string): Promise<void>;
|
|
554
|
+
readFile(fileName: string, encoding?: string): Promise<string>;
|
|
555
|
+
}
|
|
556
|
+
```
|
|
177
557
|
|
|
178
|
-
|
|
179
|
-
const registry = createToolRegistry({
|
|
180
|
-
workingDirectory: process.cwd(),
|
|
181
|
-
storage: storageAdapter,
|
|
182
|
-
logger: loggerAdapter,
|
|
183
|
-
});
|
|
184
|
-
const tools = createReleaseTools();
|
|
185
|
-
registry.registerAll(tools);
|
|
558
|
+
#### Logger Adapter
|
|
186
559
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
560
|
+
```typescript
|
|
561
|
+
interface Logger {
|
|
562
|
+
info(message: string, ...meta: unknown[]): void;
|
|
563
|
+
error(message: string, ...meta: unknown[]): void;
|
|
564
|
+
warn(message: string, ...meta: unknown[]): void;
|
|
565
|
+
debug(message: string, ...meta: unknown[]): void;
|
|
566
|
+
}
|
|
190
567
|
```
|
|
191
568
|
|
|
192
|
-
##
|
|
569
|
+
## API Reference
|
|
570
|
+
|
|
571
|
+
### Agentic Functions
|
|
572
|
+
|
|
573
|
+
#### `runAgenticCommit(config: AgenticCommitConfig): Promise<AgenticCommitResult>`
|
|
193
574
|
|
|
575
|
+
Generate commit messages with AI tool-calling capabilities.
|
|
576
|
+
|
|
577
|
+
**Config Options:**
|
|
194
578
|
```typescript
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
579
|
+
interface AgenticCommitConfig {
|
|
580
|
+
changedFiles: string[]; // List of changed file paths
|
|
581
|
+
diffContent: string; // Git diff output
|
|
582
|
+
userDirection?: string; // Optional user guidance
|
|
583
|
+
logContext?: string; // Recent commit history
|
|
584
|
+
model?: string; // OpenAI model (default: 'gpt-4o')
|
|
585
|
+
maxIterations?: number; // Max tool-calling iterations (default: 10)
|
|
586
|
+
debug?: boolean; // Enable debug output
|
|
587
|
+
debugRequestFile?: string; // File to save debug requests
|
|
588
|
+
debugResponseFile?: string; // File to save debug responses
|
|
589
|
+
storage?: StorageAdapter; // Storage adapter for file operations
|
|
590
|
+
logger?: Logger; // Logger adapter
|
|
591
|
+
openaiReasoning?: 'low' | 'medium' | 'high'; // Reasoning effort level
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**Returns:**
|
|
596
|
+
```typescript
|
|
597
|
+
interface AgenticCommitResult {
|
|
598
|
+
commitMessage: string; // Generated commit message
|
|
599
|
+
iterations: number; // Number of iterations used
|
|
600
|
+
toolCallsExecuted: number; // Number of tool calls made
|
|
601
|
+
suggestedSplits: Array<{ // Optional split suggestions
|
|
602
|
+
files: string[];
|
|
603
|
+
message: string;
|
|
604
|
+
rationale: string;
|
|
605
|
+
}>;
|
|
606
|
+
conversationHistory: ChatCompletionMessageParam[]; // Full conversation
|
|
607
|
+
toolMetrics: ToolExecutionMetric[]; // Tool usage metrics
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
#### `runAgenticRelease(config: AgenticReleaseConfig): Promise<AgenticReleaseResult>`
|
|
612
|
+
|
|
613
|
+
Generate release notes with AI tool-calling capabilities.
|
|
614
|
+
|
|
615
|
+
**Config Options:**
|
|
616
|
+
```typescript
|
|
617
|
+
interface AgenticReleaseConfig {
|
|
618
|
+
fromRef: string; // Starting git ref (e.g., 'v1.0.0')
|
|
619
|
+
toRef: string; // Ending git ref (e.g., 'HEAD')
|
|
620
|
+
logContent: string; // Git log output
|
|
621
|
+
diffContent: string; // Git diff output
|
|
622
|
+
milestoneIssues?: string; // GitHub milestone issues
|
|
623
|
+
releaseFocus?: string; // Focus area for release
|
|
624
|
+
userContext?: string; // Additional context
|
|
625
|
+
model?: string; // OpenAI model (default: 'gpt-4o')
|
|
626
|
+
maxIterations?: number; // Max iterations (default: 30)
|
|
627
|
+
debug?: boolean; // Enable debug output
|
|
628
|
+
debugRequestFile?: string; // Debug request file
|
|
629
|
+
debugResponseFile?: string; // Debug response file
|
|
630
|
+
storage?: StorageAdapter; // Storage adapter
|
|
631
|
+
logger?: Logger; // Logger adapter
|
|
632
|
+
openaiReasoning?: 'low' | 'medium' | 'high'; // Reasoning level
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**Returns:**
|
|
637
|
+
```typescript
|
|
638
|
+
interface AgenticReleaseResult {
|
|
639
|
+
releaseNotes: {
|
|
640
|
+
title: string; // Release title
|
|
641
|
+
body: string; // Release notes markdown
|
|
642
|
+
};
|
|
643
|
+
iterations: number; // Iterations used
|
|
644
|
+
toolCallsExecuted: number; // Tool calls made
|
|
645
|
+
conversationHistory: ChatCompletionMessageParam[]; // Full conversation
|
|
646
|
+
toolMetrics: ToolExecutionMetric[]; // Tool usage metrics
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
#### `runAgentic(config: AgenticConfig): Promise<AgenticResult>`
|
|
651
|
+
|
|
652
|
+
Low-level agentic execution for custom workflows.
|
|
653
|
+
|
|
654
|
+
### Traditional Prompt Functions
|
|
655
|
+
|
|
656
|
+
#### `createCommitPrompt(pathConfig, promptConfig, commandConfig)`
|
|
657
|
+
|
|
658
|
+
Create a structured prompt for commit message generation.
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
const { prompt, maxTokens } = await createCommitPrompt(
|
|
662
|
+
{ overridePaths: [], overrides: true },
|
|
663
|
+
{
|
|
664
|
+
diffContent: string,
|
|
665
|
+
logContext?: string,
|
|
666
|
+
userDirection?: string,
|
|
667
|
+
transcriptionText?: string,
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
context?: string,
|
|
671
|
+
directories?: string[],
|
|
672
|
+
}
|
|
673
|
+
);
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
#### `createReleasePrompt(pathConfig, promptConfig, commandConfig)`
|
|
677
|
+
|
|
678
|
+
Create a structured prompt for release notes generation.
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
const { prompt, maxTokens, isLargeRelease } = await createReleasePrompt(
|
|
682
|
+
{ overridePaths: [], overrides: true },
|
|
683
|
+
{
|
|
684
|
+
logContent: string,
|
|
685
|
+
diffContent: string,
|
|
686
|
+
releaseFocus?: string,
|
|
687
|
+
milestoneIssues?: string,
|
|
688
|
+
transcriptionText?: string,
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
context?: string,
|
|
692
|
+
directories?: string[],
|
|
693
|
+
}
|
|
694
|
+
);
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
#### `createReviewPrompt(pathConfig, promptConfig, commandConfig)`
|
|
698
|
+
|
|
699
|
+
Create a structured prompt for code review analysis.
|
|
700
|
+
|
|
701
|
+
### OpenAI Integration
|
|
702
|
+
|
|
703
|
+
#### `createCompletion(messages, options)`
|
|
202
704
|
|
|
203
|
-
|
|
705
|
+
Create a chat completion with OpenAI.
|
|
706
|
+
|
|
707
|
+
```typescript
|
|
708
|
+
const response = await createCompletion(
|
|
709
|
+
messages: ChatCompletionMessageParam[],
|
|
710
|
+
{
|
|
711
|
+
model?: string,
|
|
712
|
+
maxTokens?: number,
|
|
713
|
+
temperature?: number,
|
|
714
|
+
responseFormat?: { type: 'json_object' | 'text' },
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
#### `createCompletionWithRetry(messages, options, maxRetries?)`
|
|
720
|
+
|
|
721
|
+
Create a chat completion with automatic retry logic.
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
const response = await createCompletionWithRetry(
|
|
725
|
+
messages,
|
|
726
|
+
options,
|
|
727
|
+
maxRetries: number = 3
|
|
728
|
+
);
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
#### `transcribeAudio(audioPath, options?)`
|
|
732
|
+
|
|
733
|
+
Transcribe audio files using OpenAI Whisper.
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
const transcription = await transcribeAudio(
|
|
737
|
+
audioPath: string,
|
|
738
|
+
{
|
|
739
|
+
model?: string,
|
|
740
|
+
language?: string,
|
|
741
|
+
prompt?: string,
|
|
742
|
+
}
|
|
743
|
+
);
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Interactive Functions
|
|
747
|
+
|
|
748
|
+
#### `getUserChoice(prompt, choices, options?)`
|
|
749
|
+
|
|
750
|
+
Get a single-key choice from the user.
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
204
753
|
const choice = await getUserChoice(
|
|
205
754
|
'What would you like to do?',
|
|
206
|
-
[
|
|
755
|
+
[
|
|
756
|
+
{ key: 'c', label: 'Confirm' },
|
|
757
|
+
{ key: 'e', label: 'Edit' },
|
|
758
|
+
{ key: 's', label: 'Skip' },
|
|
759
|
+
],
|
|
207
760
|
{ nonTtyErrorSuggestions: ['Use --dry-run'] }
|
|
208
761
|
);
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
#### `getUserTextInput(prompt, options?)`
|
|
765
|
+
|
|
766
|
+
Get multi-line text input from the user.
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
const userInput = await getUserTextInput(
|
|
770
|
+
'Provide additional context:',
|
|
771
|
+
{ logger }
|
|
772
|
+
);
|
|
773
|
+
```
|
|
209
774
|
|
|
210
|
-
|
|
775
|
+
#### `editContentInEditor(content, templateLines?, extension?, editor?, logger?)`
|
|
776
|
+
|
|
777
|
+
Open content in user's editor for editing.
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
211
780
|
const result = await editContentInEditor(
|
|
212
781
|
'Initial content',
|
|
213
|
-
['#
|
|
214
|
-
'.md'
|
|
782
|
+
['# Edit your content below'],
|
|
783
|
+
'.md',
|
|
784
|
+
process.env.EDITOR,
|
|
785
|
+
logger
|
|
215
786
|
);
|
|
216
787
|
|
|
217
|
-
|
|
788
|
+
console.log(result.content);
|
|
789
|
+
console.log(result.wasEdited);
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
#### `getLLMFeedbackInEditor(contentType, currentContent, editor?, logger?)`
|
|
793
|
+
|
|
794
|
+
Get structured feedback from user via editor for LLM improvement loop.
|
|
795
|
+
|
|
796
|
+
```typescript
|
|
218
797
|
const feedback = await getLLMFeedbackInEditor(
|
|
219
|
-
'
|
|
220
|
-
|
|
798
|
+
'commit message',
|
|
799
|
+
currentCommitMessage,
|
|
800
|
+
undefined,
|
|
801
|
+
logger
|
|
221
802
|
);
|
|
803
|
+
```
|
|
222
804
|
|
|
223
|
-
|
|
805
|
+
#### `requireTTY(errorMessage?, logger?)`
|
|
806
|
+
|
|
807
|
+
Ensure the process is running in a TTY (terminal).
|
|
808
|
+
|
|
809
|
+
```typescript
|
|
224
810
|
requireTTY('This feature requires a terminal');
|
|
225
811
|
```
|
|
226
812
|
|
|
227
|
-
|
|
813
|
+
### Tool System
|
|
814
|
+
|
|
815
|
+
#### `createToolRegistry(context)`
|
|
228
816
|
|
|
229
|
-
|
|
817
|
+
Create a tool registry for managing tools.
|
|
230
818
|
|
|
231
819
|
```typescript
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
milestoneIssues?: string;
|
|
239
|
-
releaseFocus?: string;
|
|
240
|
-
userContext?: string;
|
|
241
|
-
model?: string;
|
|
242
|
-
maxIterations?: number; // Default: 30
|
|
243
|
-
debug?: boolean;
|
|
244
|
-
storage?: StorageAdapter;
|
|
245
|
-
logger?: Logger;
|
|
246
|
-
openaiReasoning?: 'low' | 'medium' | 'high';
|
|
247
|
-
}
|
|
820
|
+
const registry = createToolRegistry({
|
|
821
|
+
workingDirectory: process.cwd(),
|
|
822
|
+
storage: storageAdapter,
|
|
823
|
+
logger: loggerAdapter,
|
|
824
|
+
});
|
|
825
|
+
```
|
|
248
826
|
|
|
249
|
-
|
|
250
|
-
releaseNotes: { title: string; body: string };
|
|
251
|
-
iterations: number;
|
|
252
|
-
toolCallsExecuted: number;
|
|
253
|
-
conversationHistory: ChatCompletionMessageParam[];
|
|
254
|
-
toolMetrics: ToolExecutionMetric[];
|
|
255
|
-
}
|
|
827
|
+
#### `createCommitTools()`
|
|
256
828
|
|
|
257
|
-
|
|
258
|
-
interface AgenticCommitConfig {
|
|
259
|
-
changedFiles: string[];
|
|
260
|
-
diffContent: string;
|
|
261
|
-
userDirection?: string;
|
|
262
|
-
logContext?: string;
|
|
263
|
-
model?: string;
|
|
264
|
-
maxIterations?: number; // Default: 10
|
|
265
|
-
debug?: boolean;
|
|
266
|
-
storage?: StorageAdapter;
|
|
267
|
-
logger?: Logger;
|
|
268
|
-
openaiReasoning?: 'low' | 'medium' | 'high';
|
|
269
|
-
}
|
|
829
|
+
Get all commit-specific tools.
|
|
270
830
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
831
|
+
```typescript
|
|
832
|
+
const tools = createCommitTools();
|
|
833
|
+
registry.registerAll(tools);
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
#### `createReleaseTools()`
|
|
837
|
+
|
|
838
|
+
Get all release-specific tools (includes commit tools).
|
|
839
|
+
|
|
840
|
+
```typescript
|
|
841
|
+
const tools = createReleaseTools();
|
|
842
|
+
registry.registerAll(tools);
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
## Configuration
|
|
846
|
+
|
|
847
|
+
### Environment Variables
|
|
848
|
+
|
|
849
|
+
```bash
|
|
850
|
+
# Required
|
|
851
|
+
OPENAI_API_KEY=sk-... # Your OpenAI API key
|
|
852
|
+
|
|
853
|
+
# Optional
|
|
854
|
+
EDITOR=code --wait # Editor for interactive editing
|
|
855
|
+
OPENAI_BASE_URL=https://... # Custom OpenAI API endpoint
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
### Model Selection
|
|
859
|
+
|
|
860
|
+
Choose the appropriate model based on your needs:
|
|
861
|
+
|
|
862
|
+
```typescript
|
|
863
|
+
// Fast and economical
|
|
864
|
+
model: 'gpt-4o-mini'
|
|
865
|
+
|
|
866
|
+
// Balanced performance
|
|
867
|
+
model: 'gpt-4o'
|
|
868
|
+
|
|
869
|
+
// Maximum capability (reasoning models)
|
|
870
|
+
model: 'o1-preview'
|
|
871
|
+
model: 'o1-mini'
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
### Reasoning Levels
|
|
875
|
+
|
|
876
|
+
For reasoning models, control the effort level:
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
openaiReasoning: 'low' // Faster, less thorough
|
|
880
|
+
openaiReasoning: 'medium' // Balanced
|
|
881
|
+
openaiReasoning: 'high' // Most thorough, slower
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
## Advanced Usage
|
|
885
|
+
|
|
886
|
+
### Custom Storage Backend
|
|
887
|
+
|
|
888
|
+
Implement your own storage backend:
|
|
889
|
+
|
|
890
|
+
```typescript
|
|
891
|
+
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
892
|
+
|
|
893
|
+
const s3Storage = {
|
|
894
|
+
async writeOutput(fileName: string, content: string): Promise<void> {
|
|
895
|
+
const s3 = new S3Client({ region: 'us-east-1' });
|
|
896
|
+
await s3.send(new PutObjectCommand({
|
|
897
|
+
Bucket: 'my-bucket',
|
|
898
|
+
Key: `output/${fileName}`,
|
|
899
|
+
Body: content,
|
|
900
|
+
}));
|
|
901
|
+
},
|
|
902
|
+
|
|
903
|
+
async readTemp(fileName: string): Promise<string> {
|
|
904
|
+
// Implementation
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
async writeTemp(fileName: string, content: string): Promise<void> {
|
|
908
|
+
// Implementation
|
|
909
|
+
},
|
|
910
|
+
|
|
911
|
+
async readFile(fileName: string): Promise<string> {
|
|
912
|
+
// Implementation
|
|
913
|
+
},
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
// Use with agentic functions
|
|
917
|
+
await runAgenticCommit({
|
|
918
|
+
changedFiles,
|
|
919
|
+
diffContent,
|
|
920
|
+
storage: s3Storage,
|
|
921
|
+
});
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
### Custom Logger Integration
|
|
925
|
+
|
|
926
|
+
Use Winston, Pino, or any other logger:
|
|
927
|
+
|
|
928
|
+
```typescript
|
|
929
|
+
import winston from 'winston';
|
|
930
|
+
|
|
931
|
+
const winstonLogger = winston.createLogger({
|
|
932
|
+
level: 'info',
|
|
933
|
+
format: winston.format.json(),
|
|
934
|
+
transports: [
|
|
935
|
+
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
|
936
|
+
new winston.transports.File({ filename: 'combined.log' }),
|
|
937
|
+
],
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
await runAgenticRelease({
|
|
941
|
+
fromRef: 'v1.0.0',
|
|
942
|
+
toRef: 'HEAD',
|
|
943
|
+
logContent,
|
|
944
|
+
diffContent,
|
|
945
|
+
logger: winstonLogger,
|
|
946
|
+
});
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
### Monitoring Tool Usage
|
|
950
|
+
|
|
951
|
+
Track which tools are being used and how effective they are:
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
const result = await runAgenticCommit({
|
|
955
|
+
changedFiles,
|
|
956
|
+
diffContent,
|
|
957
|
+
model: 'gpt-4o',
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
// Analyze tool metrics
|
|
961
|
+
result.toolMetrics.forEach(metric => {
|
|
962
|
+
console.log(`Tool: ${metric.name}`);
|
|
963
|
+
console.log(` Success: ${metric.success}`);
|
|
964
|
+
console.log(` Duration: ${metric.duration}ms`);
|
|
965
|
+
console.log(` Iteration: ${metric.iteration}`);
|
|
966
|
+
if (metric.error) {
|
|
967
|
+
console.log(` Error: ${metric.error}`);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
// Identify most used tools
|
|
972
|
+
const toolUsage = result.toolMetrics.reduce((acc, metric) => {
|
|
973
|
+
acc[metric.name] = (acc[metric.name] || 0) + 1;
|
|
974
|
+
return acc;
|
|
975
|
+
}, {} as Record<string, number>);
|
|
976
|
+
|
|
977
|
+
console.log('Tool usage:', toolUsage);
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
### Debugging
|
|
981
|
+
|
|
982
|
+
Enable debug mode to see all AI interactions:
|
|
983
|
+
|
|
984
|
+
```typescript
|
|
985
|
+
const result = await runAgenticCommit({
|
|
986
|
+
changedFiles,
|
|
987
|
+
diffContent,
|
|
988
|
+
debug: true,
|
|
989
|
+
debugRequestFile: 'debug/request.json',
|
|
990
|
+
debugResponseFile: 'debug/response.json',
|
|
991
|
+
storage: storageAdapter,
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
// Check the debug files to see:
|
|
995
|
+
// - All messages sent to OpenAI
|
|
996
|
+
// - Tool calls and responses
|
|
997
|
+
// - Complete conversation history
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
## Best Practices
|
|
1001
|
+
|
|
1002
|
+
### 1. Choose the Right Mode
|
|
1003
|
+
|
|
1004
|
+
- Use **Traditional Mode** for:
|
|
1005
|
+
- Simple, obvious changes
|
|
1006
|
+
- Quick iterations during development
|
|
1007
|
+
- Cost-sensitive operations
|
|
1008
|
+
- Well-understood codebases
|
|
1009
|
+
|
|
1010
|
+
- Use **Agentic Mode** for:
|
|
1011
|
+
- Complex multi-file changes
|
|
1012
|
+
- Release notes requiring deep analysis
|
|
1013
|
+
- Unfamiliar codebases
|
|
1014
|
+
- High-quality, thorough documentation
|
|
1015
|
+
|
|
1016
|
+
### 2. Provide Context
|
|
1017
|
+
|
|
1018
|
+
The more context you provide, the better the results:
|
|
1019
|
+
|
|
1020
|
+
```typescript
|
|
1021
|
+
await runAgenticCommit({
|
|
1022
|
+
changedFiles,
|
|
1023
|
+
diffContent,
|
|
1024
|
+
userDirection: 'This refactors the authentication system to use OAuth2',
|
|
1025
|
+
logContext: recentCommits, // Provide recent commit history
|
|
1026
|
+
});
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
### 3. Configure Iteration Limits
|
|
1030
|
+
|
|
1031
|
+
Balance thoroughness with cost:
|
|
1032
|
+
|
|
1033
|
+
```typescript
|
|
1034
|
+
// For commits: 5-15 iterations is usually sufficient
|
|
1035
|
+
maxIterations: 10
|
|
1036
|
+
|
|
1037
|
+
// For releases: 20-40 iterations for comprehensive analysis
|
|
1038
|
+
maxIterations: 30
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
### 4. Handle Errors Gracefully
|
|
1042
|
+
|
|
1043
|
+
```typescript
|
|
1044
|
+
try {
|
|
1045
|
+
const result = await runAgenticCommit({
|
|
1046
|
+
changedFiles,
|
|
1047
|
+
diffContent,
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
// Use the result
|
|
1051
|
+
console.log(result.commitMessage);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
if (error.message.includes('API key')) {
|
|
1054
|
+
console.error('OpenAI API key not configured');
|
|
1055
|
+
} else if (error.message.includes('rate limit')) {
|
|
1056
|
+
console.error('Rate limit exceeded, try again later');
|
|
1057
|
+
} else {
|
|
1058
|
+
console.error('Unexpected error:', error);
|
|
1059
|
+
}
|
|
282
1060
|
}
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
### 5. Implement Rate Limiting
|
|
1064
|
+
|
|
1065
|
+
If you're processing many requests:
|
|
1066
|
+
|
|
1067
|
+
```typescript
|
|
1068
|
+
import pLimit from 'p-limit';
|
|
1069
|
+
|
|
1070
|
+
const limit = pLimit(3); // Max 3 concurrent requests
|
|
1071
|
+
|
|
1072
|
+
const commits = await Promise.all(
|
|
1073
|
+
changedFileSets.map(files =>
|
|
1074
|
+
limit(() => runAgenticCommit({ changedFiles: files, diffContent }))
|
|
1075
|
+
)
|
|
1076
|
+
);
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
## Troubleshooting
|
|
1080
|
+
|
|
1081
|
+
### "OpenAI API key not found"
|
|
1082
|
+
|
|
1083
|
+
Ensure your API key is set:
|
|
1084
|
+
|
|
1085
|
+
```bash
|
|
1086
|
+
export OPENAI_API_KEY=sk-...
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
Or set it programmatically:
|
|
1090
|
+
|
|
1091
|
+
```typescript
|
|
1092
|
+
process.env.OPENAI_API_KEY = 'sk-...';
|
|
1093
|
+
```
|
|
283
1094
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
1095
|
+
### "Interactive mode requires a terminal"
|
|
1096
|
+
|
|
1097
|
+
This error occurs when trying to use interactive features in non-TTY environments (e.g., CI/CD):
|
|
1098
|
+
|
|
1099
|
+
```typescript
|
|
1100
|
+
// Check before using interactive features
|
|
1101
|
+
if (process.stdin.isTTY) {
|
|
1102
|
+
const choice = await getUserChoice(prompt, choices);
|
|
1103
|
+
} else {
|
|
1104
|
+
// Use default behavior
|
|
1105
|
+
console.log('Non-interactive mode, using defaults');
|
|
292
1106
|
}
|
|
293
1107
|
```
|
|
294
1108
|
|
|
295
|
-
###
|
|
1109
|
+
### "Rate limit exceeded"
|
|
1110
|
+
|
|
1111
|
+
OpenAI has rate limits. Implement retry logic:
|
|
296
1112
|
|
|
297
1113
|
```typescript
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
1114
|
+
import { createCompletionWithRetry } from '@eldrforge/ai-service';
|
|
1115
|
+
|
|
1116
|
+
// This already includes retry logic
|
|
1117
|
+
const response = await createCompletionWithRetry(
|
|
1118
|
+
messages,
|
|
1119
|
+
options,
|
|
1120
|
+
5 // Max 5 retries
|
|
1121
|
+
);
|
|
1122
|
+
```
|
|
302
1123
|
|
|
303
|
-
|
|
304
|
-
export { runAgenticCommit } from './agentic/commit';
|
|
305
|
-
export { runAgenticRelease } from './agentic/release';
|
|
306
|
-
export { runAgentic } from './agentic/executor';
|
|
1124
|
+
### "Tool execution failed"
|
|
307
1125
|
|
|
308
|
-
|
|
309
|
-
export { createToolRegistry } from './tools/registry';
|
|
310
|
-
export { createCommitTools } from './tools/commit-tools';
|
|
311
|
-
export { createReleaseTools } from './tools/release-tools';
|
|
1126
|
+
Tool failures are logged in the metrics. Check them:
|
|
312
1127
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
createCompletion,
|
|
316
|
-
createCompletionWithRetry,
|
|
317
|
-
transcribeAudio,
|
|
318
|
-
} from './ai';
|
|
1128
|
+
```typescript
|
|
1129
|
+
const result = await runAgenticCommit({ ... });
|
|
319
1130
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
getLLMFeedbackInEditor,
|
|
326
|
-
requireTTY,
|
|
327
|
-
STANDARD_CHOICES,
|
|
328
|
-
} from './interactive';
|
|
1131
|
+
const failedTools = result.toolMetrics.filter(m => !m.success);
|
|
1132
|
+
failedTools.forEach(tool => {
|
|
1133
|
+
console.error(`Tool ${tool.name} failed: ${tool.error}`);
|
|
1134
|
+
});
|
|
1135
|
+
```
|
|
329
1136
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
1137
|
+
### "Model not found"
|
|
1138
|
+
|
|
1139
|
+
Ensure you're using a valid OpenAI model:
|
|
1140
|
+
|
|
1141
|
+
```typescript
|
|
1142
|
+
// Valid models as of 2024
|
|
1143
|
+
const validModels = [
|
|
1144
|
+
'gpt-4o',
|
|
1145
|
+
'gpt-4o-mini',
|
|
1146
|
+
'o1-preview',
|
|
1147
|
+
'o1-mini',
|
|
1148
|
+
'gpt-4-turbo',
|
|
1149
|
+
'gpt-3.5-turbo',
|
|
1150
|
+
];
|
|
333
1151
|
```
|
|
334
1152
|
|
|
335
|
-
##
|
|
1153
|
+
## TypeScript Support
|
|
1154
|
+
|
|
1155
|
+
The library is written in TypeScript and exports all types:
|
|
1156
|
+
|
|
1157
|
+
```typescript
|
|
1158
|
+
import type {
|
|
1159
|
+
AgenticCommitConfig,
|
|
1160
|
+
AgenticCommitResult,
|
|
1161
|
+
AgenticReleaseConfig,
|
|
1162
|
+
AgenticReleaseResult,
|
|
1163
|
+
StorageAdapter,
|
|
1164
|
+
Logger,
|
|
1165
|
+
Tool,
|
|
1166
|
+
ToolContext,
|
|
1167
|
+
ToolExecutionMetric,
|
|
1168
|
+
Choice,
|
|
1169
|
+
InteractiveOptions,
|
|
1170
|
+
EditorOptions,
|
|
1171
|
+
AIConfig,
|
|
1172
|
+
} from '@eldrforge/ai-service';
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
## Performance Considerations
|
|
1176
|
+
|
|
1177
|
+
### Token Usage
|
|
1178
|
+
|
|
1179
|
+
Agentic mode uses more tokens due to tool-calling:
|
|
1180
|
+
|
|
1181
|
+
- **Commit generation**: ~5,000-20,000 tokens
|
|
1182
|
+
- **Release generation**: ~20,000-100,000 tokens
|
|
1183
|
+
|
|
1184
|
+
Monitor usage with the `toolMetrics` data.
|
|
1185
|
+
|
|
1186
|
+
### Execution Time
|
|
1187
|
+
|
|
1188
|
+
- **Traditional mode**: 2-10 seconds
|
|
1189
|
+
- **Agentic mode (commits)**: 10-60 seconds
|
|
1190
|
+
- **Agentic mode (releases)**: 30-180 seconds
|
|
1191
|
+
|
|
1192
|
+
### Cost Optimization
|
|
1193
|
+
|
|
1194
|
+
1. Use `gpt-4o-mini` for development and testing
|
|
1195
|
+
2. Limit `maxIterations` for cost control
|
|
1196
|
+
3. Cache results when possible
|
|
1197
|
+
4. Use traditional mode for simple cases
|
|
1198
|
+
|
|
1199
|
+
## Contributing
|
|
1200
|
+
|
|
1201
|
+
Contributions are welcome! This library was extracted from [kodrdriv](https://github.com/calenvarek/kodrdriv).
|
|
1202
|
+
|
|
1203
|
+
### Development Setup
|
|
336
1204
|
|
|
337
1205
|
```bash
|
|
338
|
-
|
|
1206
|
+
git clone https://github.com/calenvarek/ai-service.git
|
|
1207
|
+
cd ai-service
|
|
339
1208
|
npm install
|
|
340
|
-
|
|
341
|
-
# Build
|
|
342
1209
|
npm run build
|
|
1210
|
+
npm test
|
|
1211
|
+
```
|
|
343
1212
|
|
|
344
|
-
|
|
345
|
-
npm run test
|
|
1213
|
+
### Running Tests
|
|
346
1214
|
|
|
347
|
-
|
|
348
|
-
npm
|
|
1215
|
+
```bash
|
|
1216
|
+
npm test # Run all tests
|
|
1217
|
+
npm test -- --watch # Watch mode
|
|
1218
|
+
npm test -- --coverage # With coverage
|
|
349
1219
|
```
|
|
350
1220
|
|
|
351
1221
|
## License
|
|
352
1222
|
|
|
353
1223
|
Apache-2.0
|
|
1224
|
+
|
|
1225
|
+
## Related Projects
|
|
1226
|
+
|
|
1227
|
+
- **[kodrdriv](https://github.com/calenvarek/kodrdriv)** - Full automation toolkit that uses this library
|
|
1228
|
+
- **[@eldrforge/git-tools](https://www.npmjs.com/package/@eldrforge/git-tools)** - Git utility functions
|
|
1229
|
+
- **[@riotprompt/riotprompt](https://www.npmjs.com/package/@riotprompt/riotprompt)** - Structured prompt builder
|
|
1230
|
+
|
|
1231
|
+
## Support
|
|
1232
|
+
|
|
1233
|
+
- 📖 [Full Documentation](https://github.com/calenvarek/ai-service)
|
|
1234
|
+
- 🐛 [Issue Tracker](https://github.com/calenvarek/ai-service/issues)
|
|
1235
|
+
- 💬 [Discussions](https://github.com/calenvarek/ai-service/discussions)
|
|
1236
|
+
|
|
1237
|
+
## Changelog
|
|
1238
|
+
|
|
1239
|
+
See [RELEASE_NOTES.md](./RELEASE_NOTES.md) for version history and changes.
|