@m3hti/commit-genie 1.0.0 → 1.1.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 +164 -2
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +248 -11
- package/dist/commands/generate.js.map +1 -1
- package/dist/data/wordlist.d.ts +11 -0
- package/dist/data/wordlist.d.ts.map +1 -0
- package/dist/data/wordlist.js +170 -0
- package/dist/data/wordlist.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/services/aiService.d.ts +4 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +37 -0
- package/dist/services/aiService.js.map +1 -1
- package/dist/services/configService.d.ts.map +1 -1
- package/dist/services/configService.js +21 -0
- package/dist/services/configService.js.map +1 -1
- package/dist/services/gitService.d.ts +20 -0
- package/dist/services/gitService.d.ts.map +1 -1
- package/dist/services/gitService.js +86 -0
- package/dist/services/gitService.js.map +1 -1
- package/dist/services/lintService.d.ts +32 -0
- package/dist/services/lintService.d.ts.map +1 -0
- package/dist/services/lintService.js +191 -0
- package/dist/services/lintService.js.map +1 -0
- package/dist/services/spellService.d.ts +42 -0
- package/dist/services/spellService.d.ts.map +1 -0
- package/dist/services/spellService.js +175 -0
- package/dist/services/spellService.js.map +1 -0
- package/dist/services/splitService.d.ts +32 -0
- package/dist/services/splitService.d.ts.map +1 -0
- package/dist/services/splitService.js +184 -0
- package/dist/services/splitService.js.map +1 -0
- package/dist/types/index.d.ts +66 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LintService = void 0;
|
|
4
|
+
const configService_1 = require("./configService");
|
|
5
|
+
const VALID_TYPES = [
|
|
6
|
+
'feat',
|
|
7
|
+
'fix',
|
|
8
|
+
'docs',
|
|
9
|
+
'style',
|
|
10
|
+
'refactor',
|
|
11
|
+
'test',
|
|
12
|
+
'chore',
|
|
13
|
+
'perf',
|
|
14
|
+
];
|
|
15
|
+
const DEFAULT_FORBIDDEN_PREFIXES = [
|
|
16
|
+
'added',
|
|
17
|
+
'adding',
|
|
18
|
+
'fixed',
|
|
19
|
+
'fixing',
|
|
20
|
+
'updated',
|
|
21
|
+
'updating',
|
|
22
|
+
'removed',
|
|
23
|
+
'removing',
|
|
24
|
+
'changed',
|
|
25
|
+
'changing',
|
|
26
|
+
'created',
|
|
27
|
+
'creating',
|
|
28
|
+
'deleted',
|
|
29
|
+
'deleting',
|
|
30
|
+
'implemented',
|
|
31
|
+
'implementing',
|
|
32
|
+
];
|
|
33
|
+
class LintService {
|
|
34
|
+
/**
|
|
35
|
+
* Validate a commit message against all enabled rules
|
|
36
|
+
*/
|
|
37
|
+
static lint(message) {
|
|
38
|
+
const config = configService_1.ConfigService.getConfig();
|
|
39
|
+
const lintConfig = config.linting;
|
|
40
|
+
if (lintConfig?.enabled === false) {
|
|
41
|
+
return { valid: true, violations: [] };
|
|
42
|
+
}
|
|
43
|
+
const violations = [];
|
|
44
|
+
const rules = lintConfig?.rules;
|
|
45
|
+
// Check subject max length
|
|
46
|
+
if (rules?.subjectMaxLength?.enabled !== false) {
|
|
47
|
+
const maxLength = rules?.subjectMaxLength?.maxLength || 72;
|
|
48
|
+
const violation = this.checkSubjectLength(message.full.split('\n')[0], maxLength);
|
|
49
|
+
if (violation)
|
|
50
|
+
violations.push(violation);
|
|
51
|
+
}
|
|
52
|
+
// Check type required
|
|
53
|
+
if (rules?.typeRequired?.enabled !== false) {
|
|
54
|
+
const allowedTypes = rules?.typeRequired?.allowedTypes || VALID_TYPES;
|
|
55
|
+
const violation = this.checkTypeRequired(message.type, allowedTypes);
|
|
56
|
+
if (violation)
|
|
57
|
+
violations.push(violation);
|
|
58
|
+
}
|
|
59
|
+
// Check scope format
|
|
60
|
+
if (rules?.scopeFormat?.enabled !== false && message.scope) {
|
|
61
|
+
const pattern = rules?.scopeFormat?.pattern || '^[a-z][a-z0-9-]*$';
|
|
62
|
+
const violation = this.checkScopeFormat(message.scope, pattern);
|
|
63
|
+
if (violation)
|
|
64
|
+
violations.push(violation);
|
|
65
|
+
}
|
|
66
|
+
// Check imperative mood
|
|
67
|
+
if (rules?.imperativeMood?.enabled !== false) {
|
|
68
|
+
const forbiddenPrefixes = rules?.imperativeMood?.forbiddenPrefixes || DEFAULT_FORBIDDEN_PREFIXES;
|
|
69
|
+
const violation = this.checkImperativeMood(message.description, forbiddenPrefixes);
|
|
70
|
+
if (violation)
|
|
71
|
+
violations.push(violation);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
valid: violations.length === 0,
|
|
75
|
+
violations,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if the subject line exceeds max length
|
|
80
|
+
*/
|
|
81
|
+
static checkSubjectLength(subject, maxLength) {
|
|
82
|
+
if (subject.length > maxLength) {
|
|
83
|
+
return {
|
|
84
|
+
ruleId: 'subject-max-length',
|
|
85
|
+
ruleName: 'Subject Max Length',
|
|
86
|
+
message: `Subject line is ${subject.length} characters, max allowed is ${maxLength}`,
|
|
87
|
+
severity: 'error',
|
|
88
|
+
suggestion: `Shorten to ${maxLength} characters or less`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if the commit type is valid
|
|
95
|
+
*/
|
|
96
|
+
static checkTypeRequired(type, allowedTypes) {
|
|
97
|
+
if (!type) {
|
|
98
|
+
return {
|
|
99
|
+
ruleId: 'type-required',
|
|
100
|
+
ruleName: 'Type Required',
|
|
101
|
+
message: 'Commit message must include a type prefix',
|
|
102
|
+
severity: 'error',
|
|
103
|
+
suggestion: `Use one of: ${allowedTypes.join(', ')}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!allowedTypes.includes(type)) {
|
|
107
|
+
return {
|
|
108
|
+
ruleId: 'type-invalid',
|
|
109
|
+
ruleName: 'Invalid Type',
|
|
110
|
+
message: `Type "${type}" is not in the allowed list`,
|
|
111
|
+
severity: 'error',
|
|
112
|
+
suggestion: `Use one of: ${allowedTypes.join(', ')}`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if the scope format is valid
|
|
119
|
+
*/
|
|
120
|
+
static checkScopeFormat(scope, pattern) {
|
|
121
|
+
try {
|
|
122
|
+
const regex = new RegExp(pattern);
|
|
123
|
+
if (!regex.test(scope)) {
|
|
124
|
+
return {
|
|
125
|
+
ruleId: 'scope-format',
|
|
126
|
+
ruleName: 'Scope Format',
|
|
127
|
+
message: `Scope "${scope}" does not match required format`,
|
|
128
|
+
severity: 'warning',
|
|
129
|
+
suggestion: 'Use lowercase alphanumeric characters and hyphens',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Invalid regex pattern, skip check
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if the description uses imperative mood
|
|
140
|
+
*/
|
|
141
|
+
static checkImperativeMood(description, forbiddenPrefixes) {
|
|
142
|
+
const firstWord = description.split(/\s+/)[0]?.toLowerCase();
|
|
143
|
+
if (firstWord && forbiddenPrefixes.includes(firstWord)) {
|
|
144
|
+
const imperative = this.getImperativeForm(firstWord);
|
|
145
|
+
return {
|
|
146
|
+
ruleId: 'imperative-mood',
|
|
147
|
+
ruleName: 'Imperative Mood',
|
|
148
|
+
message: `Description should use imperative mood, not "${firstWord}"`,
|
|
149
|
+
severity: 'warning',
|
|
150
|
+
suggestion: imperative ? `Use "${imperative}" instead` : 'Start with a verb in imperative form',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get the imperative form of a common past tense or gerund verb
|
|
157
|
+
*/
|
|
158
|
+
static getImperativeForm(word) {
|
|
159
|
+
const conversions = {
|
|
160
|
+
added: 'add',
|
|
161
|
+
adding: 'add',
|
|
162
|
+
fixed: 'fix',
|
|
163
|
+
fixing: 'fix',
|
|
164
|
+
updated: 'update',
|
|
165
|
+
updating: 'update',
|
|
166
|
+
removed: 'remove',
|
|
167
|
+
removing: 'remove',
|
|
168
|
+
changed: 'change',
|
|
169
|
+
changing: 'change',
|
|
170
|
+
created: 'create',
|
|
171
|
+
creating: 'create',
|
|
172
|
+
deleted: 'delete',
|
|
173
|
+
deleting: 'delete',
|
|
174
|
+
implemented: 'implement',
|
|
175
|
+
implementing: 'implement',
|
|
176
|
+
};
|
|
177
|
+
return conversions[word] || null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if linting should block the commit
|
|
181
|
+
*/
|
|
182
|
+
static shouldBlockCommit(result) {
|
|
183
|
+
const config = configService_1.ConfigService.getConfig();
|
|
184
|
+
if (config.linting?.blockOnError === false) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
return result.violations.some(v => v.severity === 'error');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
exports.LintService = LintService;
|
|
191
|
+
//# sourceMappingURL=lintService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lintService.js","sourceRoot":"","sources":["../../src/services/lintService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SpellCheckResult } from '../types';
|
|
2
|
+
export declare class SpellService {
|
|
3
|
+
private static customWords;
|
|
4
|
+
private static initialized;
|
|
5
|
+
/**
|
|
6
|
+
* Initialize custom dictionary from config
|
|
7
|
+
*/
|
|
8
|
+
private static init;
|
|
9
|
+
/**
|
|
10
|
+
* Check a commit message description for spelling errors
|
|
11
|
+
*/
|
|
12
|
+
static check(description: string): SpellCheckResult;
|
|
13
|
+
/**
|
|
14
|
+
* Extract words from text, handling camelCase and code-like tokens
|
|
15
|
+
*/
|
|
16
|
+
private static extractWords;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a word should be ignored based on patterns
|
|
19
|
+
*/
|
|
20
|
+
private static shouldIgnore;
|
|
21
|
+
/**
|
|
22
|
+
* Check if a word is valid (in dictionary or custom words)
|
|
23
|
+
*/
|
|
24
|
+
private static isValidWord;
|
|
25
|
+
/**
|
|
26
|
+
* Get spelling suggestions using Levenshtein distance
|
|
27
|
+
*/
|
|
28
|
+
private static getSuggestions;
|
|
29
|
+
/**
|
|
30
|
+
* Calculate Levenshtein distance between two strings
|
|
31
|
+
*/
|
|
32
|
+
private static levenshteinDistance;
|
|
33
|
+
/**
|
|
34
|
+
* Add a word to the session dictionary (not persisted)
|
|
35
|
+
*/
|
|
36
|
+
static addToSessionDictionary(word: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Clear the session dictionary
|
|
39
|
+
*/
|
|
40
|
+
static clearSessionDictionary(): void;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=spellService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spellService.d.ts","sourceRoot":"","sources":["../../src/services/spellService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAmB,MAAM,UAAU,CAAC;AAI7D,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,WAAW,CAA0B;IACpD,OAAO,CAAC,MAAM,CAAC,WAAW,CAAS;IAEnC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,IAAI;IAanB;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB;IAmCnD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAmC3B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAc3B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAK1B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAuB7B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAkClC;;OAEG;IACH,MAAM,CAAC,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIjD;;OAEG;IACH,MAAM,CAAC,sBAAsB,IAAI,IAAI;CAItC"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpellService = void 0;
|
|
4
|
+
const configService_1 = require("./configService");
|
|
5
|
+
const wordlist_1 = require("../data/wordlist");
|
|
6
|
+
class SpellService {
|
|
7
|
+
/**
|
|
8
|
+
* Initialize custom dictionary from config
|
|
9
|
+
*/
|
|
10
|
+
static init() {
|
|
11
|
+
if (this.initialized)
|
|
12
|
+
return;
|
|
13
|
+
const config = configService_1.ConfigService.getConfig();
|
|
14
|
+
const customDictionary = config.spellCheck?.customDictionary || [];
|
|
15
|
+
for (const word of customDictionary) {
|
|
16
|
+
this.customWords.add(word.toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
this.initialized = true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check a commit message description for spelling errors
|
|
22
|
+
*/
|
|
23
|
+
static check(description) {
|
|
24
|
+
const config = configService_1.ConfigService.getConfig();
|
|
25
|
+
if (config.spellCheck?.enabled === false) {
|
|
26
|
+
return { valid: true, errors: [] };
|
|
27
|
+
}
|
|
28
|
+
this.init();
|
|
29
|
+
const words = this.extractWords(description);
|
|
30
|
+
const errors = [];
|
|
31
|
+
const ignorePatterns = config.spellCheck?.ignorePatterns || [];
|
|
32
|
+
for (const { word, position } of words) {
|
|
33
|
+
// Skip if matches ignore pattern
|
|
34
|
+
if (this.shouldIgnore(word, ignorePatterns)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!this.isValidWord(word)) {
|
|
38
|
+
const suggestions = this.getSuggestions(word);
|
|
39
|
+
errors.push({
|
|
40
|
+
word,
|
|
41
|
+
suggestions,
|
|
42
|
+
position,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
valid: errors.length === 0,
|
|
48
|
+
errors,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract words from text, handling camelCase and code-like tokens
|
|
53
|
+
*/
|
|
54
|
+
static extractWords(text) {
|
|
55
|
+
const results = [];
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
// Split camelCase and PascalCase
|
|
58
|
+
const expanded = text.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
59
|
+
// Replace separators with spaces
|
|
60
|
+
const cleaned = expanded
|
|
61
|
+
.replace(/[_\-./\\:;,!?'"()[\]{}]/g, ' ')
|
|
62
|
+
.replace(/\d+/g, ' ');
|
|
63
|
+
// Extract words
|
|
64
|
+
const regex = /[a-zA-Z]+/g;
|
|
65
|
+
let match;
|
|
66
|
+
while ((match = regex.exec(cleaned)) !== null) {
|
|
67
|
+
const word = match[0].toLowerCase();
|
|
68
|
+
// Skip very short words and already seen words
|
|
69
|
+
if (word.length <= 2 || seen.has(word)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
seen.add(word);
|
|
73
|
+
// Find original position in the text
|
|
74
|
+
const position = text.toLowerCase().indexOf(word);
|
|
75
|
+
results.push({ word, position: position >= 0 ? position : 0 });
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if a word should be ignored based on patterns
|
|
81
|
+
*/
|
|
82
|
+
static shouldIgnore(word, patterns) {
|
|
83
|
+
for (const pattern of patterns) {
|
|
84
|
+
try {
|
|
85
|
+
const regex = new RegExp(pattern, 'i');
|
|
86
|
+
if (regex.test(word)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Invalid regex, skip
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if a word is valid (in dictionary or custom words)
|
|
98
|
+
*/
|
|
99
|
+
static isValidWord(word) {
|
|
100
|
+
const lower = word.toLowerCase();
|
|
101
|
+
return wordlist_1.WORDLIST.has(lower) || this.customWords.has(lower);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get spelling suggestions using Levenshtein distance
|
|
105
|
+
*/
|
|
106
|
+
static getSuggestions(word, maxSuggestions = 3) {
|
|
107
|
+
const lower = word.toLowerCase();
|
|
108
|
+
const suggestions = [];
|
|
109
|
+
// Only check words of similar length for performance
|
|
110
|
+
const minLen = Math.max(1, word.length - 2);
|
|
111
|
+
const maxLen = word.length + 2;
|
|
112
|
+
for (const dictWord of wordlist_1.WORDLIST) {
|
|
113
|
+
if (dictWord.length >= minLen && dictWord.length <= maxLen) {
|
|
114
|
+
const distance = this.levenshteinDistance(lower, dictWord);
|
|
115
|
+
if (distance <= 2 && distance > 0) {
|
|
116
|
+
suggestions.push({ word: dictWord, distance });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return suggestions
|
|
121
|
+
.sort((a, b) => a.distance - b.distance)
|
|
122
|
+
.slice(0, maxSuggestions)
|
|
123
|
+
.map(s => s.word);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Calculate Levenshtein distance between two strings
|
|
127
|
+
*/
|
|
128
|
+
static levenshteinDistance(a, b) {
|
|
129
|
+
if (a.length === 0)
|
|
130
|
+
return b.length;
|
|
131
|
+
if (b.length === 0)
|
|
132
|
+
return a.length;
|
|
133
|
+
const matrix = [];
|
|
134
|
+
// Initialize first column
|
|
135
|
+
for (let i = 0; i <= b.length; i++) {
|
|
136
|
+
matrix[i] = [i];
|
|
137
|
+
}
|
|
138
|
+
// Initialize first row
|
|
139
|
+
for (let j = 0; j <= a.length; j++) {
|
|
140
|
+
matrix[0][j] = j;
|
|
141
|
+
}
|
|
142
|
+
// Fill in the rest of the matrix
|
|
143
|
+
for (let i = 1; i <= b.length; i++) {
|
|
144
|
+
for (let j = 1; j <= a.length; j++) {
|
|
145
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
146
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
|
|
150
|
+
matrix[i][j - 1] + 1, // insertion
|
|
151
|
+
matrix[i - 1][j] + 1 // deletion
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return matrix[b.length][a.length];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Add a word to the session dictionary (not persisted)
|
|
160
|
+
*/
|
|
161
|
+
static addToSessionDictionary(word) {
|
|
162
|
+
this.customWords.add(word.toLowerCase());
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Clear the session dictionary
|
|
166
|
+
*/
|
|
167
|
+
static clearSessionDictionary() {
|
|
168
|
+
this.customWords.clear();
|
|
169
|
+
this.initialized = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
exports.SpellService = SpellService;
|
|
173
|
+
SpellService.customWords = new Set();
|
|
174
|
+
SpellService.initialized = false;
|
|
175
|
+
//# sourceMappingURL=spellService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spellService.js","sourceRoot":"","sources":["../../src/services/spellService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { FileChange, SplitSuggestion } from '../types';
|
|
2
|
+
export declare class SplitService {
|
|
3
|
+
/**
|
|
4
|
+
* Analyze staged files and detect if they should be split into multiple commits
|
|
5
|
+
*/
|
|
6
|
+
static analyzeSplitOpportunity(files: FileChange[], _diff: string): SplitSuggestion | null;
|
|
7
|
+
/**
|
|
8
|
+
* Group files by their detected intent (commit type + scope)
|
|
9
|
+
*/
|
|
10
|
+
private static groupFilesByIntent;
|
|
11
|
+
/**
|
|
12
|
+
* Detect scope from file path based on directory structure
|
|
13
|
+
*/
|
|
14
|
+
private static detectScopeFromPath;
|
|
15
|
+
/**
|
|
16
|
+
* Generate a human-readable reason for why splitting is recommended
|
|
17
|
+
*/
|
|
18
|
+
private static generateSplitReason;
|
|
19
|
+
/**
|
|
20
|
+
* Generate a suggested commit message for a group of files
|
|
21
|
+
*/
|
|
22
|
+
private static generateGroupMessage;
|
|
23
|
+
/**
|
|
24
|
+
* Get appropriate action verb for commit type
|
|
25
|
+
*/
|
|
26
|
+
private static getActionForType;
|
|
27
|
+
/**
|
|
28
|
+
* Get the file type category for display
|
|
29
|
+
*/
|
|
30
|
+
static getFileTypeLabel(filePath: string): string;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=splitService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitService.d.ts","sourceRoot":"","sources":["../../src/services/splitService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA0B,eAAe,EAAE,MAAM,UAAU,CAAC;AAI/E,qBAAa,YAAY;IACvB;;OAEG;IACH,MAAM,CAAC,uBAAuB,CAC5B,KAAK,EAAE,UAAU,EAAE,EACnB,KAAK,EAAE,MAAM,GACZ,eAAe,GAAG,IAAI;IAmCzB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAgDjC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAgDlC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAelC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAiBnC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAc/B;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;CAUlD"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SplitService = void 0;
|
|
4
|
+
const configService_1 = require("./configService");
|
|
5
|
+
const filePatterns_1 = require("../utils/filePatterns");
|
|
6
|
+
class SplitService {
|
|
7
|
+
/**
|
|
8
|
+
* Analyze staged files and detect if they should be split into multiple commits
|
|
9
|
+
*/
|
|
10
|
+
static analyzeSplitOpportunity(files, _diff) {
|
|
11
|
+
const config = configService_1.ConfigService.getConfig();
|
|
12
|
+
const splitConfig = config.splitting;
|
|
13
|
+
if (splitConfig?.enabled === false) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const minFiles = splitConfig?.minFilesForSuggestion || 2;
|
|
17
|
+
// Not enough files to suggest splitting
|
|
18
|
+
if (files.length < minFiles) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const groups = this.groupFilesByIntent(files, splitConfig);
|
|
22
|
+
// If only one group, no split needed
|
|
23
|
+
if (groups.length <= 1) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
// Only suggest split if each group has meaningful changes
|
|
27
|
+
const meaningfulGroups = groups.filter(g => g.files.length > 0);
|
|
28
|
+
if (meaningfulGroups.length <= 1) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
shouldSplit: true,
|
|
33
|
+
reason: this.generateSplitReason(meaningfulGroups),
|
|
34
|
+
groups: meaningfulGroups,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Group files by their detected intent (commit type + scope)
|
|
39
|
+
*/
|
|
40
|
+
static groupFilesByIntent(files, splitConfig) {
|
|
41
|
+
const groupByScope = splitConfig?.groupByScope !== false;
|
|
42
|
+
const groupByType = splitConfig?.groupByType !== false;
|
|
43
|
+
const groups = new Map();
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const commitType = (0, filePatterns_1.getCommitTypeForFile)(file.path);
|
|
46
|
+
const scope = groupByScope ? this.detectScopeFromPath(file.path) : undefined;
|
|
47
|
+
// Create group key based on configuration
|
|
48
|
+
let key;
|
|
49
|
+
if (groupByType && groupByScope) {
|
|
50
|
+
key = `${commitType}:${scope || 'default'}`;
|
|
51
|
+
}
|
|
52
|
+
else if (groupByType) {
|
|
53
|
+
key = commitType;
|
|
54
|
+
}
|
|
55
|
+
else if (groupByScope) {
|
|
56
|
+
key = scope || 'default';
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
key = 'all';
|
|
60
|
+
}
|
|
61
|
+
if (!groups.has(key)) {
|
|
62
|
+
groups.set(key, {
|
|
63
|
+
type: commitType,
|
|
64
|
+
scope,
|
|
65
|
+
files: [],
|
|
66
|
+
suggestedMessage: '',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
groups.get(key).files.push(file);
|
|
70
|
+
}
|
|
71
|
+
// Generate suggested messages for each group
|
|
72
|
+
for (const group of groups.values()) {
|
|
73
|
+
group.suggestedMessage = this.generateGroupMessage(group);
|
|
74
|
+
}
|
|
75
|
+
return Array.from(groups.values());
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Detect scope from file path based on directory structure
|
|
79
|
+
*/
|
|
80
|
+
static detectScopeFromPath(filePath) {
|
|
81
|
+
const parts = filePath.split(/[/\\]/);
|
|
82
|
+
// Look for common scope-indicating directories
|
|
83
|
+
const scopeIndicators = ['src', 'lib', 'packages', 'apps', 'modules'];
|
|
84
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
85
|
+
if (scopeIndicators.includes(parts[i]) && parts[i + 1]) {
|
|
86
|
+
const potentialScope = parts[i + 1];
|
|
87
|
+
// Validate it looks like a scope (not a file)
|
|
88
|
+
if (!potentialScope.includes('.') && potentialScope.length <= 20) {
|
|
89
|
+
return potentialScope;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Fallback: use first directory if it looks like a scope
|
|
94
|
+
if (parts.length > 1) {
|
|
95
|
+
const firstDir = parts[0];
|
|
96
|
+
const validScopes = [
|
|
97
|
+
'api',
|
|
98
|
+
'ui',
|
|
99
|
+
'web',
|
|
100
|
+
'app',
|
|
101
|
+
'core',
|
|
102
|
+
'common',
|
|
103
|
+
'shared',
|
|
104
|
+
'utils',
|
|
105
|
+
'helpers',
|
|
106
|
+
'services',
|
|
107
|
+
'components',
|
|
108
|
+
'hooks',
|
|
109
|
+
'models',
|
|
110
|
+
'types',
|
|
111
|
+
'config',
|
|
112
|
+
'scripts',
|
|
113
|
+
'tools',
|
|
114
|
+
'tests',
|
|
115
|
+
'docs',
|
|
116
|
+
];
|
|
117
|
+
if (validScopes.includes(firstDir.toLowerCase())) {
|
|
118
|
+
return firstDir.toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Generate a human-readable reason for why splitting is recommended
|
|
125
|
+
*/
|
|
126
|
+
static generateSplitReason(groups) {
|
|
127
|
+
const types = [...new Set(groups.map(g => g.type))];
|
|
128
|
+
const scopes = [...new Set(groups.map(g => g.scope).filter(Boolean))];
|
|
129
|
+
if (types.length > 1) {
|
|
130
|
+
return `Mixed commit types detected: ${types.join(', ')}`;
|
|
131
|
+
}
|
|
132
|
+
if (scopes.length > 1) {
|
|
133
|
+
return `Changes span multiple modules: ${scopes.join(', ')}`;
|
|
134
|
+
}
|
|
135
|
+
return 'Changes appear to address multiple concerns';
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Generate a suggested commit message for a group of files
|
|
139
|
+
*/
|
|
140
|
+
static generateGroupMessage(group) {
|
|
141
|
+
const fileNames = group.files
|
|
142
|
+
.slice(0, 3)
|
|
143
|
+
.map(f => {
|
|
144
|
+
const parts = f.path.split(/[/\\]/);
|
|
145
|
+
return parts[parts.length - 1];
|
|
146
|
+
})
|
|
147
|
+
.join(', ');
|
|
148
|
+
const moreFiles = group.files.length > 3 ? ` +${group.files.length - 3} more` : '';
|
|
149
|
+
const scopePart = group.scope ? `(${group.scope})` : '';
|
|
150
|
+
const action = this.getActionForType(group.type);
|
|
151
|
+
return `${group.type}${scopePart}: ${action} ${fileNames}${moreFiles}`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get appropriate action verb for commit type
|
|
155
|
+
*/
|
|
156
|
+
static getActionForType(type) {
|
|
157
|
+
const actions = {
|
|
158
|
+
feat: 'add',
|
|
159
|
+
fix: 'fix',
|
|
160
|
+
docs: 'update',
|
|
161
|
+
style: 'format',
|
|
162
|
+
refactor: 'refactor',
|
|
163
|
+
test: 'add tests for',
|
|
164
|
+
chore: 'update',
|
|
165
|
+
perf: 'optimize',
|
|
166
|
+
};
|
|
167
|
+
return actions[type] || 'update';
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get the file type category for display
|
|
171
|
+
*/
|
|
172
|
+
static getFileTypeLabel(filePath) {
|
|
173
|
+
const type = (0, filePatterns_1.detectFileType)(filePath);
|
|
174
|
+
const labels = {
|
|
175
|
+
test: 'Test',
|
|
176
|
+
docs: 'Docs',
|
|
177
|
+
config: 'Config',
|
|
178
|
+
source: 'Source',
|
|
179
|
+
};
|
|
180
|
+
return labels[type] || 'Source';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.SplitService = SplitService;
|
|
184
|
+
//# sourceMappingURL=splitService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitService.js","sourceRoot":"","sources":["../../src/services/splitService.ts"],"names":[],"mappings":"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"}
|