@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.
@@ -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
  // ========================================