commic 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.
Files changed (50) hide show
  1. package/.husky/pre-commit +2 -0
  2. package/README.md +306 -0
  3. package/biome.json +50 -0
  4. package/dist/ai/AIService.d.ts +51 -0
  5. package/dist/ai/AIService.d.ts.map +1 -0
  6. package/dist/ai/AIService.js +351 -0
  7. package/dist/ai/AIService.js.map +1 -0
  8. package/dist/config/ConfigManager.d.ts +49 -0
  9. package/dist/config/ConfigManager.d.ts.map +1 -0
  10. package/dist/config/ConfigManager.js +124 -0
  11. package/dist/config/ConfigManager.js.map +1 -0
  12. package/dist/errors/CustomErrors.d.ts +54 -0
  13. package/dist/errors/CustomErrors.d.ts.map +1 -0
  14. package/dist/errors/CustomErrors.js +99 -0
  15. package/dist/errors/CustomErrors.js.map +1 -0
  16. package/dist/git/GitService.d.ts +77 -0
  17. package/dist/git/GitService.d.ts.map +1 -0
  18. package/dist/git/GitService.js +219 -0
  19. package/dist/git/GitService.js.map +1 -0
  20. package/dist/index.d.ts +3 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +48 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/orchestrator/MainOrchestrator.d.ts +63 -0
  25. package/dist/orchestrator/MainOrchestrator.d.ts.map +1 -0
  26. package/dist/orchestrator/MainOrchestrator.js +225 -0
  27. package/dist/orchestrator/MainOrchestrator.js.map +1 -0
  28. package/dist/types/index.d.ts +55 -0
  29. package/dist/types/index.d.ts.map +1 -0
  30. package/dist/types/index.js +2 -0
  31. package/dist/types/index.js.map +1 -0
  32. package/dist/ui/UIManager.d.ts +118 -0
  33. package/dist/ui/UIManager.d.ts.map +1 -0
  34. package/dist/ui/UIManager.js +369 -0
  35. package/dist/ui/UIManager.js.map +1 -0
  36. package/dist/validation/ConventionalCommitsValidator.d.ts +33 -0
  37. package/dist/validation/ConventionalCommitsValidator.d.ts.map +1 -0
  38. package/dist/validation/ConventionalCommitsValidator.js +114 -0
  39. package/dist/validation/ConventionalCommitsValidator.js.map +1 -0
  40. package/package.json +49 -0
  41. package/src/ai/AIService.ts +413 -0
  42. package/src/config/ConfigManager.ts +141 -0
  43. package/src/errors/CustomErrors.ts +176 -0
  44. package/src/git/GitService.ts +246 -0
  45. package/src/index.ts +55 -0
  46. package/src/orchestrator/MainOrchestrator.ts +263 -0
  47. package/src/types/index.ts +60 -0
  48. package/src/ui/UIManager.ts +420 -0
  49. package/src/validation/ConventionalCommitsValidator.ts +139 -0
  50. package/tsconfig.json +24 -0
