@ksw8954/git-ai-commit 1.1.5 → 1.1.7

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/commands/ai.d.ts +9 -1
  3. package/dist/commands/ai.d.ts.map +1 -1
  4. package/dist/commands/ai.js +104 -46
  5. package/dist/commands/ai.js.map +1 -1
  6. package/dist/commands/commit.d.ts.map +1 -1
  7. package/dist/commands/commit.js +2 -0
  8. package/dist/commands/commit.js.map +1 -1
  9. package/dist/commands/completion.js +1 -1
  10. package/dist/commands/config.d.ts +6 -0
  11. package/dist/commands/config.d.ts.map +1 -1
  12. package/dist/commands/config.js +13 -0
  13. package/dist/commands/config.js.map +1 -1
  14. package/dist/commands/configCommand.d.ts +2 -0
  15. package/dist/commands/configCommand.d.ts.map +1 -1
  16. package/dist/commands/configCommand.js +16 -0
  17. package/dist/commands/configCommand.js.map +1 -1
  18. package/dist/commands/git.d.ts +2 -0
  19. package/dist/commands/git.d.ts.map +1 -1
  20. package/dist/commands/git.js +30 -0
  21. package/dist/commands/git.js.map +1 -1
  22. package/dist/commands/prCommand.d.ts.map +1 -1
  23. package/dist/commands/prCommand.js +2 -0
  24. package/dist/commands/prCommand.js.map +1 -1
  25. package/dist/commands/tag.d.ts +4 -0
  26. package/dist/commands/tag.d.ts.map +1 -1
  27. package/dist/commands/tag.js +128 -2
  28. package/dist/commands/tag.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/__tests__/ai.test.ts +45 -68
  31. package/src/__tests__/tagCommand.test.ts +9 -2
  32. package/src/commands/ai.ts +131 -53
  33. package/src/commands/commit.ts +2 -0
  34. package/src/commands/completion.ts +1 -1
  35. package/src/commands/config.ts +19 -0
  36. package/src/commands/configCommand.ts +22 -0
  37. package/src/commands/git.ts +35 -0
  38. package/src/commands/prCommand.ts +2 -0
  39. package/src/commands/tag.ts +157 -2
@@ -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
 
@@ -59,6 +66,8 @@ export class TagCommand {
59
66
  apiKey: mergedApiKey!,
60
67
  baseURL: mergedBaseURL,
61
68
  model: mergedModel,
69
+ fallbackModel: storedConfig.fallbackModel,
70
+ reasoningEffort: storedConfig.reasoningEffort,
62
71
  language: storedConfig.language
63
72
  };
64
73
  }
@@ -106,7 +115,22 @@ export class TagCommand {
106
115
  trimmedName = newVersion;
107
116
  }
108
117
 
109
- // Check if tag already exists locally or on remote
118
+ const styleMismatch = await this.checkTagStyleMismatch(trimmedName);
119
+ if (styleMismatch) {
120
+ const shouldProceed = await this.confirmStyleMismatch(styleMismatch);
121
+ if (!shouldProceed) {
122
+ console.log('Tag creation cancelled by user.');
123
+ await LogService.append({
124
+ command: 'tag',
125
+ args: { name: trimmedName, ...options, apiKey: options.apiKey ? '***' : undefined },
126
+ status: 'cancelled',
127
+ details: `tag style mismatch: "${styleMismatch.newPattern}" vs "${styleMismatch.dominantPattern}"`,
128
+ model: mergedModel
129
+ });
130
+ return;
131
+ }
132
+ }
133
+
110
134
  const localTagExists = await GitService.tagExists(trimmedName);
111
135
  let remoteTagExists = await GitService.remoteTagExists(trimmedName);
112
136
  let previousTagMessage: string | null = null;
@@ -219,7 +243,59 @@ export class TagCommand {
219
243
  }
220
244
  }
221
245
 
