174 lines
4.3 KiB
Plaintext
174 lines
4.3 KiB
Plaintext
|
|
---
|
||
|
|
description:
|
||
|
|
globs:
|
||
|
|
alwaysApply: true
|
||
|
|
---
|
||
|
|
# Semantic PR Validator
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
name: Semantic PR Validator
|
||
|
|
description: Validates that PR titles follow the Conventional Commits specification
|
||
|
|
author: Cursor AI
|
||
|
|
version: 1.0.0
|
||
|
|
tags:
|
||
|
|
- git
|
||
|
|
- pull-request
|
||
|
|
- semantic
|
||
|
|
- conventional-commits
|
||
|
|
activation:
|
||
|
|
always: true
|
||
|
|
event:
|
||
|
|
- pull_request
|
||
|
|
- pull_request_title_edit
|
||
|
|
- command
|
||
|
|
triggers:
|
||
|
|
- pull_request.created
|
||
|
|
- pull_request.edited
|
||
|
|
- command: "validate_pr_title"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Rule Definition
|
||
|
|
|
||
|
|
This rule ensures that pull request titles follow the [Conventional Commits](mdc:https:/www.conventionalcommits.org) specification.
|
||
|
|
|
||
|
|
## Validation Logic
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// Function to validate PR titles against Conventional Commits spec
|
||
|
|
function validatePRTitle(title) {
|
||
|
|
// Regular expression for conventional commits format
|
||
|
|
const conventionalCommitRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9-_]+\))?(!)?: [a-z0-9].+$/i;
|
||
|
|
|
||
|
|
if (!conventionalCommitRegex.test(title)) {
|
||
|
|
return {
|
||
|
|
valid: false,
|
||
|
|
errors: [
|
||
|
|
"PR title doesn't follow the Conventional Commits format: type(scope): description",
|
||
|
|
"Example valid titles:",
|
||
|
|
"- feat(widget): add new table component",
|
||
|
|
"- fix: resolve login issue",
|
||
|
|
"- docs(readme): update installation instructions"
|
||
|
|
]
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for empty scope in parentheses
|
||
|
|
if (title.includes('()')) {
|
||
|
|
return {
|
||
|
|
valid: false,
|
||
|
|
errors: [
|
||
|
|
"Empty scope provided. Either include a scope value or remove the parentheses."
|
||
|
|
]
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extract parts
|
||
|
|
const match = title.match(/^([a-z]+)(?:\(([a-z0-9-_]+)\))?(!)?:/i);
|
||
|
|
|
||
|
|
if (!match || !match[1]) {
|
||
|
|
return {
|
||
|
|
valid: false,
|
||
|
|
errors: [
|
||
|
|
"Failed to parse PR title format. Please follow the pattern: type(scope): description"
|
||
|
|
]
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
const type = match[1].toLowerCase();
|
||
|
|
|
||
|
|
// Validate type
|
||
|
|
const validTypes = ["feat", "fix", "docs", "style", "refactor",
|
||
|
|
"perf", "test", "build", "ci", "chore", "revert"];
|
||
|
|
|
||
|
|
if (!validTypes.includes(type)) {
|
||
|
|
return {
|
||
|
|
valid: false,
|
||
|
|
errors: [
|
||
|
|
`Invalid type "${type}". Valid types are: ${validTypes.join(', ')}`
|
||
|
|
]
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return { valid: true };
|
||
|
|
}
|
||
|
|
|
||
|
|
// Triggered when a PR is created or the title is changed
|
||
|
|
function onPRTitleChange(prTitle) {
|
||
|
|
const validation = validatePRTitle(prTitle);
|
||
|
|
|
||
|
|
if (!validation.valid) {
|
||
|
|
return {
|
||
|
|
status: "failure",
|
||
|
|
message: "PR title doesn't follow Conventional Commits format",
|
||
|
|
details: validation.errors.join('\n'),
|
||
|
|
suggestions: [
|
||
|
|
{
|
||
|
|
label: "Fix PR title format",
|
||
|
|
description: "Update title to follow type(scope): description format"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
status: "success",
|
||
|
|
message: "PR title follows Conventional Commits format"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Run on activation
|
||
|
|
function activate(context) {
|
||
|
|
// Register event handlers
|
||
|
|
context.on('pull_request.created', (event) => {
|
||
|
|
const prTitle = event.pull_request.title;
|
||
|
|
return onPRTitleChange(prTitle);
|
||
|
|
});
|
||
|
|
|
||
|
|
context.on('pull_request.edited', (event) => {
|
||
|
|
const prTitle = event.pull_request.title;
|
||
|
|
return onPRTitleChange(prTitle);
|
||
|
|
});
|
||
|
|
|
||
|
|
context.registerCommand('validate_pr_title', (args) => {
|
||
|
|
const prTitle = args.title || context.currentPR?.title;
|
||
|
|
if (!prTitle) {
|
||
|
|
return {
|
||
|
|
status: "error",
|
||
|
|
message: "No PR title provided"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return onPRTitleChange(prTitle);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Export the functions
|
||
|
|
module.exports = {
|
||
|
|
activate,
|
||
|
|
onPRTitleChange,
|
||
|
|
validatePRTitle
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
## When It Runs
|
||
|
|
|
||
|
|
This rule automatically runs in the following scenarios:
|
||
|
|
- When a new pull request is created
|
||
|
|
- When a pull request title is edited
|
||
|
|
- When a developer asks for validation via Cursor command: `validate_pr_title`
|
||
|
|
|
||
|
|
## Usage Example
|
||
|
|
|
||
|
|
To validate a PR title before submitting:
|
||
|
|
|
||
|
|
1. Create a branch and make your changes
|
||
|
|
2. Prepare to create a PR
|
||
|
|
3. Use the command: `validate_pr_title` in Cursor
|
||
|
|
4. Cursor will check your title and suggest corrections if needed
|
||
|
|
|
||
|
|
## Examples of Valid PR Titles
|
||
|
|
|
||
|
|
- `feat(widgets): add new table widget capabilities`
|
||
|
|
- `fix(auth): resolve login redirect issue`
|
||
|
|
- `docs: update README with setup instructions`
|
||
|
|
- `refactor(api): simplify error handling logic`
|
||
|
|
- `chore: update dependencies to latest versions`
|