@@ -0,0 +1,263 @@
1
+ import { AIService } from '../ai/AIService.js';
2
+ import type { ConfigManager } from '../config/ConfigManager.js';
3
+ import { APIError, ConfigurationError, GitRepositoryError } from '../errors/CustomErrors.js';
4
+ import type { GitService } from '../git/GitService.js';
5
+ import type { CLIOptions, Config } from '../types/index.js';
6
+ import type { UIManager } from '../ui/UIManager.js';
7
+
8
+ /**
9
+ * Orchestrates the complete workflow of the Commit CLI
10
+ * Coordinates between configuration, Git operations, AI generation, and user interaction
11
+ */
12
+ export class MainOrchestrator {
13
+ constructor(
14
+ private readonly configManager: ConfigManager,
15
+ private readonly gitService: GitService,
16
+ private readonly uiManager: UIManager
17
+ ) {}
18
+
19
+ /**
20
+ * Execute the complete commit workflow
21
+ * @param options CLI options from command line
22
+ */
23
+ async execute(options: CLIOptions): Promise<void> {
24
+ try {
25
+ // Show welcome message
26
+ this.uiManager.showWelcome();
27
+
28
+ // Step 1: Handle configuration
29
+ const config = await this.handleConfiguration(options.reconfigure);
30
+
31
+ // Step 2: Find and validate repository
32
+ const repositoryPath = options.path || '.';
33
+ const repository = await this.findAndValidateRepository(repositoryPath);
34
+
35
+ // Step 3: Get Git diff
36
+ const diff = await this.getGitDiff(repository.rootPath);
37
+
38
+ // Step 4: Generate commit suggestions
39
+ const suggestions = await this.generateSuggestions(config, diff);
40
+
41
+ // Step 5: Let user select a commit message
42
+ const selectedIndex = await this.uiManager.promptForCommitSelection(suggestions);
43
+
44
+ // Handle cancellation
45
+ if (selectedIndex === -1) {
46
+ this.uiManager.showInfo('Commit cancelled. No changes were made.');
47
+ return;
48
+ }
49
+
50
+ // Step 6: Stage changes if needed
51
+ await this.stageChangesIfNeeded(repository.rootPath, diff);
52
+
53
+ // Step 7: Execute commit
54
+ const commitHash = await this.executeCommit(
55
+ repository.rootPath,
56
+ suggestions[selectedIndex].message
57
+ );
58
+
59
+ // Step 8: Show success with more details
60
+ const shortHash = commitHash.substring(0, 7);
61
+ const repoName = this.gitService.getRepositoryName(repository.rootPath);
62
+ const branch = await this.gitService.getCurrentBranch(repository.rootPath);
63
+ this.uiManager.showSuccess(
64
+ `Committed successfully!\n` +
65
+ ` Repository: ${repoName}\n` +
66
+ ` Branch: ${branch}\n` +
67
+ ` Commit: ${this.uiManager.muted(shortHash)}`
68
+ );
69
+ } catch (error) {
70
+ this.handleError(error);
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Handle configuration loading or prompting
77
+ * @param forceReconfigure Force reconfiguration even if config exists
78
+ * @returns Configuration object
79
+ */
80
+ private async handleConfiguration(forceReconfigure?: boolean): Promise<Config> {
81
+ try {
82
+ // Check if we need to reconfigure
83
+ if (forceReconfigure) {
84
+ this.uiManager.showInfo('Reconfiguring...');
85
+ const config = await this.configManager.promptForConfig(this.uiManager);
86
+ await this.configManager.save(config);
87
+ this.uiManager.showSuccess('Configuration saved!');
88
+ return config;
89
+ }
90
+
91
+ // Try to load existing config
92
+ const existingConfig = await this.configManager.load();
93
+
94
+ if (existingConfig) {
95
+ return existingConfig;
96
+ }
97
+
98
+ // No config exists - prompt for first-time setup
99
+ this.uiManager.showInfo('First-time setup required');
100
+ const config = await this.configManager.promptForConfig(this.uiManager);
101
+ await this.configManager.save(config);
102
+ this.uiManager.showSuccess('Configuration saved!');
103
+ return config;
104
+ } catch (error) {
105
+ if (error instanceof ConfigurationError) {
106
+ throw error;
107
+ }
108
+ throw ConfigurationError.configSaveFailed(error as Error);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Find and validate Git repository
114
+ * @param path Path to search for repository
115
+ * @returns Repository information
116
+ */
117
+ private async findAndValidateRepository(path: string): Promise<{ rootPath: string }> {
118
+ const spinner = this.uiManager.showLoading('Finding Git repository...');
119
+
120
+ try {
121
+ const repository = await this.gitService.findRepository(path);
122
+
123
+ // Check if repository has commits
124
+ const hasCommits = await this.gitService.hasCommits(repository.rootPath);
125
+
126
+ if (!hasCommits) {
127
+ spinner.fail('Repository has no commits');
128
+ throw GitRepositoryError.noCommitsFound();
129
+ }
130
+
131
+ // Get repository info
132
+ const repoName = this.gitService.getRepositoryName(repository.rootPath);
133
+ const branch = await this.gitService.getCurrentBranch(repository.rootPath);
134
+ const remoteUrl = await this.gitService.getRemoteUrl(repository.rootPath);
135
+
136
+ spinner.succeed('Repository found');
137
+
138
+ // Show repository info with better spacing
139
+ this.uiManager.newLine();
140
+ this.uiManager.showRepositoryInfo(repoName, branch, repository.rootPath, remoteUrl);
141
+
142
+ return repository;
143
+ } catch (error) {
144
+ spinner.fail('Failed to find repository');
145
+ throw error;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Get Git diff and validate changes exist
151
+ * @param repoPath Repository root path
152
+ * @returns Git diff
153
+ */
154
+ private async getGitDiff(repoPath: string) {
155
+ const spinner = this.uiManager.showLoading('Analyzing changes...');
156
+
157
+ try {
158
+ const diff = await this.gitService.getDiff(repoPath);
159
+
160
+ if (!diff.hasChanges) {
161
+ spinner.fail('No changes to commit');
162
+ throw GitRepositoryError.noChanges();
163
+ }
164
+
165
+ // Get diff statistics
166
+ const stats = await this.gitService.getDiffStats(repoPath);
167
+
168
+ spinner.succeed('Changes analyzed');
169
+
170
+ // Show change statistics with better spacing
171
+ this.uiManager.showChangeStats(stats);
172
+
173
+ return diff;
174
+ } catch (error) {
175
+ spinner.fail('Failed to analyze changes');
176
+ throw error;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Generate commit message suggestions using AI
182
+ * @param config Configuration with API key
183
+ * @param diff Git diff
184
+ * @returns Array of suggestions
185
+ */
186
+ private async generateSuggestions(config: Config, diff: any) {
187
+ this.uiManager.newLine();
188
+ this.uiManager.showSectionHeader('🤖 AI Generation');
189
+ this.uiManager.showAIGenerationInfo(config.model, 4);
190
+
191
+ const spinner = this.uiManager.showLoading(' Generating commit messages...');
192
+
193
+ try {
194
+ const aiService = new AIService(config.apiKey, config.model);
195
+ const suggestions = await aiService.generateCommitMessages(diff, 4);
196
+
197
+ spinner.succeed(` Generated ${suggestions.length} suggestions`);
198
+ this.uiManager.newLine();
199
+
200
+ return suggestions;
201
+ } catch (error) {
202
+ spinner.fail(' Failed to generate suggestions');
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Stage changes if there are unstaged changes
209
+ * @param repoPath Repository root path
210
+ * @param diff Git diff
211
+ */
212
+ private async stageChangesIfNeeded(repoPath: string, diff: any): Promise<void> {
213
+ if (diff.unstaged && diff.unstaged.length > 0) {
214
+ this.uiManager.newLine();
215
+ const spinner = this.uiManager.showLoading('Staging changes...');
216
+ try {
217
+ await this.gitService.stageAll(repoPath);
218
+ spinner.succeed('Changes staged');
219
+ this.uiManager.newLine();
220
+ } catch (error) {
221
+ spinner.fail('Failed to stage changes');
222
+ throw error;
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Execute Git commit
229
+ * @param repoPath Repository root path
230
+ * @param message Commit message
231
+ * @returns Commit hash
232
+ */
233
+ private async executeCommit(repoPath: string, message: string): Promise<string> {
234
+ this.uiManager.newLine();
235
+ const spinner = this.uiManager.showLoading('Committing changes...');
236
+
237
+ try {
238
+ const commitHash = await this.gitService.commit(repoPath, message);
239
+ spinner.succeed('Commit created successfully');
240
+ this.uiManager.newLine();
241
+ return commitHash;
242
+ } catch (error) {
243
+ spinner.fail('Commit failed');
244
+ throw error;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Handle errors with user-friendly messages
250
+ * @param error Error to handle
251
+ */
252
+ private handleError(error: unknown): void {
253
+ if (
254
+ error instanceof GitRepositoryError ||
255
+ error instanceof ConfigurationError ||
256
+ error instanceof APIError
257
+ ) {
258
+ this.uiManager.showErrorWithSuggestion(error.message, error.suggestion || '');
259
+ } else {
260
+ this.uiManager.showError('An unexpected error occurred', (error as Error).message);
261
+ }
262
+ }
263
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Configuration structure stored in ~/.commit-cli/config.json
3
+ */
4
+ export interface Config {
5
+ apiKey: string;
6
+ model: string;
7
+ version: string;
8
+ }
9
+
10
+ /**
11
+ * Git repository information
12
+ */
13
+ export interface GitRepository {
14
+ path: string;
15
+ rootPath: string;
16
+ }
17
+
18
+ /**
19
+ * Git diff information
20
+ */
21
+ export interface GitDiff {
22
+ staged: string;
23
+ unstaged: string;
24
+ hasChanges: boolean;
25
+ }
26
+
27
+ /**
28
+ * Commit message suggestion
29
+ */
30
+ export interface CommitSuggestion {
31
+ message: string;
32
+ type: 'single-line' | 'multi-line';
33
+ }
34
+
35
+ /**
36
+ * Validation result for commit messages
37
+ */
38
+ export interface ValidationResult {
39
+ valid: boolean;
40
+ errors: string[];
41
+ }
42
+
43
+ /**
44
+ * Parsed commit message structure
45
+ */
46
+ export interface ParsedCommitMessage {
47
+ type: string;
48
+ scope?: string;
49
+ breaking: boolean;
50
+ description: string;
51
+ body?: string;
52
+ }
53
+
54
+ /**
55
+ * CLI options from command line arguments
56
+ */
57
+ export interface CLIOptions {
58
+ path?: string;
59
+ reconfigure?: boolean;
60
+ }