222
- const historyResult = await GitService.getCommitSummariesSince(baseTag);
246
+ let historyResult = await GitService.getCommitSummariesSince(baseTag);
247
+
248
+ if (!historyResult.success && baseTag) {
249
+ console.log(`\n⚠️ No commits found since tag ${baseTag}.`);
250
+
251
+ const shouldDeleteBase = await this.confirmBaseTagDelete(baseTag);
252
+ if (shouldDeleteBase) {
253
+ const olderTagResult = await GitService.getTagBefore(baseTag);
254
+ const olderBase = olderTagResult.success ? olderTagResult.tag : undefined;
255
+
256
+ const localExists = await GitService.tagExists(baseTag);
257
+ if (localExists) {
258
+ const localDeleted = await GitService.deleteLocalTag(baseTag);
259
+ if (localDeleted) {
260
+ console.log(`✅ Local tag ${baseTag} deleted`);
261
+ } else {
262
+ console.error(`❌ Failed to delete local tag ${baseTag}`);
263
+ }
264
+ }
265
+
266
+ const remoteExists = await GitService.remoteTagExists(baseTag);
267
+ if (remoteExists) {
268
+ const shouldDeleteRemote = await this.confirmRemoteTagDelete(baseTag);
269
+ if (shouldDeleteRemote) {
270
+ const remoteDeleted = await GitService.deleteRemoteTag(baseTag);
271
+ if (remoteDeleted) {
272
+ console.log(`✅ Remote tag ${baseTag} deleted`);
273
+ } else {
274
+ console.error(`❌ Failed to delete remote tag ${baseTag}`);
275
+ }
276
+ }
277
+ }
278
+
279
+ if (olderBase) {
280
+ console.log(`Using ${olderBase} as new base tag.`);
281
+ baseTag = olderBase;
282
+ } else {
283
+ console.log('No earlier tag found; using entire commit history.');
284
+ baseTag = undefined;
285
+ }
286
+
287
+ styleReferenceMessage = null;
288
+ if (baseTag && baseTag !== trimmedName) {
289
+ styleReferenceMessage = await GitService.getTagMessage(baseTag);
290
+ if (styleReferenceMessage) {
291
+ console.log(`Using ${baseTag} message as style reference.`);
292
+ }
293
+ }
294
+
295
+ historyResult = await GitService.getCommitSummariesSince(baseTag);
296
+ }
297
+ }
298
+
223
299
  if (!historyResult.success || !historyResult.log) {
224
300
  console.error('Error:', historyResult.error ?? 'Unable to read commit history.');
225
301
  await LogService.append({
@@ -387,6 +463,69 @@ export class TagCommand {
387
463
  });
388
464
  }
389
465
 
466
+ private extractTagPattern(tagName: string): string {
467
+ return tagName.replace(/[a-zA-Z]+/g, '{word}').replace(/\d+/g, '{n}');
468
+ }
469
+
470
+ private async checkTagStyleMismatch(newTagName: string): Promise<TagStyleMismatch | null> {
471
+ const recentTags = await GitService.getRecentTags(10);
472
+ if (recentTags.length === 0) {
473
+ return null;
474
+ }
475
+
476
+ const newPattern = this.extractTagPattern(newTagName);
477
+
478
+ const patternCounts = new Map<string, string[]>();
479
+ for (const tag of recentTags) {
480
+ const pattern = this.extractTagPattern(tag);
481
+ const existing = patternCounts.get(pattern) || [];
482
+ existing.push(tag);
483
+ patternCounts.set(pattern, existing);
484
+ }
485
+
486
+ let dominantPattern = '';
487
+ let maxCount = 0;
488
+ let dominantExamples: string[] = [];
489
+ for (const [pattern, tags] of patternCounts) {
490
+ if (tags.length > maxCount) {
491
+ maxCount = tags.length;
492
+ dominantPattern = pattern;
493
+ dominantExamples = tags;
494
+ }
495
+ }
496
+
497
+ if (dominantPattern && dominantPattern !== newPattern && maxCount >= 2) {
498
+ return {
499
+ newTag: newTagName,
500
+ newPattern,
501
+ dominantPattern,
502
+ examples: dominantExamples.slice(0, 3)
503
+ };
504
+ }
505
+
506
+ return null;
507
+ }
508
+
509
+ private async confirmStyleMismatch(mismatch: TagStyleMismatch): Promise<boolean> {
510
+ const rl = readline.createInterface({
511
+ input: process.stdin,
512
+ output: process.stdout
513
+ });
514
+
515
+ console.log(`\n⚠️ Tag name style mismatch detected.`);
516
+ console.log(` New tag: ${mismatch.newTag} (pattern: ${mismatch.newPattern})`);
517
+ console.log(` Recent tags: ${mismatch.examples.join(', ')} (pattern: ${mismatch.dominantPattern})`);
518
+
519
+ const answer: string = await new Promise(resolve => {
520
+ rl.question(`Proceed with "${mismatch.newTag}" anyway? (y/n): `, resolve);
521
+ });
522
+
523
+ rl.close();
524
+
525
+ const normalized = answer.trim().toLowerCase();
526
+ return normalized === 'y' || normalized === 'yes';
527
+ }
528
+
390
529
  private async selectRemotesForPush(tagName: string, remotes: string[]): Promise<string[] | null> {
391
530
  const rl = readline.createInterface({
392
531
  input: process.stdin,
@@ -482,6 +621,22 @@ export class TagCommand {
482
621
  return normalized === 'y' || normalized === 'yes';
483
622
  }
484
623
 
624
+ private async confirmBaseTagDelete(tagName: string): Promise<boolean> {
625
+ const rl = readline.createInterface({
626
+ input: process.stdin,
627
+ output: process.stdout
628
+ });
629
+
630
+ const answer: string = await new Promise(resolve => {
631
+ rl.question(`Delete tag ${tagName} and use an older base? (y/n): `, resolve);
632
+ });
633
+
634
+ rl.close();
635
+
636
+ const normalized = answer.trim().toLowerCase();
637
+ return normalized === 'y' || normalized === 'yes';
638
+ }
639
+
485
640
  private async confirmForcePush(tagName: string): Promise<boolean> {
486
641
  const rl = readline.createInterface({
487
642
  input: process.stdin,