@entro314labs/ai-changelog-generator 3.0.5 → 3.1.1
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/CHANGELOG.md +92 -0
- package/README.md +22 -0
- package/package.json +10 -10
- package/src/application/orchestrators/changelog.orchestrator.js +469 -0
- package/src/application/services/application.service.js +13 -0
- package/src/infrastructure/cli/cli.controller.js +52 -0
- package/src/infrastructure/interactive/interactive-staging.service.js +313 -0
- package/src/infrastructure/validation/commit-message-validation.service.js +458 -0
- package/src/shared/constants/colors.js +28 -6
- package/src/shared/utils/utils.js +352 -0
|
@@ -1990,6 +1990,358 @@ export function assessChangeComplexity(diff) {
|
|
|
1990
1990
|
};
|
|
1991
1991
|
}
|
|
1992
1992
|
|
|
1993
|
+
// ========================================
|
|
1994
|
+
// BRANCH INTELLIGENCE UTILITIES
|
|
1995
|
+
// ========================================
|
|
1996
|
+
|
|
1997
|
+
/**
|
|
1998
|
+
* Get current git branch name
|
|
1999
|
+
*/
|
|
2000
|
+
export function getCurrentBranch() {
|
|
2001
|
+
try {
|
|
2002
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
|
|
2003
|
+
return branch !== 'HEAD' ? branch : null;
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
console.warn(colors.warning('Could not determine current branch'));
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
/**
|
|
2011
|
+
* Extract branch context and intelligence from branch name
|
|
2012
|
+
* Based on better-commits patterns: type/ticket-description, feat/ABC-123-add-feature
|
|
2013
|
+
*/
|
|
2014
|
+
export function analyzeBranchIntelligence(branchName = null) {
|
|
2015
|
+
const branch = branchName || getCurrentBranch();
|
|
2016
|
+
|
|
2017
|
+
if (!branch) {
|
|
2018
|
+
return {
|
|
2019
|
+
branch: null,
|
|
2020
|
+
type: null,
|
|
2021
|
+
ticket: null,
|
|
2022
|
+
description: null,
|
|
2023
|
+
confidence: 0,
|
|
2024
|
+
patterns: []
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
const analysis = {
|
|
2029
|
+
branch,
|
|
2030
|
+
type: null,
|
|
2031
|
+
ticket: null,
|
|
2032
|
+
description: null,
|
|
2033
|
+
confidence: 0,
|
|
2034
|
+
patterns: []
|
|
2035
|
+
};
|
|
2036
|
+
|
|
2037
|
+
// Pattern 1: type/ticket-description (e.g., feat/ABC-123-add-feature)
|
|
2038
|
+
const typeTicketDescPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/([A-Z]{2,}-\d+)-(.+)$/i;
|
|
2039
|
+
let match = branch.match(typeTicketDescPattern);
|
|
2040
|
+
if (match) {
|
|
2041
|
+
analysis.type = match[1].toLowerCase();
|
|
2042
|
+
analysis.ticket = match[2].toUpperCase();
|
|
2043
|
+
analysis.description = match[3].replace(/[-_]/g, ' ');
|
|
2044
|
+
analysis.confidence = 95;
|
|
2045
|
+
analysis.patterns.push('type/ticket-description');
|
|
2046
|
+
return analysis;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// Pattern 2: type/description (e.g., feat/add-new-feature)
|
|
2050
|
+
const typeDescPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/(.+)$/i;
|
|
2051
|
+
match = branch.match(typeDescPattern);
|
|
2052
|
+
if (match) {
|
|
2053
|
+
analysis.type = match[1].toLowerCase();
|
|
2054
|
+
analysis.description = match[2].replace(/[-_]/g, ' ');
|
|
2055
|
+
analysis.confidence = 85;
|
|
2056
|
+
analysis.patterns.push('type/description');
|
|
2057
|
+
|
|
2058
|
+
// Look for ticket in description
|
|
2059
|
+
const ticketInDesc = match[2].match(/([A-Z]{2,}-\d+)/);
|
|
2060
|
+
if (ticketInDesc) {
|
|
2061
|
+
analysis.ticket = ticketInDesc[1];
|
|
2062
|
+
analysis.confidence = 90;
|
|
2063
|
+
analysis.patterns.push('ticket-in-description');
|
|
2064
|
+
}
|
|
2065
|
+
return analysis;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// Pattern 3: ticket-description (e.g., ABC-123-fix-bug)
|
|
2069
|
+
const ticketDescPattern = /^([A-Z]{2,}-\d+)-(.+)$/;
|
|
2070
|
+
match = branch.match(ticketDescPattern);
|
|
2071
|
+
if (match) {
|
|
2072
|
+
analysis.ticket = match[1];
|
|
2073
|
+
analysis.description = match[2].replace(/[-_]/g, ' ');
|
|
2074
|
+
analysis.confidence = 70;
|
|
2075
|
+
analysis.patterns.push('ticket-description');
|
|
2076
|
+
|
|
2077
|
+
// Infer type from description
|
|
2078
|
+
const inferredType = inferTypeFromDescription(match[2]);
|
|
2079
|
+
if (inferredType) {
|
|
2080
|
+
analysis.type = inferredType;
|
|
2081
|
+
analysis.confidence = 75;
|
|
2082
|
+
analysis.patterns.push('inferred-type');
|
|
2083
|
+
}
|
|
2084
|
+
return analysis;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// Pattern 4: just type at start (e.g., feat-add-feature)
|
|
2088
|
+
const typeStartPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)[-_](.+)$/i;
|
|
2089
|
+
match = branch.match(typeStartPattern);
|
|
2090
|
+
if (match) {
|
|
2091
|
+
analysis.type = match[1].toLowerCase();
|
|
2092
|
+
analysis.description = match[2].replace(/[-_]/g, ' ');
|
|
2093
|
+
analysis.confidence = 60;
|
|
2094
|
+
analysis.patterns.push('type-start');
|
|
2095
|
+
return analysis;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// Pattern 5: ticket at start (e.g., ABC-123_add_feature)
|
|
2099
|
+
const ticketStartPattern = /^([A-Z]{2,}-\d+)[-_](.+)$/;
|
|
2100
|
+
match = branch.match(ticketStartPattern);
|
|
2101
|
+
if (match) {
|
|
2102
|
+
analysis.ticket = match[1];
|
|
2103
|
+
analysis.description = match[2].replace(/[-_]/g, ' ');
|
|
2104
|
+
analysis.confidence = 60;
|
|
2105
|
+
analysis.patterns.push('ticket-start');
|
|
2106
|
+
|
|
2107
|
+
// Infer type from description
|
|
2108
|
+
const inferredType = inferTypeFromDescription(match[2]);
|
|
2109
|
+
if (inferredType) {
|
|
2110
|
+
analysis.type = inferredType;
|
|
2111
|
+
analysis.confidence = 65;
|
|
2112
|
+
analysis.patterns.push('inferred-type');
|
|
2113
|
+
}
|
|
2114
|
+
return analysis;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// Pattern 6: just ticket anywhere (e.g., add-feature-ABC-123)
|
|
2118
|
+
const ticketAnywherePattern = /([A-Z]{2,}-\d+)/;
|
|
2119
|
+
match = branch.match(ticketAnywherePattern);
|
|
2120
|
+
if (match) {
|
|
2121
|
+
analysis.ticket = match[1];
|
|
2122
|
+
analysis.confidence = 40;
|
|
2123
|
+
analysis.patterns.push('ticket-anywhere');
|
|
2124
|
+
|
|
2125
|
+
// Use the whole branch as description, removing ticket
|
|
2126
|
+
analysis.description = branch.replace(match[1], '').replace(/[-_]/g, ' ').trim();
|
|
2127
|
+
return analysis;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
// Pattern 7: conventional type anywhere in branch
|
|
2131
|
+
const typeAnywherePattern = /(feat|fix|docs|style|refactor|perf|test|build|ci|chore)/i;
|
|
2132
|
+
match = branch.match(typeAnywherePattern);
|
|
2133
|
+
if (match) {
|
|
2134
|
+
analysis.type = match[1].toLowerCase();
|
|
2135
|
+
analysis.confidence = 30;
|
|
2136
|
+
analysis.patterns.push('type-anywhere');
|
|
2137
|
+
analysis.description = branch.replace(/[-_]/g, ' ');
|
|
2138
|
+
return analysis;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
// Fallback: use branch as description and try to infer type
|
|
2142
|
+
analysis.description = branch.replace(/[-_]/g, ' ');
|
|
2143
|
+
const inferredType = inferTypeFromDescription(branch);
|
|
2144
|
+
if (inferredType) {
|
|
2145
|
+
analysis.type = inferredType;
|
|
2146
|
+
analysis.confidence = 25;
|
|
2147
|
+
analysis.patterns.push('inferred-type');
|
|
2148
|
+
} else {
|
|
2149
|
+
analysis.confidence = 10;
|
|
2150
|
+
analysis.patterns.push('no-pattern');
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
return analysis;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
/**
|
|
2157
|
+
* Infer commit type from description text
|
|
2158
|
+
*/
|
|
2159
|
+
function inferTypeFromDescription(description) {
|
|
2160
|
+
const text = description.toLowerCase();
|
|
2161
|
+
|
|
2162
|
+
// Feature indicators
|
|
2163
|
+
if (text.match(/add|new|create|implement|introduce|feature/)) return 'feat';
|
|
2164
|
+
|
|
2165
|
+
// Fix indicators
|
|
2166
|
+
if (text.match(/fix|bug|error|issue|resolve|correct|patch/)) return 'fix';
|
|
2167
|
+
|
|
2168
|
+
// Documentation indicators
|
|
2169
|
+
if (text.match(/doc|readme|comment|guide|tutorial/)) return 'docs';
|
|
2170
|
+
|
|
2171
|
+
// Style indicators
|
|
2172
|
+
if (text.match(/style|format|lint|prettier|eslint/)) return 'style';
|
|
2173
|
+
|
|
2174
|
+
// Refactor indicators
|
|
2175
|
+
if (text.match(/refactor|restructure|reorganize|cleanup|clean/)) return 'refactor';
|
|
2176
|
+
|
|
2177
|
+
// Performance indicators
|
|
2178
|
+
if (text.match(/perf|performance|optimize|speed|fast/)) return 'perf';
|
|
2179
|
+
|
|
2180
|
+
// Test indicators
|
|
2181
|
+
if (text.match(/test|spec|unit|integration/)) return 'test';
|
|
2182
|
+
|
|
2183
|
+
// Build indicators
|
|
2184
|
+
if (text.match(/build|deploy|package|bundle|webpack|vite/)) return 'build';
|
|
2185
|
+
|
|
2186
|
+
// CI indicators
|
|
2187
|
+
if (text.match(/ci|pipeline|action|workflow|github/)) return 'ci';
|
|
2188
|
+
|
|
2189
|
+
// Chore indicators
|
|
2190
|
+
if (text.match(/chore|update|upgrade|dependency|deps|config/)) return 'chore';
|
|
2191
|
+
|
|
2192
|
+
return null;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
/**
|
|
2196
|
+
* Generate enhanced commit message context from branch intelligence
|
|
2197
|
+
*/
|
|
2198
|
+
export function generateCommitContextFromBranch(branchAnalysis, changes = []) {
|
|
2199
|
+
if (!branchAnalysis || branchAnalysis.confidence < 20) {
|
|
2200
|
+
return '';
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
let context = '\n**Branch Context:**';
|
|
2204
|
+
|
|
2205
|
+
if (branchAnalysis.type) {
|
|
2206
|
+
context += `\n- Inferred type: ${branchAnalysis.type}`;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
if (branchAnalysis.ticket) {
|
|
2210
|
+
context += `\n- Related ticket: ${branchAnalysis.ticket}`;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
if (branchAnalysis.description) {
|
|
2214
|
+
context += `\n- Branch description: ${branchAnalysis.description}`;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
context += `\n- Confidence: ${branchAnalysis.confidence}%`;
|
|
2218
|
+
context += `\n- Detection patterns: ${branchAnalysis.patterns.join(', ')}`;
|
|
2219
|
+
|
|
2220
|
+
return context;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
/**
|
|
2224
|
+
* Get suggested commit type based on branch analysis and file changes
|
|
2225
|
+
*/
|
|
2226
|
+
export function getSuggestedCommitType(branchAnalysis, changes = []) {
|
|
2227
|
+
// High confidence branch type takes precedence
|
|
2228
|
+
if (branchAnalysis && branchAnalysis.type && branchAnalysis.confidence >= 70) {
|
|
2229
|
+
return {
|
|
2230
|
+
type: branchAnalysis.type,
|
|
2231
|
+
source: 'branch',
|
|
2232
|
+
confidence: branchAnalysis.confidence
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
// Analyze file changes to infer type
|
|
2237
|
+
const changeAnalysis = analyzeChangesForType(changes);
|
|
2238
|
+
|
|
2239
|
+
// Medium confidence branch type combined with file analysis
|
|
2240
|
+
if (branchAnalysis && branchAnalysis.type && branchAnalysis.confidence >= 40) {
|
|
2241
|
+
if (changeAnalysis.type === branchAnalysis.type) {
|
|
2242
|
+
return {
|
|
2243
|
+
type: branchAnalysis.type,
|
|
2244
|
+
source: 'branch+files',
|
|
2245
|
+
confidence: Math.min(95, branchAnalysis.confidence + 20)
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
// Use file-based analysis
|
|
2251
|
+
if (changeAnalysis.confidence >= 60) {
|
|
2252
|
+
return changeAnalysis;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
// Low confidence branch type as fallback
|
|
2256
|
+
if (branchAnalysis && branchAnalysis.type) {
|
|
2257
|
+
return {
|
|
2258
|
+
type: branchAnalysis.type,
|
|
2259
|
+
source: 'branch-fallback',
|
|
2260
|
+
confidence: branchAnalysis.confidence
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
// Default fallback
|
|
2265
|
+
return {
|
|
2266
|
+
type: 'feat',
|
|
2267
|
+
source: 'default',
|
|
2268
|
+
confidence: 20
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
/**
|
|
2273
|
+
* Analyze file changes to suggest commit type
|
|
2274
|
+
*/
|
|
2275
|
+
function analyzeChangesForType(changes) {
|
|
2276
|
+
if (!changes || changes.length === 0) {
|
|
2277
|
+
return { type: 'feat', source: 'default', confidence: 20 };
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
const categories = {
|
|
2281
|
+
docs: 0,
|
|
2282
|
+
test: 0,
|
|
2283
|
+
config: 0,
|
|
2284
|
+
source: 0,
|
|
2285
|
+
style: 0
|
|
2286
|
+
};
|
|
2287
|
+
|
|
2288
|
+
// Categorize files
|
|
2289
|
+
changes.forEach(change => {
|
|
2290
|
+
const path = change.path || change.filePath || '';
|
|
2291
|
+
const ext = path.split('.').pop()?.toLowerCase() || '';
|
|
2292
|
+
|
|
2293
|
+
if (path.match(/readme|doc|\.md$|guide|tutorial/i)) {
|
|
2294
|
+
categories.docs++;
|
|
2295
|
+
} else if (path.match(/test|spec|\.test\.|\.spec\./i)) {
|
|
2296
|
+
categories.test++;
|
|
2297
|
+
} else if (path.match(/config|\.json$|\.yaml$|\.yml$|\.toml$|\.ini$/i)) {
|
|
2298
|
+
categories.config++;
|
|
2299
|
+
} else if (path.match(/\.css$|\.scss$|\.less$|\.styl$/i)) {
|
|
2300
|
+
categories.style++;
|
|
2301
|
+
} else if (['js', 'ts', 'jsx', 'tsx', 'py', 'go', 'rs', 'java', 'c', 'cpp'].includes(ext)) {
|
|
2302
|
+
categories.source++;
|
|
2303
|
+
}
|
|
2304
|
+
});
|
|
2305
|
+
|
|
2306
|
+
const total = Object.values(categories).reduce((a, b) => a + b, 0);
|
|
2307
|
+
|
|
2308
|
+
if (total === 0) {
|
|
2309
|
+
return { type: 'feat', source: 'files-default', confidence: 30 };
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
// Determine primary type
|
|
2313
|
+
if (categories.docs / total > 0.6) {
|
|
2314
|
+
return { type: 'docs', source: 'files', confidence: 80 };
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
if (categories.test / total > 0.6) {
|
|
2318
|
+
return { type: 'test', source: 'files', confidence: 80 };
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
if (categories.config / total > 0.6) {
|
|
2322
|
+
return { type: 'chore', source: 'files', confidence: 70 };
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
if (categories.style / total > 0.6) {
|
|
2326
|
+
return { type: 'style', source: 'files', confidence: 75 };
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// Mixed or source-heavy changes - look at modification patterns
|
|
2330
|
+
const hasNewFiles = changes.some(c => c.status === 'A' || c.status === '??');
|
|
2331
|
+
const hasDeletedFiles = changes.some(c => c.status === 'D');
|
|
2332
|
+
|
|
2333
|
+
if (hasNewFiles && !hasDeletedFiles) {
|
|
2334
|
+
return { type: 'feat', source: 'files-new', confidence: 65 };
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
if (hasDeletedFiles) {
|
|
2338
|
+
return { type: 'refactor', source: 'files-deleted', confidence: 60 };
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// Default to feat for source changes
|
|
2342
|
+
return { type: 'feat', source: 'files-mixed', confidence: 50 };
|
|
2343
|
+
}
|
|
2344
|
+
|
|
1993
2345
|
// ========================================
|
|
1994
2346
|
// CHANGELOG UTILITIES (EXTENDED)
|
|
1995
2347
|
// ========================================
|