@ksw8954/git-ai-commit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +38 -0
- package/CRUSH.md +28 -0
- package/Makefile +32 -0
- package/README.md +145 -0
- package/dist/commands/ai.d.ts +35 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +206 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/commit.d.ts +17 -0
- package/dist/commands/commit.d.ts.map +1 -0
- package/dist/commands/commit.js +126 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/config.d.ts +33 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +141 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/configCommand.d.ts +20 -0
- package/dist/commands/configCommand.d.ts.map +1 -0
- package/dist/commands/configCommand.js +108 -0
- package/dist/commands/configCommand.js.map +1 -0
- package/dist/commands/git.d.ts +26 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +150 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/loadEnv.d.ts +2 -0
- package/dist/commands/loadEnv.d.ts.map +1 -0
- package/dist/commands/loadEnv.js +11 -0
- package/dist/commands/loadEnv.js.map +1 -0
- package/dist/commands/prCommand.d.ts +16 -0
- package/dist/commands/prCommand.d.ts.map +1 -0
- package/dist/commands/prCommand.js +61 -0
- package/dist/commands/prCommand.js.map +1 -0
- package/dist/commands/tag.d.ts +17 -0
- package/dist/commands/tag.d.ts.map +1 -0
- package/dist/commands/tag.js +127 -0
- package/dist/commands/tag.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/commit.d.ts +3 -0
- package/dist/prompts/commit.d.ts.map +1 -0
- package/dist/prompts/commit.js +101 -0
- package/dist/prompts/commit.js.map +1 -0
- package/dist/prompts/pr.d.ts +3 -0
- package/dist/prompts/pr.d.ts.map +1 -0
- package/dist/prompts/pr.js +58 -0
- package/dist/prompts/pr.js.map +1 -0
- package/dist/prompts/tag.d.ts +3 -0
- package/dist/prompts/tag.d.ts.map +1 -0
- package/dist/prompts/tag.js +42 -0
- package/dist/prompts/tag.js.map +1 -0
- package/eslint.config.js +35 -0
- package/jest.config.js +16 -0
- package/package.json +51 -0
- package/src/__tests__/ai.test.ts +185 -0
- package/src/__tests__/commitCommand.test.ts +155 -0
- package/src/__tests__/config.test.ts +238 -0
- package/src/__tests__/git.test.ts +88 -0
- package/src/__tests__/integration.test.ts +138 -0
- package/src/__tests__/prCommand.test.ts +121 -0
- package/src/__tests__/tagCommand.test.ts +197 -0
- package/src/commands/ai.ts +266 -0
- package/src/commands/commit.ts +215 -0
- package/src/commands/config.ts +182 -0
- package/src/commands/configCommand.ts +139 -0
- package/src/commands/git.ts +174 -0
- package/src/commands/history.ts +82 -0
- package/src/commands/loadEnv.ts +5 -0
- package/src/commands/log.ts +71 -0
- package/src/commands/prCommand.ts +108 -0
- package/src/commands/tag.ts +230 -0
- package/src/index.ts +29 -0
- package/src/prompts/commit.ts +105 -0
- package/src/prompts/pr.ts +64 -0
- package/src/prompts/tag.ts +48 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
import { AIService, AIServiceConfig } from './ai';
|
|
4
|
+
import { ConfigService } from './config';
|
|
5
|
+
import { GitService } from './git';
|
|
6
|
+
import { LogService } from './log';
|
|
7
|
+
|
|
8
|
+
export interface TagOptions {
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
baseTag?: string;
|
|
14
|
+
prompt?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class TagCommand {
|
|
18
|
+
private program: Command;
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.program = new Command('tag')
|
|
22
|
+
.description('Create an annotated git tag with optional AI-generated notes')
|
|
23
|
+
.argument('<name>', 'Tag name to create')
|
|
24
|
+
.option('-k, --api-key <key>', 'OpenAI API key (overrides env var)')
|
|
25
|
+
.option('--base-url <url>', 'Custom API base URL (overrides env var)')
|
|
26
|
+
.option('-m, --model <model>', 'Model to use (overrides env var)')
|
|
27
|
+
.option('--message <message>', 'Tag message to use directly (skips AI generation)')
|
|
28
|
+
.option('-t, --base-tag <tag>', 'Existing tag to diff against when generating notes')
|
|
29
|
+
.option('--prompt <text>', 'Additional instructions to append to the AI prompt for this tag')
|
|
30
|
+
.action(async (tagName: string, options: TagOptions) => {
|
|
31
|
+
await this.handleTag(tagName, options);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private resolveAIConfig(options: TagOptions): AIServiceConfig {
|
|
36
|
+
const storedConfig = ConfigService.getConfig();
|
|
37
|
+
|
|
38
|
+
const mergedApiKey = options.apiKey || storedConfig.apiKey;
|
|
39
|
+
const mergedBaseURL = options.baseUrl || storedConfig.baseURL;
|
|
40
|
+
const mergedModel = options.model || storedConfig.model;
|
|
41
|
+
|
|
42
|
+
ConfigService.validateConfig({
|
|
43
|
+
apiKey: mergedApiKey,
|
|
44
|
+
language: storedConfig.language
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
apiKey: mergedApiKey!,
|
|
49
|
+
baseURL: mergedBaseURL,
|
|
50
|
+
model: mergedModel,
|
|
51
|
+
language: storedConfig.language
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private async handleTag(tagName: string, options: TagOptions): Promise<void> {
|
|
56
|
+
const trimmedName = tagName?.trim();
|
|
57
|
+
|
|
58
|
+
if (!trimmedName) {
|
|
59
|
+
console.error('Tag name is required.');
|
|
60
|
+
await LogService.append({
|
|
61
|
+
command: 'tag',
|
|
62
|
+
args: { name: tagName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
63
|
+
status: 'failure',
|
|
64
|
+
details: 'missing tag name'
|
|
65
|
+
});
|
|
66
|
+
process.exit(1);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let tagMessage = options.message?.trim();
|
|
71
|
+
|
|
72
|
+
if (!tagMessage) {
|
|
73
|
+
console.log('Collecting commit history for tag notes...');
|
|
74
|
+
|
|
75
|
+
let baseTag = options.baseTag?.trim();
|
|
76
|
+
if (!baseTag) {
|
|
77
|
+
const latestTagResult = await GitService.getLatestTag();
|
|
78
|
+
if (latestTagResult.success && latestTagResult.tag) {
|
|
79
|
+
baseTag = latestTagResult.tag;
|
|
80
|
+
console.log(`Using latest tag ${baseTag} as base.`);
|
|
81
|
+
} else {
|
|
82
|
+
console.log('No existing tag found; using entire commit history.');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const historyResult = await GitService.getCommitSummariesSince(baseTag);
|
|
87
|
+
if (!historyResult.success || !historyResult.log) {
|
|
88
|
+
console.error('Error:', historyResult.error ?? 'Unable to read commit history.');
|
|
89
|
+
await LogService.append({
|
|
90
|
+
command: 'tag',
|
|
91
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
92
|
+
status: 'failure',
|
|
93
|
+
details: historyResult.error ?? 'Unable to read commit history.'
|
|
94
|
+
});
|
|
95
|
+
process.exit(1);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let aiConfig: AIServiceConfig;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
aiConfig = this.resolveAIConfig(options);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
console.error('Error:', message);
|
|
106
|
+
await LogService.append({
|
|
107
|
+
command: 'tag',
|
|
108
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
109
|
+
status: 'failure',
|
|
110
|
+
details: message
|
|
111
|
+
});
|
|
112
|
+
process.exit(1);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const aiService = new AIService(aiConfig);
|
|
117
|
+
const aiResult = await aiService.generateTagNotes(trimmedName, historyResult.log, options.prompt);
|
|
118
|
+
|
|
119
|
+
if (!aiResult.success || !aiResult.notes) {
|
|
120
|
+
console.error('Error:', aiResult.error ?? 'Failed to generate tag notes.');
|
|
121
|
+
await LogService.append({
|
|
122
|
+
command: 'tag',
|
|
123
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
124
|
+
status: 'failure',
|
|
125
|
+
details: aiResult.error ?? 'Failed to generate tag notes.'
|
|
126
|
+
});
|
|
127
|
+
process.exit(1);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
tagMessage = aiResult.notes;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Show preview and confirm before creating the tag
|
|
135
|
+
console.log('\nTag message preview:\n');
|
|
136
|
+
console.log(tagMessage);
|
|
137
|
+
|
|
138
|
+
const shouldCreate = await this.confirmTagCreate(trimmedName);
|
|
139
|
+
|
|
140
|
+
if (!shouldCreate) {
|
|
141
|
+
console.log('Tag creation cancelled by user.');
|
|
142
|
+
await LogService.append({
|
|
143
|
+
command: 'tag',
|
|
144
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
145
|
+
status: 'cancelled',
|
|
146
|
+
details: 'user declined tag creation'
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log(`Creating annotated tag ${trimmedName}...`);
|
|
152
|
+
const created = await GitService.createAnnotatedTag(trimmedName, tagMessage);
|
|
153
|
+
|
|
154
|
+
if (!created) {
|
|
155
|
+
console.error('❌ Failed to create tag');
|
|
156
|
+
await LogService.append({
|
|
157
|
+
command: 'tag',
|
|
158
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
159
|
+
status: 'failure',
|
|
160
|
+
details: 'git tag creation failed'
|
|
161
|
+
});
|
|
162
|
+
process.exit(1);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`✅ Tag ${trimmedName} created successfully!`);
|
|
167
|
+
|
|
168
|
+
const shouldPush = await this.confirmTagPush(trimmedName);
|
|
169
|
+
|
|
170
|
+
if (shouldPush) {
|
|
171
|
+
console.log(`Pushing tag ${trimmedName} to remote...`);
|
|
172
|
+
const pushSuccess = await GitService.pushTag(trimmedName);
|
|
173
|
+
|
|
174
|
+
if (pushSuccess) {
|
|
175
|
+
console.log(`✅ Tag ${trimmedName} pushed successfully!`);
|
|
176
|
+
} else {
|
|
177
|
+
console.error('❌ Failed to push tag to remote');
|
|
178
|
+
await LogService.append({
|
|
179
|
+
command: 'tag',
|
|
180
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
181
|
+
status: 'failure',
|
|
182
|
+
details: 'tag push failed'
|
|
183
|
+
});
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await LogService.append({
|
|
189
|
+
command: 'tag',
|
|
190
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
191
|
+
status: 'success'
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async confirmTagPush(tagName: string): Promise<boolean> {
|
|
196
|
+
const rl = readline.createInterface({
|
|
197
|
+
input: process.stdin,
|
|
198
|
+
output: process.stdout
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const answer: string = await new Promise(resolve => {
|
|
202
|
+
rl.question(`Push tag ${tagName} to remote? (y/n): `, resolve);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
rl.close();
|
|
206
|
+
|
|
207
|
+
const normalized = answer.trim().toLowerCase();
|
|
208
|
+
return normalized === 'y' || normalized === 'yes';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private async confirmTagCreate(tagName: string): Promise<boolean> {
|
|
212
|
+
const rl = readline.createInterface({
|
|
213
|
+
input: process.stdin,
|
|
214
|
+
output: process.stdout
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const answer: string = await new Promise(resolve => {
|
|
218
|
+
rl.question(`Create annotated tag ${tagName}? (y/n): `, resolve);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
rl.close();
|
|
222
|
+
|
|
223
|
+
const normalized = answer.trim().toLowerCase();
|
|
224
|
+
return normalized === 'y' || normalized === 'yes';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
getCommand(): Command {
|
|
228
|
+
return this.program;
|
|
229
|
+
}
|
|
230
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { CommitCommand } from './commands/commit';
|
|
5
|
+
import { ConfigCommand } from './commands/configCommand';
|
|
6
|
+
import { PullRequestCommand } from './commands/prCommand';
|
|
7
|
+
import { TagCommand } from './commands/tag';
|
|
8
|
+
import { HistoryCommand } from './commands/history';
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('git-ai-commit')
|
|
14
|
+
.description('AI-powered git commit message generator')
|
|
15
|
+
.version('1.0.0');
|
|
16
|
+
|
|
17
|
+
const commitCommand = new CommitCommand();
|
|
18
|
+
const configCommand = new ConfigCommand();
|
|
19
|
+
const pullRequestCommand = new PullRequestCommand();
|
|
20
|
+
const tagCommand = new TagCommand();
|
|
21
|
+
const historyCommand = new HistoryCommand();
|
|
22
|
+
|
|
23
|
+
program.addCommand(commitCommand.getCommand());
|
|
24
|
+
program.addCommand(configCommand.getCommand());
|
|
25
|
+
program.addCommand(pullRequestCommand.getCommand());
|
|
26
|
+
program.addCommand(tagCommand.getCommand());
|
|
27
|
+
program.addCommand(historyCommand.getCommand());
|
|
28
|
+
|
|
29
|
+
program.parse();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export type CommitPromptLanguage = 'ko' | 'en';
|
|
2
|
+
|
|
3
|
+
export const generateCommitPrompt = (
|
|
4
|
+
gitContext: string,
|
|
5
|
+
customInstructions = '',
|
|
6
|
+
language: CommitPromptLanguage = 'ko'
|
|
7
|
+
): string => {
|
|
8
|
+
const languageRequirement = language === 'ko'
|
|
9
|
+
? `## Korean Language Requirement
|
|
10
|
+
- All commit messages MUST be written in Korean (한글)
|
|
11
|
+
- Description, body, and footer text must use Korean
|
|
12
|
+
- Keep Korean messages concise`
|
|
13
|
+
: `## English Language Requirement
|
|
14
|
+
- All commit messages MUST be written in English
|
|
15
|
+
- Description, body, and footer text must use English
|
|
16
|
+
- Keep the tone concise and professional`;
|
|
17
|
+
|
|
18
|
+
const bodyGuidelines = language === 'ko'
|
|
19
|
+
? `### Body Guidelines (Optional)
|
|
20
|
+
- Insert one blank line after the description and express additional details as
|
|
21
|
+
markdown bullet points (\`- \`) written in Korean.
|
|
22
|
+
- Each bullet should explain the "what" and "why", not the "how".
|
|
23
|
+
- Wrap at 72 characters per line and keep bullets concise.
|
|
24
|
+
- Use the bullet body only for complex changes that need clarification; omit it
|
|
25
|
+
when the summary line is sufficient.`
|
|
26
|
+
: `### Body Guidelines (Optional)
|
|
27
|
+
- Insert one blank line after the description and use markdown bullet points (\`- \`) in English.
|
|
28
|
+
- Each bullet should explain the "what" and "why", not the "how".
|
|
29
|
+
- Wrap at 72 characters per line and keep bullets concise.
|
|
30
|
+
- Include a body only for complex changes that need clarification.`;
|
|
31
|
+
|
|
32
|
+
const footerGuidelines = language === 'ko'
|
|
33
|
+
? `### Footer Guidelines (Optional)
|
|
34
|
+
- Start one blank line after body
|
|
35
|
+
- **Breaking Changes**: \`BREAKING CHANGE: description\``
|
|
36
|
+
: `### Footer Guidelines (Optional)
|
|
37
|
+
- Start one blank line after the body
|
|
38
|
+
- **Breaking Changes**: \`BREAKING CHANGE: description\``;
|
|
39
|
+
|
|
40
|
+
return `# Conventional Commit Message Generator
|
|
41
|
+
## System Instructions
|
|
42
|
+
You are an expert Git commit message generator that creates conventional commit messages based on staged changes. Analyze the provided git diff output and generate appropriate conventional commit messages following the specification.
|
|
43
|
+
|
|
44
|
+
${customInstructions}
|
|
45
|
+
|
|
46
|
+
## CRITICAL: Commit Message Output Rules
|
|
47
|
+
- DO NOT include any memory bank status indicators like "[Memory Bank: Active]" or "[Memory Bank: Missing]"
|
|
48
|
+
- DO NOT include any task-specific formatting or artifacts from other rules
|
|
49
|
+
- ONLY Generate a clean conventional commit message as specified below
|
|
50
|
+
|
|
51
|
+
${gitContext}
|
|
52
|
+
|
|
53
|
+
## Conventional Commits Format
|
|
54
|
+
Generate commit messages following this exact structure:
|
|
55
|
+
\n<type>[optional scope]: <description>
|
|
56
|
+
[optional body]
|
|
57
|
+
[optional footer(s)]
|
|
58
|
+
|
|
59
|
+
### Core Types (Required)
|
|
60
|
+
- **feat**: New feature or functionality (MINOR version bump)
|
|
61
|
+
- **fix**: Bug fix or error correction (PATCH version bump)
|
|
62
|
+
|
|
63
|
+
### Additional Types (Extended)
|
|
64
|
+
- **docs**: Documentation changes only
|
|
65
|
+
- **style**: Code style changes (whitespace, formatting, semicolons, etc.)
|
|
66
|
+
- **refactor**: Code refactoring without feature changes or bug fixes
|
|
67
|
+
- **perf**: Performance improvements
|
|
68
|
+
- **test**: Adding or fixing tests
|
|
69
|
+
- **build**: Build system or external dependency changes
|
|
70
|
+
- **ci**: CI/CD configuration changes
|
|
71
|
+
- **chore**: Maintenance tasks, tooling changes
|
|
72
|
+
- **revert**: Reverting previous commits
|
|
73
|
+
|
|
74
|
+
### Scope Guidelines
|
|
75
|
+
- Use parentheses: \`feat(api):\`, \`fix(ui):\`
|
|
76
|
+
- Common scopes: \`api\`, \`ui\`, \`auth\`, \`db\`, \`config\`, \`deps\`, \`docs\`
|
|
77
|
+
- For monorepos: package or module names
|
|
78
|
+
- Keep scope concise and lowercase
|
|
79
|
+
|
|
80
|
+
### Description Rules
|
|
81
|
+
- Use imperative mood ("add" not "added" or "adds")
|
|
82
|
+
- Start with lowercase letter
|
|
83
|
+
- No period at the end
|
|
84
|
+
- Be concise but descriptive
|
|
85
|
+
- Must be written as a single line without line breaks
|
|
86
|
+
|
|
87
|
+
${languageRequirement}
|
|
88
|
+
|
|
89
|
+
${bodyGuidelines}
|
|
90
|
+
|
|
91
|
+
${footerGuidelines}
|
|
92
|
+
|
|
93
|
+
## Analysis Instructions
|
|
94
|
+
When analyzing staged changes:
|
|
95
|
+
1. Determine Primary Type based on the nature of changes
|
|
96
|
+
2. Identify Scope from modified directories or modules
|
|
97
|
+
3. Craft Description focusing on the most significant change
|
|
98
|
+
4. Determine if there are Breaking Changes
|
|
99
|
+
5. For complex changes, include a detailed body explaining what and why
|
|
100
|
+
6. Add appropriate footers for issue references or breaking changes
|
|
101
|
+
|
|
102
|
+
For significant changes, include a detailed body explaining the changes.
|
|
103
|
+
|
|
104
|
+
Return ONLY the commit message in the conventional format, nothing else.`;
|
|
105
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type PullRequestPromptLanguage = 'ko' | 'en';
|
|
2
|
+
|
|
3
|
+
export const generatePullRequestPrompt = (
|
|
4
|
+
baseBranch: string,
|
|
5
|
+
compareBranch: string,
|
|
6
|
+
customInstructions = '',
|
|
7
|
+
language: PullRequestPromptLanguage = 'ko'
|
|
8
|
+
): string => {
|
|
9
|
+
const languageRequirement = language === 'ko'
|
|
10
|
+
? 'Write the entire pull request title and body in Korean.'
|
|
11
|
+
: 'Write the entire pull request title and body in English.';
|
|
12
|
+
|
|
13
|
+
const titleGuidelines = language === 'ko'
|
|
14
|
+
? `### 제목 작성 규칙
|
|
15
|
+
- 한 줄짜리 명령형 문장으로 작성합니다 (예: "Refactor validator 로직 정리").
|
|
16
|
+
- 접두사는 사용하지 않습니다 (예: "Feat:" 금지).
|
|
17
|
+
- 72자를 넘지 않도록 합니다.`
|
|
18
|
+
: `### Title Guidelines
|
|
19
|
+
- Use a single imperative sentence (e.g., "Refactor validator handling").
|
|
20
|
+
- Do not prefix with labels like "Feat:".
|
|
21
|
+
- Keep the title under 72 characters.`;
|
|
22
|
+
|
|
23
|
+
const summaryGuidelines = language === 'ko'
|
|
24
|
+
? `### 본문 구성
|
|
25
|
+
- "## Summary" 헤딩 아래에 핵심 변경 사항을 강조하는 불릿 리스트를 작성합니다.
|
|
26
|
+
- 각 불릿은 "무엇을"과 "왜"를 포함하고, 한국어로 1줄 내로 작성합니다.
|
|
27
|
+
- 영향이 큰 변경은 별도의 불릿으로 구분합니다.`
|
|
28
|
+
: `### Body Structure
|
|
29
|
+
- Under a "## Summary" heading, add bullet points that explain what changed and why.
|
|
30
|
+
- Keep each bullet to a single concise English sentence.
|
|
31
|
+
- Separate large areas of impact into individual bullets.`;
|
|
32
|
+
|
|
33
|
+
const testingGuidelines = language === 'ko'
|
|
34
|
+
? `### 테스트 정보
|
|
35
|
+
- "## Testing" 헤딩 아래에 검증 방법을 불릿으로 정리합니다.
|
|
36
|
+
- 수동 테스트, 자동 테스트, 혹은 "테스트 필요 없음"을 명시합니다.`
|
|
37
|
+
: `### Testing Details
|
|
38
|
+
- Under a "## Testing" heading, list how the changes were verified.
|
|
39
|
+
- Call out manual steps, automated checks, or "Not tested" when applicable.`;
|
|
40
|
+
|
|
41
|
+
return `You are an expert software engineer preparing a pull request description.
|
|
42
|
+
Compare the git history and code changes between the base branch "${baseBranch}" and the compare branch "${compareBranch}" and summarise the meaningful differences in a PR-friendly format.
|
|
43
|
+
|
|
44
|
+
${customInstructions}
|
|
45
|
+
|
|
46
|
+
## Output Contract
|
|
47
|
+
- Return ONLY markdown suitable for pasting into a pull request form.
|
|
48
|
+
- First line MUST be the pull request title, adhering to the title guidelines below.
|
|
49
|
+
- After one blank line, include the sections exactly as shown:
|
|
50
|
+
- "## Summary"
|
|
51
|
+
- "## Testing"
|
|
52
|
+
- If a section has no content, provide a single bullet: "- 없음" in Korean or "- None" in English (use language-appropriate wording).
|
|
53
|
+
- Do not invent changes that are not present in the provided diff.
|
|
54
|
+
|
|
55
|
+
${languageRequirement}
|
|
56
|
+
|
|
57
|
+
${titleGuidelines}
|
|
58
|
+
|
|
59
|
+
${summaryGuidelines}
|
|
60
|
+
|
|
61
|
+
${testingGuidelines}
|
|
62
|
+
|
|
63
|
+
Focus on user-facing impact, breaking changes, and notable refactors. Avoid raw diff dumps.`;
|
|
64
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type TagPromptLanguage = 'ko' | 'en';
|
|
2
|
+
|
|
3
|
+
export const generateTagPrompt = (
|
|
4
|
+
tagName: string,
|
|
5
|
+
customInstructions = '',
|
|
6
|
+
language: TagPromptLanguage = 'ko'
|
|
7
|
+
): string => {
|
|
8
|
+
const summaryInstruction = language === 'ko'
|
|
9
|
+
? 'Begin with a short summary sentence (in Korean) that captures the overall impact of the release.'
|
|
10
|
+
: 'Begin with a short summary sentence (in English) that captures the overall impact of the release.';
|
|
11
|
+
|
|
12
|
+
const listInstruction = language === 'ko'
|
|
13
|
+
? 'After the summary, list every change as a markdown bullet (-) grouped by category: 사용자 기능, 버그 수정, 유지 보수.'
|
|
14
|
+
: 'After the summary, list every change as a markdown bullet (-) grouped by category: User Features, Bug Fixes, Maintenance.';
|
|
15
|
+
|
|
16
|
+
const emptyCategoryInstruction = language === 'ko'
|
|
17
|
+
? 'If a category has no changes, include a bullet stating - 해당 사항 없음.'
|
|
18
|
+
: 'If a category has no changes, include a bullet stating - None.';
|
|
19
|
+
|
|
20
|
+
const noChangesInstruction = language === 'ko'
|
|
21
|
+
? 'If no changes exist at all, state "변경 사항 없음" plainly.'
|
|
22
|
+
: 'If no changes exist at all, state "No changes to report." plainly.';
|
|
23
|
+
|
|
24
|
+
const outputLanguageLine = language === 'ko'
|
|
25
|
+
? 'Write the release notes in Korean using concise markdown.'
|
|
26
|
+
: 'Write the release notes in English using concise markdown.';
|
|
27
|
+
|
|
28
|
+
return `You are an experienced release manager. Produce clear, user-facing release notes that describe the differences between the previous tag and ${tagName}.
|
|
29
|
+
|
|
30
|
+
## Objective
|
|
31
|
+
Summarize the meaningful changes that occurred between the prior release tag and ${tagName}. Treat the commit log provided by the user message as the complete history of changes since the previous tag.
|
|
32
|
+
|
|
33
|
+
## Input Context
|
|
34
|
+
- Target tag to publish: ${tagName}
|
|
35
|
+
- Commit history between the previous tag and ${tagName} will be supplied in the user message (most recent first).
|
|
36
|
+
|
|
37
|
+
${customInstructions}
|
|
38
|
+
|
|
39
|
+
## Output Requirements
|
|
40
|
+
- ${outputLanguageLine}
|
|
41
|
+
- ${summaryInstruction}
|
|
42
|
+
- ${listInstruction}
|
|
43
|
+
- Use short phrases for each bullet and include scope/component names when helpful, without copying commit messages verbatim.
|
|
44
|
+
- ${emptyCategoryInstruction}
|
|
45
|
+
- ${noChangesInstruction}
|
|
46
|
+
- Do not invent work beyond what appears in the commit log.
|
|
47
|
+
- Return only the release notes content with no surrounding commentary.`;
|
|
48
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"resolveJsonModule": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
19
|
+
}
|