@ksw8954/git-ai-commit 1.1.4 → 1.1.6
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 +1 -0
- package/CHANGELOG.md +17 -0
- package/dist/commands/ai.d.ts +4 -1
- package/dist/commands/ai.d.ts.map +1 -1
- package/dist/commands/ai.js +44 -46
- package/dist/commands/ai.js.map +1 -1
- package/dist/commands/git.d.ts +2 -0
- package/dist/commands/git.d.ts.map +1 -1
- package/dist/commands/git.js +30 -0
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/tag.d.ts +4 -0
- package/dist/commands/tag.d.ts.map +1 -1
- package/dist/commands/tag.js +149 -5
- package/dist/commands/tag.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/ai.test.ts +45 -68
- package/src/__tests__/tagCommand.test.ts +9 -2
- package/src/commands/ai.ts +55 -53
- package/src/commands/git.ts +35 -0
- package/src/commands/tag.ts +178 -6
package/src/commands/tag.ts
CHANGED
|
@@ -14,6 +14,13 @@ export interface TagOptions {
|
|
|
14
14
|
prompt?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
interface TagStyleMismatch {
|
|
18
|
+
newTag: string;
|
|
19
|
+
newPattern: string;
|
|
20
|
+
dominantPattern: string;
|
|
21
|
+
examples: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
export class TagCommand {
|
|
18
25
|
private program: Command;
|
|
19
26
|
|
|
@@ -106,9 +113,24 @@ export class TagCommand {
|
|
|
106
113
|
trimmedName = newVersion;
|
|
107
114
|
}
|
|
108
115
|
|
|
109
|
-
|
|
116
|
+
const styleMismatch = await this.checkTagStyleMismatch(trimmedName);
|
|
117
|
+
if (styleMismatch) {
|
|
118
|
+
const shouldProceed = await this.confirmStyleMismatch(styleMismatch);
|
|
119
|
+
if (!shouldProceed) {
|
|
120
|
+
console.log('Tag creation cancelled by user.');
|
|
121
|
+
await LogService.append({
|
|
122
|
+
command: 'tag',
|
|
123
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
124
|
+
status: 'cancelled',
|
|
125
|
+
details: `tag style mismatch: "${styleMismatch.newPattern}" vs "${styleMismatch.dominantPattern}"`,
|
|
126
|
+
model: mergedModel
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
110
132
|
const localTagExists = await GitService.tagExists(trimmedName);
|
|
111
|
-
let remoteTagExists =
|
|
133
|
+
let remoteTagExists = await GitService.remoteTagExists(trimmedName);
|
|
112
134
|
let previousTagMessage: string | null = null;
|
|
113
135
|
|
|
114
136
|
if (localTagExists) {
|
|
@@ -130,9 +152,6 @@ export class TagCommand {
|
|
|
130
152
|
return;
|
|
131
153
|
}
|
|
132
154
|
|
|
133
|
-
// Check if tag exists on remote
|
|
134
|
-
remoteTagExists = await GitService.remoteTagExists(trimmedName);
|
|
135
|
-
|
|
136
155
|
if (remoteTagExists) {
|
|
137
156
|
console.log(`⚠️ Tag ${trimmedName} also exists on remote.`);
|
|
138
157
|
const shouldDeleteRemote = await this.confirmRemoteTagDelete(trimmedName);
|
|
@@ -173,6 +192,28 @@ export class TagCommand {
|
|
|
173
192
|
return;
|
|
174
193
|
}
|
|
175
194
|
console.log(`✅ Local tag ${trimmedName} deleted`);
|
|
195
|
+
} else if (remoteTagExists) {
|
|
196
|
+
console.log(`⚠️ Tag ${trimmedName} exists on remote but not locally.`);
|
|
197
|
+
const shouldDeleteRemote = await this.confirmRemoteTagDelete(trimmedName);
|
|
198
|
+
|
|
199
|
+
if (shouldDeleteRemote) {
|
|
200
|
+
console.log(`Deleting remote tag ${trimmedName}...`);
|
|
201
|
+
const remoteDeleted = await GitService.deleteRemoteTag(trimmedName);
|
|
202
|
+
if (!remoteDeleted) {
|
|
203
|
+
console.error('❌ Failed to delete remote tag');
|
|
204
|
+
await LogService.append({
|
|
205
|
+
command: 'tag',
|
|
206
|
+
args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
|
|
207
|
+
status: 'failure',
|
|
208
|
+
details: 'remote tag deletion failed',
|
|
209
|
+
model: mergedModel
|
|
210
|
+
});
|
|
211
|
+
process.exit(1);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
console.log(`✅ Remote tag ${trimmedName} deleted`);
|
|
215
|
+
remoteTagExists = false;
|
|
216
|
+
}
|
|
176
217
|
}
|
|
177
218
|
|
|
178
219
|
let tagMessage = options.message?.trim();
|
|
@@ -200,7 +241,59 @@ export class TagCommand {
|
|
|
200
241
|
}
|
|
201
242
|
}
|
|
202
243
|
|
|
203
|
-
|
|
244
|
+
let historyResult = await GitService.getCommitSummariesSince(baseTag);
|
|
245
|
+
|
|
246
|
+
if (!historyResult.success && baseTag) {
|
|
247
|
+
console.log(`\n⚠️ No commits found since tag ${baseTag}.`);
|
|
248
|
+
|
|
249
|
+
const shouldDeleteBase = await this.confirmBaseTagDelete(baseTag);
|
|
250
|
+
if (shouldDeleteBase) {
|
|
251
|
+
const olderTagResult = await GitService.getTagBefore(baseTag);
|
|
252
|
+
const olderBase = olderTagResult.success ? olderTagResult.tag : undefined;
|
|
253
|
+
|
|
254
|
+
const localExists = await GitService.tagExists(baseTag);
|
|
255
|
+
if (localExists) {
|
|
256
|
+
const localDeleted = await GitService.deleteLocalTag(baseTag);
|
|
257
|
+
if (localDeleted) {
|
|
258
|
+
console.log(`✅ Local tag ${baseTag} deleted`);
|
|
259
|
+
} else {
|
|
260
|
+
console.error(`❌ Failed to delete local tag ${baseTag}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const remoteExists = await GitService.remoteTagExists(baseTag);
|
|
265
|
+
if (remoteExists) {
|
|
266
|
+
const shouldDeleteRemote = await this.confirmRemoteTagDelete(baseTag);
|
|
267
|
+
if (shouldDeleteRemote) {
|
|
268
|
+
const remoteDeleted = await GitService.deleteRemoteTag(baseTag);
|
|
269
|
+
if (remoteDeleted) {
|
|
270
|
+
console.log(`✅ Remote tag ${baseTag} deleted`);
|
|
271
|
+
} else {
|
|
272
|
+
console.error(`❌ Failed to delete remote tag ${baseTag}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (olderBase) {
|
|
278
|
+
console.log(`Using ${olderBase} as new base tag.`);
|
|
279
|
+
baseTag = olderBase;
|
|
280
|
+
} else {
|
|
281
|
+
console.log('No earlier tag found; using entire commit history.');
|
|
282
|
+
baseTag = undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
styleReferenceMessage = null;
|
|
286
|
+
if (baseTag && baseTag !== trimmedName) {
|
|
287
|
+
styleReferenceMessage = await GitService.getTagMessage(baseTag);
|
|
288
|
+
if (styleReferenceMessage) {
|
|
289
|
+
console.log(`Using ${baseTag} message as style reference.`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
historyResult = await GitService.getCommitSummariesSince(baseTag);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
204
297
|
if (!historyResult.success || !historyResult.log) {
|
|
205
298
|
console.error('Error:', historyResult.error ?? 'Unable to read commit history.');
|
|
206
299
|
await LogService.append({
|
|
@@ -368,6 +461,69 @@ export class TagCommand {
|
|
|
368
461
|
});
|
|
369
462
|
}
|
|
370
463
|
|
|
464
|
+
private extractTagPattern(tagName: string): string {
|
|
465
|
+
return tagName.replace(/[a-zA-Z]+/g, '{word}').replace(/\d+/g, '{n}');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private async checkTagStyleMismatch(newTagName: string): Promise<TagStyleMismatch | null> {
|
|
469
|
+
const recentTags = await GitService.getRecentTags(10);
|
|
470
|
+
if (recentTags.length === 0) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const newPattern = this.extractTagPattern(newTagName);
|
|
475
|
+
|
|
476
|
+
const patternCounts = new Map<string, string[]>();
|
|
477
|
+
for (const tag of recentTags) {
|
|
478
|
+
const pattern = this.extractTagPattern(tag);
|
|
479
|
+
const existing = patternCounts.get(pattern) || [];
|
|
480
|
+
existing.push(tag);
|
|
481
|
+
patternCounts.set(pattern, existing);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
let dominantPattern = '';
|
|
485
|
+
let maxCount = 0;
|
|
486
|
+
let dominantExamples: string[] = [];
|
|
487
|
+
for (const [pattern, tags] of patternCounts) {
|
|
488
|
+
if (tags.length > maxCount) {
|
|
489
|
+
maxCount = tags.length;
|
|
490
|
+
dominantPattern = pattern;
|
|
491
|
+
dominantExamples = tags;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (dominantPattern && dominantPattern !== newPattern && maxCount >= 2) {
|
|
496
|
+
return {
|
|
497
|
+
newTag: newTagName,
|
|
498
|
+
newPattern,
|
|
499
|
+
dominantPattern,
|
|
500
|
+
examples: dominantExamples.slice(0, 3)
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private async confirmStyleMismatch(mismatch: TagStyleMismatch): Promise<boolean> {
|
|
508
|
+
const rl = readline.createInterface({
|
|
509
|
+
input: process.stdin,
|
|
510
|
+
output: process.stdout
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
console.log(`\n⚠️ Tag name style mismatch detected.`);
|
|
514
|
+
console.log(` New tag: ${mismatch.newTag} (pattern: ${mismatch.newPattern})`);
|
|
515
|
+
console.log(` Recent tags: ${mismatch.examples.join(', ')} (pattern: ${mismatch.dominantPattern})`);
|
|
516
|
+
|
|
517
|
+
const answer: string = await new Promise(resolve => {
|
|
518
|
+
rl.question(`Proceed with "${mismatch.newTag}" anyway? (y/n): `, resolve);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
rl.close();
|
|
522
|
+
|
|
523
|
+
const normalized = answer.trim().toLowerCase();
|
|
524
|
+
return normalized === 'y' || normalized === 'yes';
|
|
525
|
+
}
|
|
526
|
+
|
|
371
527
|
private async selectRemotesForPush(tagName: string, remotes: string[]): Promise<string[] | null> {
|
|
372
528
|
const rl = readline.createInterface({
|
|
373
529
|
input: process.stdin,
|
|
@@ -463,6 +619,22 @@ export class TagCommand {
|
|
|
463
619
|
return normalized === 'y' || normalized === 'yes';
|
|
464
620
|
}
|
|
465
621
|
|
|
622
|
+
private async confirmBaseTagDelete(tagName: string): Promise<boolean> {
|
|
623
|
+
const rl = readline.createInterface({
|
|
624
|
+
input: process.stdin,
|
|
625
|
+
output: process.stdout
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const answer: string = await new Promise(resolve => {
|
|
629
|
+
rl.question(`Delete tag ${tagName} and use an older base? (y/n): `, resolve);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
rl.close();
|
|
633
|
+
|
|
634
|
+
const normalized = answer.trim().toLowerCase();
|
|
635
|
+
return normalized === 'y' || normalized === 'yes';
|
|
636
|
+
}
|
|
637
|
+
|
|
466
638
|
private async confirmForcePush(tagName: string): Promise<boolean> {
|
|
467
639
|
const rl = readline.createInterface({
|
|
468
640
|
input: process.stdin,
|