gitpt 1.1.0 → 1.2.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/README.md +36 -26
- package/dist/commands/commit.js +25 -0
- package/dist/utils/api.d.ts +1 -1
- package/dist/utils/api.js +6 -3
- package/dist/utils/commitlint.d.ts +24 -0
- package/dist/utils/commitlint.js +124 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -6,10 +6,15 @@ Git Prompt Tool is a CLI tool that helps you write commit messages using AI thro
|
|
|
6
6
|
|
|
7
7
|
- Acts as a complete git replacement - all git commands are supported
|
|
8
8
|
- Generate commit messages with AI based on your code changes
|
|
9
|
+
|
|
10
|
+
`gitpt commit`
|
|
9
11
|
- Create pull requests with AI-generated titles and descriptions
|
|
12
|
+
|
|
13
|
+
`gitpt pr create`
|
|
10
14
|
- Compatible with all regular git options (flags, arguments, etc.)
|
|
11
15
|
- Edit suggested messages before committing
|
|
12
16
|
- Works with various AI models via OpenRouter
|
|
17
|
+
- [Commitlint](https://commitlint.js.org/) support - read directly from your repository
|
|
13
18
|
|
|
14
19
|
## Installation
|
|
15
20
|
|
|
@@ -59,14 +64,8 @@ gitpt merge --no-ff feature-branch
|
|
|
59
64
|
Add files to the staging area just like you would with git:
|
|
60
65
|
|
|
61
66
|
```bash
|
|
62
|
-
# Same as git add .
|
|
67
|
+
# Same as git add . (supports all the regular git add options)
|
|
63
68
|
gitpt add .
|
|
64
|
-
|
|
65
|
-
# Same as git add -p
|
|
66
|
-
gitpt add -p
|
|
67
|
-
|
|
68
|
-
# Same as git add src/*.ts
|
|
69
|
-
gitpt add src/*.ts
|
|
70
69
|
```
|
|
71
70
|
|
|
72
71
|
### Creating Commits
|
|
@@ -75,14 +74,22 @@ Generate an AI-powered commit message based on your staged changes:
|
|
|
75
74
|
|
|
76
75
|
```bash
|
|
77
76
|
gitpt commit
|
|
77
|
+
|
|
78
|
+
# Or supply -m argument, if you want to avoid gitpt generating the message
|
|
79
|
+
gitpt commit -m "feat: file hash validation"
|
|
80
|
+
|
|
81
|
+
# Pass any other git commit options
|
|
82
|
+
gitpt commit --amend
|
|
78
83
|
```
|
|
79
84
|
|
|
80
85
|
The tool will:
|
|
81
86
|
1. Analyze your staged changes
|
|
82
87
|
2. Generate a commit message using the configured AI model
|
|
83
|
-
3.
|
|
84
|
-
4.
|
|
85
|
-
5.
|
|
88
|
+
3. Validate against commitlint rules (if configured)
|
|
89
|
+
4. Regenerate the message if it fails validation
|
|
90
|
+
5. Show you the suggested message
|
|
91
|
+
6. Let you edit the message before committing
|
|
92
|
+
7. Create the commit with your approved message
|
|
86
93
|
|
|
87
94
|
### Changing Models
|
|
88
95
|
|
|
@@ -99,20 +106,9 @@ gitpt model openai/gpt-4o
|
|
|
99
106
|
gitpt model anthropic/claude-3-haiku
|
|
100
107
|
```
|
|
101
108
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
You can use any standard git commit options with the `gitpt commit` command:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
# Skip editing the message
|
|
108
|
-
gitpt commit --no-edit
|
|
109
|
-
|
|
110
|
-
# Provide your own message instead of generating one
|
|
111
|
-
gitpt commit -m "Your message here"
|
|
109
|
+
## GitHub Usage
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
gitpt commit --amend
|
|
115
|
-
```
|
|
111
|
+
If you have GitHub CLI (`gh`) installed, you can use GitPT to interact with GitHub (e.g. generate full pull requests).
|
|
116
112
|
|
|
117
113
|
### Creating Pull Requests
|
|
118
114
|
|
|
@@ -145,8 +141,6 @@ gitpt pr create --no-edit
|
|
|
145
141
|
gitpt pr create --title "Your PR title here"
|
|
146
142
|
```
|
|
147
143
|
|
|
148
|
-
> **Note:** This command requires GitHub CLI (`gh`) to be installed and authenticated.
|
|
149
|
-
|
|
150
144
|
## How It Works
|
|
151
145
|
|
|
152
146
|
GitPT leverages AI via OpenRouter to enhance your Git workflow while acting as a complete git wrapper:
|
|
@@ -155,6 +149,8 @@ GitPT leverages AI via OpenRouter to enhance your Git workflow while acting as a
|
|
|
155
149
|
|
|
156
150
|
- **For commits:** Sends a diff of your staged changes to the AI, which generates a contextual commit message following best practices.
|
|
157
151
|
|
|
152
|
+
- **Commitlint Integration:** Automatically detects commitlint configuration files and validates generated commit messages against your project's commit conventions. If validation fails, it regenerates a compliant message.
|
|
153
|
+
|
|
158
154
|
- **For pull requests:** Analyzes the commits and file changes between your branch and the base branch, then generates a suitable title and detailed description for your PR.
|
|
159
155
|
|
|
160
156
|
- **For other git commands:** Passes them through directly to git with all arguments and options preserved, ensuring complete compatibility with your existing git workflow.
|
|
@@ -176,6 +172,20 @@ npm run build
|
|
|
176
172
|
npm link
|
|
177
173
|
```
|
|
178
174
|
|
|
175
|
+
## Commitlint Integration
|
|
176
|
+
|
|
177
|
+
GitPT automatically detects and integrates with [commitlint](https://commitlint.js.org/) if it's configured in your repository:
|
|
178
|
+
|
|
179
|
+
- **Automatic Detection:** GitPT checks for common commitlint configuration files (commitlint.config.js, .commitlintrc.*, etc.)
|
|
180
|
+
|
|
181
|
+
- **Rule-Aware Generation:** When commitlint is detected, GitPT instructs the AI to generate messages that follow your specific commit conventions
|
|
182
|
+
|
|
183
|
+
- **Validation & Regeneration:** Generated messages are validated against your commitlint rules before committing. If validation fails, GitPT automatically regenerates a compliant message
|
|
184
|
+
|
|
185
|
+
- **Error Feedback:** Validation errors are sent to the AI to help it understand how to fix the message
|
|
186
|
+
|
|
187
|
+
This integration ensures that all AI-generated commit messages follow your team's established commit conventions without requiring manual corrections.
|
|
188
|
+
|
|
179
189
|
## License
|
|
180
190
|
|
|
181
|
-
MIT
|
|
191
|
+
MIT
|
package/dist/commands/commit.js
CHANGED
|
@@ -3,6 +3,7 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { isGitRepository, hasStagedChanges, getStagedChanges, executeGitCommit } from '../utils/git.js';
|
|
4
4
|
import { generateCommitMessage } from '../utils/api.js';
|
|
5
5
|
import { getConfig } from '../utils/config.js';
|
|
6
|
+
import { hasCommitlintConfig, validateCommitMessage } from '../utils/commitlint.js';
|
|
6
7
|
export async function commitCommand(options) {
|
|
7
8
|
if (!isGitRepository()) {
|
|
8
9
|
console.error(chalk.red('Error: Not a git repository'));
|
|
@@ -29,8 +30,32 @@ export async function commitCommand(options) {
|
|
|
29
30
|
// Get staged changes
|
|
30
31
|
const diff = getStagedChanges();
|
|
31
32
|
console.log(chalk.blue('Generating commit message...'));
|
|
33
|
+
// Check if commitlint is configured
|
|
34
|
+
if (hasCommitlintConfig()) {
|
|
35
|
+
console.log(chalk.blue('Commitlint configuration detected. Generating message according to rules...'));
|
|
36
|
+
}
|
|
32
37
|
// Generate commit message
|
|
33
38
|
commitMessage = await generateCommitMessage(diff);
|
|
39
|
+
// If commitlint is configured, validate the message
|
|
40
|
+
if (hasCommitlintConfig()) {
|
|
41
|
+
console.log(chalk.blue('Validating commit message against commitlint rules...'));
|
|
42
|
+
const validation = await validateCommitMessage(commitMessage);
|
|
43
|
+
if (!validation.valid && validation.errors) {
|
|
44
|
+
console.log(chalk.yellow('Commit message failed validation. Regenerating...'));
|
|
45
|
+
console.log(chalk.gray(validation.errors));
|
|
46
|
+
// Regenerate with validation errors
|
|
47
|
+
commitMessage = await generateCommitMessage(diff, validation.errors);
|
|
48
|
+
// Validate again
|
|
49
|
+
const revalidation = await validateCommitMessage(commitMessage);
|
|
50
|
+
if (!revalidation.valid) {
|
|
51
|
+
console.log(chalk.yellow('Warning: Regenerated message still has validation issues.'));
|
|
52
|
+
console.log(chalk.gray(revalidation.errors));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log(chalk.green('✓ Commit message passed validation'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
34
59
|
console.log(chalk.green('✓ Commit message generated'));
|
|
35
60
|
console.log('');
|
|
36
61
|
console.log(chalk.cyan('Generated message:'));
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function generateCommitMessage(diff: string): Promise<string>;
|
|
1
|
+
export declare function generateCommitMessage(diff: string, validationErrors?: string): Promise<string>;
|
package/dist/utils/api.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import fetch from "node-fetch";
|
|
2
2
|
import { getConfig } from "./config.js";
|
|
3
|
+
import { hasCommitlintConfig, getCommitlintRules } from "./commitlint.js";
|
|
3
4
|
const OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
|
|
4
|
-
export async function generateCommitMessage(diff) {
|
|
5
|
+
export async function generateCommitMessage(diff, validationErrors) {
|
|
6
|
+
// Check if commitlint is configured
|
|
7
|
+
const hasCommitlint = hasCommitlintConfig();
|
|
5
8
|
const config = getConfig();
|
|
6
9
|
if (!config) {
|
|
7
10
|
throw new Error('GitPT is not configured. Please run "gitpt setup" first.');
|
|
@@ -12,7 +15,7 @@ export async function generateCommitMessage(diff) {
|
|
|
12
15
|
role: "system",
|
|
13
16
|
content: `You are a helpful assistant that generates concise, informative Git commit messages.
|
|
14
17
|
Follow these strict rules:
|
|
15
|
-
1. Use conventional commit format: type: description
|
|
18
|
+
${hasCommitlint ? getCommitlintRules() : `1. Use conventional commit format: type: description
|
|
16
19
|
2. Types are: feat, fix, docs, style, refactor, test, chore
|
|
17
20
|
3. NO scopes in parentheses - do not use feat(scope)
|
|
18
21
|
4. Keep the entire message under 100 characters
|
|
@@ -23,7 +26,7 @@ export async function generateCommitMessage(diff) {
|
|
|
23
26
|
- feat: add user authentication
|
|
24
27
|
- fix: resolve null pointer in login
|
|
25
28
|
- chore: update dependencies
|
|
26
|
-
- style: format css files`,
|
|
29
|
+
- style: format css files`}${validationErrors ? `\n\nYOUR PREVIOUS MESSAGE FAILED VALIDATION WITH THESE ERRORS:\n${validationErrors}\n\nFIX THESE ISSUES IN YOUR NEW MESSAGE.` : ''}`,
|
|
27
30
|
},
|
|
28
31
|
{
|
|
29
32
|
role: "user",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a parsed commitlint configuration
|
|
3
|
+
*/
|
|
4
|
+
export interface CommitlintConfig {
|
|
5
|
+
rules?: Record<string, any>;
|
|
6
|
+
extends?: string | string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Check if a commitlint configuration exists in the repository
|
|
10
|
+
*/
|
|
11
|
+
export declare function hasCommitlintConfig(): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Get the commitlint configuration format rules as a string
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCommitlintRules(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Validate a commit message against commitlint rules
|
|
18
|
+
* @param message The commit message to validate
|
|
19
|
+
* @returns An object with success status and error message if applicable
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateCommitMessage(message: string): Promise<{
|
|
22
|
+
valid: boolean;
|
|
23
|
+
errors?: string;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
/**
|
|
4
|
+
* Check if a commitlint configuration exists in the repository
|
|
5
|
+
*/
|
|
6
|
+
export function hasCommitlintConfig() {
|
|
7
|
+
const possibleConfigFiles = [
|
|
8
|
+
'.commitlintrc',
|
|
9
|
+
'.commitlintrc.json',
|
|
10
|
+
'.commitlintrc.yaml',
|
|
11
|
+
'.commitlintrc.yml',
|
|
12
|
+
'.commitlintrc.js',
|
|
13
|
+
'commitlint.config.js',
|
|
14
|
+
'package.json'
|
|
15
|
+
];
|
|
16
|
+
// Check if any of the possible config files exist
|
|
17
|
+
return possibleConfigFiles.some(file => {
|
|
18
|
+
try {
|
|
19
|
+
if (file === 'package.json') {
|
|
20
|
+
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
21
|
+
return packageJson.commitlint !== undefined;
|
|
22
|
+
}
|
|
23
|
+
return fs.existsSync(file);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the commitlint configuration format rules as a string
|
|
32
|
+
*/
|
|
33
|
+
export function getCommitlintRules() {
|
|
34
|
+
try {
|
|
35
|
+
// Try to get the rules using commitlint CLI if available
|
|
36
|
+
try {
|
|
37
|
+
const rulesOutput = execSync('npx commitlint --print-config', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
38
|
+
const config = JSON.parse(rulesOutput);
|
|
39
|
+
return formatCommitlintRules(config);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
// If CLI approach fails, try to find and parse config files manually
|
|
43
|
+
// This is a simplified approach - in a real implementation, you'd want to handle
|
|
44
|
+
// all possible config files and formats
|
|
45
|
+
if (fs.existsSync('commitlint.config.js')) {
|
|
46
|
+
// We can't directly require the JS file in ESM, so we'll return a generic message
|
|
47
|
+
return "Follow the conventional commit format (type(scope): message)";
|
|
48
|
+
}
|
|
49
|
+
if (fs.existsSync('.commitlintrc.json')) {
|
|
50
|
+
const config = JSON.parse(fs.readFileSync('.commitlintrc.json', 'utf8'));
|
|
51
|
+
return formatCommitlintRules(config);
|
|
52
|
+
}
|
|
53
|
+
// Default to conventional commits format if we can't parse the config
|
|
54
|
+
return "Follow the conventional commit format (type(scope): message)";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
// If all else fails, return a generic message
|
|
59
|
+
return "Follow the conventional commit format (type(scope): message)";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Format commitlint rules into a human-readable string
|
|
64
|
+
*/
|
|
65
|
+
function formatCommitlintRules(config) {
|
|
66
|
+
let rulesDescription = "Follow these commit message rules:\n";
|
|
67
|
+
if (!config.rules) {
|
|
68
|
+
return "Follow the conventional commit format (type(scope): message)";
|
|
69
|
+
}
|
|
70
|
+
// Extract type-enum rule if it exists
|
|
71
|
+
if (config.rules['type-enum'] && Array.isArray(config.rules['type-enum'][2])) {
|
|
72
|
+
const allowedTypes = config.rules['type-enum'][2];
|
|
73
|
+
rulesDescription += `- Commit type must be one of: ${allowedTypes.join(', ')}\n`;
|
|
74
|
+
}
|
|
75
|
+
// Extract other common rules
|
|
76
|
+
if (config.rules['scope-enum'] && Array.isArray(config.rules['scope-enum'][2])) {
|
|
77
|
+
const allowedScopes = config.rules['scope-enum'][2];
|
|
78
|
+
rulesDescription += `- Scope must be one of: ${allowedScopes.join(', ')}\n`;
|
|
79
|
+
}
|
|
80
|
+
if (config.rules['subject-case']) {
|
|
81
|
+
rulesDescription += `- Subject must follow case rules\n`;
|
|
82
|
+
}
|
|
83
|
+
if (config.rules['subject-max-length']) {
|
|
84
|
+
const maxLength = config.rules['subject-max-length'][2];
|
|
85
|
+
rulesDescription += `- Subject must be no longer than ${maxLength} characters\n`;
|
|
86
|
+
}
|
|
87
|
+
return rulesDescription;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validate a commit message against commitlint rules
|
|
91
|
+
* @param message The commit message to validate
|
|
92
|
+
* @returns An object with success status and error message if applicable
|
|
93
|
+
*/
|
|
94
|
+
export async function validateCommitMessage(message) {
|
|
95
|
+
if (!hasCommitlintConfig()) {
|
|
96
|
+
// If no commitlint config, consider it valid
|
|
97
|
+
return { valid: true };
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
// Create a temporary file with the message
|
|
101
|
+
const tempFile = `/tmp/gitpt-commit-msg-${Date.now()}`;
|
|
102
|
+
fs.writeFileSync(tempFile, message);
|
|
103
|
+
try {
|
|
104
|
+
// Run commitlint against the file
|
|
105
|
+
execSync(`npx commitlint --config commitlint.config.js < ${tempFile}`, { stdio: 'ignore' });
|
|
106
|
+
// If we get here, validation passed
|
|
107
|
+
fs.unlinkSync(tempFile); // Clean up temp file
|
|
108
|
+
return { valid: true };
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
// Capture the error output for feedback
|
|
112
|
+
const errorOutput = execSync(`npx commitlint --config commitlint.config.js < ${tempFile} 2>&1 || true`).toString();
|
|
113
|
+
fs.unlinkSync(tempFile); // Clean up temp file
|
|
114
|
+
return {
|
|
115
|
+
valid: false,
|
|
116
|
+
errors: errorOutput
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
// If we can't run commitlint, consider it valid to avoid blocking
|
|
122
|
+
return { valid: true };
|
|
123
|
+
}
|
|
124
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitpt",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "CLI tool that helps you write commit messages & pull request descriptions using AI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -48,5 +48,9 @@
|
|
|
48
48
|
"configstore": "^7.0.0",
|
|
49
49
|
"inquirer": "^12.5.2",
|
|
50
50
|
"node-fetch": "^3.3.2"
|
|
51
|
+
},
|
|
52
|
+
"optionalDependencies": {
|
|
53
|
+
"@commitlint/cli": "^18.4.3",
|
|
54
|
+
"@commitlint/config-conventional": "^18.4.3"
|
|
51
55
|
}
|
|
52
56
|
}
|