@ksw8954/git-ai-commit 1.1.1 → 1.1.3
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/CHANGELOG.md +17 -0
- package/dist/commands/ai.d.ts +1 -1
- package/dist/commands/ai.d.ts.map +1 -1
- package/dist/commands/ai.js +10 -3
- package/dist/commands/ai.js.map +1 -1
- package/dist/commands/commit.d.ts.map +1 -1
- package/dist/commands/commit.js +8 -0
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/git.d.ts +1 -0
- package/dist/commands/git.d.ts.map +1 -1
- package/dist/commands/git.js +20 -0
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +5 -5
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/log.d.ts +9 -1
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +19 -1
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/prCommand.d.ts.map +1 -1
- package/dist/commands/prCommand.js +8 -4
- package/dist/commands/prCommand.js.map +1 -1
- package/dist/commands/tag.d.ts +1 -0
- package/dist/commands/tag.d.ts.map +1 -1
- package/dist/commands/tag.js +82 -28
- package/dist/commands/tag.js.map +1 -1
- package/dist/prompts/tag.d.ts +1 -1
- package/dist/prompts/tag.d.ts.map +1 -1
- package/dist/prompts/tag.js +35 -3
- package/dist/prompts/tag.js.map +1 -1
- package/package.json +12 -12
- package/src/commands/ai.ts +19 -3
- package/src/commands/commit.ts +8 -0
- package/src/commands/git.ts +20 -0
- package/src/commands/history.ts +6 -5
- package/src/commands/log.ts +30 -2
- package/src/commands/prCommand.ts +8 -4
- package/src/commands/tag.ts +98 -30
- package/src/prompts/tag.ts +39 -3
|
@@ -50,7 +50,8 @@ export class PullRequestCommand {
|
|
|
50
50
|
command: 'pr',
|
|
51
51
|
args: { ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
52
52
|
status: 'failure',
|
|
53
|
-
details: err
|
|
53
|
+
details: err,
|
|
54
|
+
model: mergedModel
|
|
54
55
|
});
|
|
55
56
|
process.exit(1);
|
|
56
57
|
return;
|
|
@@ -77,7 +78,8 @@ export class PullRequestCommand {
|
|
|
77
78
|
command: 'pr',
|
|
78
79
|
args: { ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
79
80
|
status: 'failure',
|
|
80
|
-
details: err
|
|
81
|
+
details: err,
|
|
82
|
+
model: mergedModel
|
|
81
83
|
});
|
|
82
84
|
process.exit(1);
|
|
83
85
|
return;
|
|
@@ -87,7 +89,8 @@ export class PullRequestCommand {
|
|
|
87
89
|
await LogService.append({
|
|
88
90
|
command: 'pr',
|
|
89
91
|
args: { ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
90
|
-
status: 'success'
|
|
92
|
+
status: 'success',
|
|
93
|
+
model: mergedModel
|
|
91
94
|
});
|
|
92
95
|
} catch (error) {
|
|
93
96
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -96,7 +99,8 @@ export class PullRequestCommand {
|
|
|
96
99
|
command: 'pr',
|
|
97
100
|
args: { ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
98
101
|
status: 'failure',
|
|
99
|
-
details: message
|
|
102
|
+
details: message,
|
|
103
|
+
model: options.model
|
|
100
104
|
});
|
|
101
105
|
process.exit(1);
|
|
102
106
|
}
|
package/src/commands/tag.ts
CHANGED
|
@@ -20,18 +20,29 @@ export class TagCommand {
|
|
|
20
20
|
constructor() {
|
|
21
21
|
this.program = new Command('tag')
|
|
22
22
|
.description('Create an annotated git tag with optional AI-generated notes')
|
|
23
|
-
.argument('
|
|
23
|
+
.argument('[name]', 'Tag name to create (auto-increments patch version if omitted)')
|
|
24
24
|
.option('-k, --api-key <key>', 'OpenAI API key (overrides env var)')
|
|
25
25
|
.option('--base-url <url>', 'Custom API base URL (overrides env var)')
|
|
26
26
|
.option('-m, --model <model>', 'Model to use (overrides env var)')
|
|
27
27
|
.option('--message <message>', 'Tag message to use directly (skips AI generation)')
|
|
28
28
|
.option('-t, --base-tag <tag>', 'Existing tag to diff against when generating notes')
|
|
29
29
|
.option('--prompt <text>', 'Additional instructions to append to the AI prompt for this tag')
|
|
30
|
-
.action(async (tagName: string, options: TagOptions) => {
|
|
30
|
+
.action(async (tagName: string | undefined, options: TagOptions) => {
|
|
31
31
|
await this.handleTag(tagName, options);
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
private incrementPatchVersion(version: string): string | null {
|
|
36
|
+
// Match semver patterns: v1.2.3, 1.2.3, v1.2.3-beta, etc.
|
|
37
|
+
const match = version.match(/^(v?)(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
38
|
+
if (!match) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const [, prefix, major, minor, patch, suffix] = match;
|
|
42
|
+
const newPatch = parseInt(patch, 10) + 1;
|
|
43
|
+
return `${prefix}${major}.${minor}.${newPatch}${suffix}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
private resolveAIConfig(options: TagOptions): AIServiceConfig {
|
|
36
47
|
const storedConfig = ConfigService.getConfig();
|
|
37
48
|
|
|
@@ -52,27 +63,58 @@ export class TagCommand {
|
|
|
52
63
|
};
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
private async handleTag(tagName: string, options: TagOptions): Promise<void> {
|
|
56
|
-
const
|
|
66
|
+
private async handleTag(tagName: string | undefined, options: TagOptions): Promise<void> {
|
|
67
|
+
const storedConfig = ConfigService.getConfig();
|
|
68
|
+
const mergedModel = options.model || storedConfig.model;
|
|
57
69
|
|
|
70
|
+
let trimmedName = tagName?.trim();
|
|
71
|
+
|
|
72
|
+
// If no tag name provided, auto-increment from latest tag
|
|
58
73
|
if (!trimmedName) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
const latestTagResult = await GitService.getLatestTag();
|
|
75
|
+
|
|
76
|
+
if (!latestTagResult.success || !latestTagResult.tag) {
|
|
77
|
+
console.error('No existing tags found. Please provide a tag name explicitly.');
|
|
78
|
+
await LogService.append({
|
|
79
|
+
command: 'tag',
|
|
80
|
+
args: { ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
81
|
+
status: 'failure',
|
|
82
|
+
details: 'no existing tags found for auto-increment',
|
|
83
|
+
model: mergedModel
|
|
84
|
+
});
|
|
85
|
+
process.exit(1);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const newVersion = this.incrementPatchVersion(latestTagResult.tag);
|
|
90
|
+
|
|
91
|
+
if (!newVersion) {
|
|
92
|
+
console.error(`Cannot parse version from tag "${latestTagResult.tag}". Please provide a tag name explicitly.`);
|
|
93
|
+
await LogService.append({
|
|
94
|
+
command: 'tag',
|
|
95
|
+
args: { ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
96
|
+
status: 'failure',
|
|
97
|
+
details: `cannot parse version from tag: ${latestTagResult.tag}`,
|
|
98
|
+
model: mergedModel
|
|
99
|
+
});
|
|
100
|
+
process.exit(1);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`Latest tag: ${latestTagResult.tag}`);
|
|
105
|
+
console.log(`New tag: ${newVersion}`);
|
|
106
|
+
trimmedName = newVersion;
|
|
68
107
|
}
|
|
69
108
|
|
|
70
109
|
// Check if tag already exists locally
|
|
71
110
|
const localTagExists = await GitService.tagExists(trimmedName);
|
|
72
111
|
let remoteTagExists = false;
|
|
73
|
-
let
|
|
112
|
+
let previousTagMessage: string | null = null;
|
|
74
113
|
|
|
75
114
|
if (localTagExists) {
|
|
115
|
+
// Get existing tag message before deletion for reference
|
|
116
|
+
previousTagMessage = await GitService.getTagMessage(trimmedName);
|
|
117
|
+
|
|
76
118
|
console.log(`⚠️ Tag ${trimmedName} already exists locally.`);
|
|
77
119
|
const shouldDelete = await this.confirmTagDelete(trimmedName);
|
|
78
120
|
|
|
@@ -82,7 +124,8 @@ export class TagCommand {
|
|
|
82
124
|
command: 'tag',
|
|
83
125
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
84
126
|
status: 'cancelled',
|
|
85
|
-
details: 'user declined to replace existing tag'
|
|
127
|
+
details: 'user declined to replace existing tag',
|
|
128
|
+
model: mergedModel
|
|
86
129
|
});
|
|
87
130
|
return;
|
|
88
131
|
}
|
|
@@ -103,7 +146,8 @@ export class TagCommand {
|
|
|
103
146
|
command: 'tag',
|
|
104
147
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
105
148
|
status: 'failure',
|
|
106
|
-
details: 'remote tag deletion failed'
|
|
149
|
+
details: 'remote tag deletion failed',
|
|
150
|
+
model: mergedModel
|
|
107
151
|
});
|
|
108
152
|
process.exit(1);
|
|
109
153
|
return;
|
|
@@ -122,13 +166,13 @@ export class TagCommand {
|
|
|
122
166
|
command: 'tag',
|
|
123
167
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
124
168
|
status: 'failure',
|
|
125
|
-
details: 'local tag deletion failed'
|
|
169
|
+
details: 'local tag deletion failed',
|
|
170
|
+
model: mergedModel
|
|
126
171
|
});
|
|
127
172
|
process.exit(1);
|
|
128
173
|
return;
|
|
129
174
|
}
|
|
130
175
|
console.log(`✅ Local tag ${trimmedName} deleted`);
|
|
131
|
-
wasTagReplaced = true;
|
|
132
176
|
}
|
|
133
177
|
|
|
134
178
|
let tagMessage = options.message?.trim();
|
|
@@ -147,6 +191,15 @@ export class TagCommand {
|
|
|
147
191
|
}
|
|
148
192
|
}
|
|
149
193
|
|
|
194
|
+
// Get style reference from base tag (if different from current tag being replaced)
|
|
195
|
+
let styleReferenceMessage: string | null = null;
|
|
196
|
+
if (baseTag && baseTag !== trimmedName) {
|
|
197
|
+
styleReferenceMessage = await GitService.getTagMessage(baseTag);
|
|
198
|
+
if (styleReferenceMessage) {
|
|
199
|
+
console.log(`Using ${baseTag} message as style reference.`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
150
203
|
const historyResult = await GitService.getCommitSummariesSince(baseTag);
|
|
151
204
|
if (!historyResult.success || !historyResult.log) {
|
|
152
205
|
console.error('Error:', historyResult.error ?? 'Unable to read commit history.');
|
|
@@ -154,7 +207,8 @@ export class TagCommand {
|
|
|
154
207
|
command: 'tag',
|
|
155
208
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
156
209
|
status: 'failure',
|
|
157
|
-
details: historyResult.error ?? 'Unable to read commit history.'
|
|
210
|
+
details: historyResult.error ?? 'Unable to read commit history.',
|
|
211
|
+
model: mergedModel
|
|
158
212
|
});
|
|
159
213
|
process.exit(1);
|
|
160
214
|
return;
|
|
@@ -171,14 +225,21 @@ export class TagCommand {
|
|
|
171
225
|
command: 'tag',
|
|
172
226
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
173
227
|
status: 'failure',
|
|
174
|
-
details: message
|
|
228
|
+
details: message,
|
|
229
|
+
model: mergedModel
|
|
175
230
|
});
|
|
176
231
|
process.exit(1);
|
|
177
232
|
return;
|
|
178
233
|
}
|
|
179
234
|
|
|
180
235
|
const aiService = new AIService(aiConfig);
|
|
181
|
-
const aiResult = await aiService.generateTagNotes(
|
|
236
|
+
const aiResult = await aiService.generateTagNotes(
|
|
237
|
+
trimmedName,
|
|
238
|
+
historyResult.log,
|
|
239
|
+
options.prompt,
|
|
240
|
+
previousTagMessage,
|
|
241
|
+
styleReferenceMessage
|
|
242
|
+
);
|
|
182
243
|
|
|
183
244
|
if (!aiResult.success || !aiResult.notes) {
|
|
184
245
|
console.error('Error:', aiResult.error ?? 'Failed to generate tag notes.');
|
|
@@ -186,7 +247,8 @@ export class TagCommand {
|
|
|
186
247
|
command: 'tag',
|
|
187
248
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
188
249
|
status: 'failure',
|
|
189
|
-
details: aiResult.error ?? 'Failed to generate tag notes.'
|
|
250
|
+
details: aiResult.error ?? 'Failed to generate tag notes.',
|
|
251
|
+
model: mergedModel
|
|
190
252
|
});
|
|
191
253
|
process.exit(1);
|
|
192
254
|
return;
|
|
@@ -207,7 +269,8 @@ export class TagCommand {
|
|
|
207
269
|
command: 'tag',
|
|
208
270
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
209
271
|
status: 'cancelled',
|
|
210
|
-
details: 'user declined tag creation'
|
|
272
|
+
details: 'user declined tag creation',
|
|
273
|
+
model: mergedModel
|
|
211
274
|
});
|
|
212
275
|
return;
|
|
213
276
|
}
|
|
@@ -221,7 +284,8 @@ export class TagCommand {
|
|
|
221
284
|
command: 'tag',
|
|
222
285
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
223
286
|
status: 'failure',
|
|
224
|
-
details: 'git tag creation failed'
|
|
287
|
+
details: 'git tag creation failed',
|
|
288
|
+
model: mergedModel
|
|
225
289
|
});
|
|
226
290
|
process.exit(1);
|
|
227
291
|
return;
|
|
@@ -238,8 +302,8 @@ export class TagCommand {
|
|
|
238
302
|
const selectedRemotes = await this.selectRemotesForPush(trimmedName, remotes);
|
|
239
303
|
|
|
240
304
|
if (selectedRemotes && selectedRemotes.length > 0) {
|
|
241
|
-
// If tag
|
|
242
|
-
const needsForcePush =
|
|
305
|
+
// If remote tag still exists (user declined to delete), use force push
|
|
306
|
+
const needsForcePush = remoteTagExists;
|
|
243
307
|
|
|
244
308
|
if (needsForcePush) {
|
|
245
309
|
console.log(`\n⚠️ Tag ${trimmedName} may exist on remote. Force push is required.`);
|
|
@@ -251,7 +315,8 @@ export class TagCommand {
|
|
|
251
315
|
command: 'tag',
|
|
252
316
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
253
317
|
status: 'cancelled',
|
|
254
|
-
details: 'user declined force push'
|
|
318
|
+
details: 'user declined force push',
|
|
319
|
+
model: mergedModel
|
|
255
320
|
});
|
|
256
321
|
return;
|
|
257
322
|
}
|
|
@@ -268,7 +333,8 @@ export class TagCommand {
|
|
|
268
333
|
command: 'tag',
|
|
269
334
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
270
335
|
status: 'failure',
|
|
271
|
-
details: `tag force push to ${remote} failed
|
|
336
|
+
details: `tag force push to ${remote} failed`,
|
|
337
|
+
model: mergedModel
|
|
272
338
|
});
|
|
273
339
|
}
|
|
274
340
|
}
|
|
@@ -285,7 +351,8 @@ export class TagCommand {
|
|
|
285
351
|
command: 'tag',
|
|
286
352
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
287
353
|
status: 'failure',
|
|
288
|
-
details: `tag push to ${remote} failed
|
|
354
|
+
details: `tag push to ${remote} failed`,
|
|
355
|
+
model: mergedModel
|
|
289
356
|
});
|
|
290
357
|
}
|
|
291
358
|
}
|
|
@@ -296,7 +363,8 @@ export class TagCommand {
|
|
|
296
363
|
await LogService.append({
|
|
297
364
|
command: 'tag',
|
|
298
365
|
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
299
|
-
status: 'success'
|
|
366
|
+
status: 'success',
|
|
367
|
+
model: mergedModel
|
|
300
368
|
});
|
|
301
369
|
}
|
|
302
370
|
|
package/src/prompts/tag.ts
CHANGED
|
@@ -3,7 +3,9 @@ export type TagPromptLanguage = 'ko' | 'en';
|
|
|
3
3
|
export const generateTagPrompt = (
|
|
4
4
|
tagName: string,
|
|
5
5
|
customInstructions = '',
|
|
6
|
-
language: TagPromptLanguage = 'ko'
|
|
6
|
+
language: TagPromptLanguage = 'ko',
|
|
7
|
+
isImprovement = false,
|
|
8
|
+
hasStyleReference = false
|
|
7
9
|
): string => {
|
|
8
10
|
const titleInstruction = language === 'ko'
|
|
9
11
|
? `첫 줄에 버전 "${tagName}"을 제목으로 작성하세요.`
|
|
@@ -51,15 +53,49 @@ export const generateTagPrompt = (
|
|
|
51
53
|
? '릴리즈 노트를 한국어로 작성하세요.'
|
|
52
54
|
: 'Write the release notes in English.';
|
|
53
55
|
|
|
56
|
+
const improvementInstruction = isImprovement
|
|
57
|
+
? language === 'ko'
|
|
58
|
+
? `\n## 개선 모드
|
|
59
|
+
이전 릴리즈 노트가 제공됩니다. 이를 참고하여:
|
|
60
|
+
- 기존 내용의 구조와 스타일을 유지하면서 개선하세요.
|
|
61
|
+
- 누락된 변경사항이 있으면 추가하세요.
|
|
62
|
+
- 불필요하거나 중복된 내용은 제거하세요.
|
|
63
|
+
- 더 명확하고 사용자 친화적인 표현으로 다듬으세요.\n`
|
|
64
|
+
: `\n## Improvement Mode
|
|
65
|
+
Previous release notes are provided. Use them as reference to:
|
|
66
|
+
- Maintain the structure and style while improving the content.
|
|
67
|
+
- Add any missing changes from the commit log.
|
|
68
|
+
- Remove unnecessary or redundant content.
|
|
69
|
+
- Refine the language to be clearer and more user-friendly.\n`
|
|
70
|
+
: '';
|
|
71
|
+
|
|
72
|
+
const styleReferenceInstruction = hasStyleReference && !isImprovement
|
|
73
|
+
? language === 'ko'
|
|
74
|
+
? `\n## 스타일 참조
|
|
75
|
+
이전 태그의 릴리즈 노트가 스타일 참조용으로 제공됩니다.
|
|
76
|
+
- 제공된 형식과 구조를 따르세요.
|
|
77
|
+
- 제목, 카테고리, 항목 스타일을 일관되게 유지하세요.
|
|
78
|
+
- 언어 톤과 상세도 수준을 맞추세요.\n`
|
|
79
|
+
: `\n## Style Reference
|
|
80
|
+
A previous tag's release notes are provided as style reference.
|
|
81
|
+
- Follow the provided format and structure.
|
|
82
|
+
- Maintain consistency in title, categories, and item styles.
|
|
83
|
+
- Match the language tone and level of detail.\n`
|
|
84
|
+
: '';
|
|
85
|
+
|
|
54
86
|
return `You are an experienced release manager. Produce clear, user-facing release notes in GitHub Release style.
|
|
55
87
|
|
|
56
88
|
## Objective
|
|
57
|
-
|
|
89
|
+
${isImprovement
|
|
90
|
+
? `Improve the existing release notes for ${tagName} based on the commit history and previous notes.`
|
|
91
|
+
: `Create release notes for ${tagName} that describe the meaningful changes since the previous release.`}
|
|
58
92
|
|
|
59
93
|
## Input Context
|
|
60
94
|
- Target tag to publish: ${tagName}
|
|
61
95
|
- Commit history between the previous tag and ${tagName} will be supplied in the user message.
|
|
62
|
-
|
|
96
|
+
${isImprovement ? '- Previous release notes are provided for improvement.' : ''}
|
|
97
|
+
${hasStyleReference && !isImprovement ? '- Style reference from previous tag is provided.' : ''}
|
|
98
|
+
${improvementInstruction}${styleReferenceInstruction}
|
|
63
99
|
${customInstructions ? `## Additional Instructions\n${customInstructions}\n` : ''}
|
|
64
100
|
## Output Format (GitHub Release Style)
|
|
65
101
|
${titleInstruction}
|