@logickernel/agileflow 0.2.2 → 0.4.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.
@@ -1,166 +1,289 @@
1
- # Version-Centric CI/CD Approach
1
+ # Version-Centric CI/CD
2
2
 
3
- AgileFlow introduces a revolutionary approach to CI/CD that prioritizes **version management over environment-based deployments**. This paradigm shift eliminates the complexity of managing multiple deployment branches and environments, replacing them with a streamlined, version-focused workflow.
3
+ AgileFlow enables a **version-centric approach** to CI/CD where versioning is decoupled from build and deployment. This architecture simplifies pipelines and provides flexibility.
4
4
 
5
- ## Traditional Git-Based Flows vs. AgileFlow
5
+ ## The Decoupled Architecture
6
6
 
7
- ### Traditional Approach (Branch-Based Environments)
8
- Traditional CI/CD pipelines often rely on branch-based environment management:
7
+ ```
8
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
9
+ │ Merge to main │ │ Tag: v1.2.3 │ │ Build/Deploy │
10
+ │ │ ──────▶ │ │ ──────▶ │ │
11
+ │ AgileFlow │ │ (event) │ │ Your pipelines │
12
+ │ creates tag │ │ │ │ │
13
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
14
+ ```
15
+
16
+ ### How It Works
17
+
18
+ 1. **On merge to main**: AgileFlow analyzes commits and creates a version tag
19
+ 2. **Tag creation event**: Triggers your build and deploy pipelines
20
+ 3. **Build/Deploy**: Uses the tag as the version identifier
21
+
22
+ ### Benefits
23
+
24
+ - **Separation of concerns** — Versioning is independent from build/deploy
25
+ - **Flexibility** — Any process can hook into tag creation
26
+ - **Simplicity** — Each pipeline has one responsibility
27
+ - **Reusability** — Same build pipeline for all versions
28
+ - **Auditability** — Clear version trail for every deployment
29
+
30
+ ---
31
+
32
+ ## Traditional vs. Version-Centric
33
+
34
+ ### Traditional (Coupled)
9
35
 
10
- ```mermaid
11
- graph LR
12
- A[main branch] --> B[staging branch]
13
- B --> C[production branch]
14
- D[feature branch] --> A
15
- E[hotfix branch] --> A
36
+ ```yaml
37
+ # Everything in one pipeline
38
+ on: push to main
39
+ calculate version
40
+ build
41
+ deploy staging
42
+ → deploy production
16
43
  ```
17
44
 
18
- **Problems with Traditional Approach:**
19
- - **Environment Drift**: Different branches can diverge, leading to "works in staging, breaks in production"
20
- - **Complex Branch Management**: Multiple long-lived branches require constant synchronization
21
- - **Deployment Uncertainty**: Hard to know exactly what version is running in each environment
22
- - **Rollback Complexity**: Rolling back requires managing multiple branch states
23
- - **Version Inconsistency**: Different environments may run different versions
24
-
25
- ### AgileFlow Approach (Version-Centric)
26
- AgileFlow simplifies this by making **every deployment, test, and operation version-centric**:
27
-
28
- ```mermaid
29
- graph LR
30
- A[main branch] --> B[Version v1.2.3]
31
- B --> C[Deploy to Staging]
32
- B --> D[Deploy to Production]
33
- B --> E[Run Integration Tests]
34
- B --> F[Performance Testing]
45
+ **Problems:**
46
+ - Complex, monolithic pipelines
47
+ - Version logic mixed with build logic
48
+ - Hard to rerun individual steps
49
+
50
+ ### Version-Centric (Decoupled)
51
+
52
+ ```yaml
53
+ # Pipeline 1: Versioning
54
+ on: push to main
55
+ → AgileFlow creates tag
56
+
57
+ # Pipeline 2: Release
58
+ on: tag created
59
+ build with tag version
60
+ deploy staging
61
+ deploy production
35
62
  ```
36
63
 
37
- **Benefits of Version-Centric Approach:**
38
- - **Single Source of Truth**: All environments run the exact same version
39
- - **Predictable Deployments**: Every deployment uses a well-identified, immutable version
40
- - **Simplified Rollbacks**: Rollback to any previous version with confidence
41
- - **Consistent Testing**: All tests run against the same version that will be deployed
42
- - **Clear Audit Trail**: Every deployment is tied to a specific, documented version
64
+ **Benefits:**
65
+ - Simple, focused pipelines
66
+ - Versioning completely separate
67
+ - Easy to rerun builds for any version
43
68
 
44
- ## Simplified Pipeline Stages
69
+ ---
45
70
 
46
- AgileFlow's CI/CD pipeline consists of just 6 focused stages:
71
+ ## Implementation
47
72
 
48
- ### 1. **Version** Stage
49
- - **Purpose**: Generate semantic version and comprehensive release notes
50
- - **Output**: `VERSION` variable available to all subsequent stages
51
- - **Automation**: Uses AgileFlow tool to analyze commit history and determine next version
52
- - **Artifacts**: Version tag pushed to repository, release notes generated
73
+ ### GitHub Actions
53
74
 
54
- ### 2. **Test** Stage
55
- - **Purpose**: Run tests against the source code before building
56
- - **Input**: Uses the source code and `VERSION` variable from the version stage
57
- - **Output**: Test results and validation that the code is ready for building
58
- - **Benefits**: Catch issues early before building artifacts
75
+ **Versioning workflow** (`.github/workflows/version.yml`):
76
+ ```yaml
77
+ name: Version
78
+ on:
79
+ push:
80
+ branches: [main]
59
81
 
60
- ### 3. **Build** Stage
61
- - **Purpose**: Create application artifacts and Docker images
62
- - **Input**: Uses the `VERSION` variable from the version stage
63
- - **Output**: Versioned artifacts (e.g., `app:v1.2.3`, `frontend:v1.2.3`)
64
- - **Consistency**: All builds use the same version identifier
82
+ jobs:
83
+ version:
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - uses: actions/checkout@v4
87
+ with:
88
+ fetch-depth: 0
65
89
 
66
- ### 4. **Deploy** Stage
67
- - **Purpose**: Deploy the versioned artifacts to various environments
68
- - **Approach**: Deploy the same version to staging, production, etc.
69
- - **Benefits**: Identical behavior across all environments
70
- - **Rollback**: Simple version-based rollback (e.g., "rollback to v1.2.2")
90
+ - uses: actions/setup-node@v4
91
+ with:
92
+ node-version: '20'
71
93
 
72
- ### 5. **E2E** Stage
73
- - **Purpose**: Run end-to-end tests against the deployed version
74
- - **Scope**: Integration tests, end-to-end tests, performance tests
75
- - **Target**: Tests run against the actual deployed version
76
- - **Confidence**: Tests validate exactly what will run in production
94
+ - name: Create version tag
95
+ env:
96
+ AGILEFLOW_TOKEN: ${{ secrets.AGILEFLOW_TOKEN }}
97
+ run: npx @logickernel/agileflow github
98
+ ```
77
99
 
78
- ### 6. **Clean** Stage
79
- - **Purpose**: Cleanup temporary resources and artifacts
80
- - **Maintenance**: Remove old Docker images, temporary files, etc.
81
- - **Optimization**: Keep only necessary version artifacts
100
+ **Release workflow** (`.github/workflows/release.yml`):
101
+ ```yaml
102
+ name: Release
103
+ on:
104
+ push:
105
+ tags:
106
+ - 'v*'
82
107
 
83
- ## Real-World Example
108
+ jobs:
109
+ build:
110
+ runs-on: ubuntu-latest
111
+ steps:
112
+ - uses: actions/checkout@v4
84
113
 
85
- Here's how the version-centric approach works in practice:
114
+ - name: Get version
115
+ run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
116
+
117
+ - name: Build
118
+ run: docker build -t myapp:$VERSION .
119
+
120
+ deploy-staging:
121
+ needs: build
122
+ runs-on: ubuntu-latest
123
+ environment: staging
124
+ steps:
125
+ - run: kubectl set image deployment/myapp myapp=myapp:$VERSION
126
+
127
+ deploy-production:
128
+ needs: build
129
+ runs-on: ubuntu-latest
130
+ environment: production
131
+ steps:
132
+ - run: kubectl set image deployment/myapp myapp=myapp:$VERSION
133
+ ```
134
+
135
+ ### GitLab CI
86
136
 
87
137
  ```yaml
88
- # .gitlab-ci.yml
89
- include:
90
- - local: templates/AgileFlow.gitlab-ci.yml
138
+ stages:
139
+ - version
140
+ - build
141
+ - deploy
91
142
 
92
- # Test stage runs tests against source code
93
- test:
94
- stage: test
143
+ # Versioning - runs on merge to main
144
+ agileflow:
145
+ stage: version
146
+ image: node:20-alpine
95
147
  script:
96
- - npm test
97
- - npm run lint
148
+ - npx @logickernel/agileflow gitlab
149
+ rules:
150
+ - if: '$CI_COMMIT_BRANCH == "main"'
98
151
 
99
- # Build stage uses VERSION from agileflow job
152
+ # Build - runs on tag creation
100
153
  build:
101
154
  stage: build
102
155
  script:
103
- - docker build -t myapp:${VERSION} .
104
- - docker push myapp:${VERSION}
105
- needs:
106
- - test
107
-
108
- # Deploy stage deploys the same version everywhere
109
- deploy-testing:
110
- stage: deploy
111
- script:
112
- - kubectl set image deployment/myapp myapp=myapp:${VERSION}
113
- environment:
114
- name: testing
115
- needs:
116
- - build
156
+ - docker build -t myapp:$CI_COMMIT_TAG .
157
+ - docker push myapp:$CI_COMMIT_TAG
158
+ rules:
159
+ - if: '$CI_COMMIT_TAG =~ /^v/'
117
160
 
161
+ # Deploy - runs on tag creation
118
162
  deploy-staging:
119
163
  stage: deploy
120
164
  script:
121
- - kubectl set image deployment/myapp myapp=myapp:${VERSION}
165
+ - kubectl set image deployment/myapp myapp=myapp:$CI_COMMIT_TAG
122
166
  environment:
123
167
  name: staging
124
- when: manual
125
- needs:
126
- - build
168
+ rules:
169
+ - if: '$CI_COMMIT_TAG =~ /^v/'
127
170
 
128
171
  deploy-production:
129
172
  stage: deploy
130
173
  script:
131
- - kubectl set image deployment/myapp myapp=myapp:${VERSION}
174
+ - kubectl set image deployment/myapp myapp=myapp:$CI_COMMIT_TAG
132
175
  environment:
133
176
  name: production
134
177
  when: manual
135
- needs:
136
- - build
178
+ rules:
179
+ - if: '$CI_COMMIT_TAG =~ /^v/'
180
+ ```
181
+
182
+ ---
137
183
 
138
- # E2E stage validates the deployed version
139
- integration-tests:
140
- stage: e2e
184
+ ## Version-Centric Deployments
185
+
186
+ ### All Environments Use the Same Version
187
+
188
+ ```
189
+ Tag v1.2.3
190
+
191
+ ├──▶ Build: myapp:v1.2.3
192
+
193
+ ├──▶ Staging: myapp:v1.2.3
194
+
195
+ └──▶ Production: myapp:v1.2.3
196
+ ```
197
+
198
+ No environment drift — every environment runs identical code.
199
+
200
+ ### Simple Rollbacks
201
+
202
+ ```bash
203
+ # Rollback = deploy previous tag
204
+ kubectl set image deployment/myapp myapp=myapp:v1.2.2
205
+ ```
206
+
207
+ ### Clear Audit Trail
208
+
209
+ ```bash
210
+ # What version is running?
211
+ kubectl get deployment myapp -o jsonpath='{.spec.template.spec.containers[0].image}'
212
+ # myapp:v1.2.3
213
+
214
+ # What's in that version?
215
+ git show v1.2.3
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Advanced Patterns
221
+
222
+ ### Conditional Deployments
223
+
224
+ Deploy only specific version types:
225
+
226
+ ```yaml
227
+ # Only deploy minor/major versions to production
228
+ deploy-production:
229
+ rules:
230
+ - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.0$/'
231
+ ```
232
+
233
+ ### Multiple Services
234
+
235
+ Same version for all services in a monorepo:
236
+
237
+ ```yaml
238
+ build-backend:
239
+ script:
240
+ - docker build -t backend:$CI_COMMIT_TAG ./backend
241
+
242
+ build-frontend:
243
+ script:
244
+ - docker build -t frontend:$CI_COMMIT_TAG ./frontend
245
+ ```
246
+
247
+ ### Notifications
248
+
249
+ Announce new versions:
250
+
251
+ ```yaml
252
+ notify:
141
253
  script:
142
- - ./run-tests.sh --version ${VERSION}
143
- needs:
144
- - deploy-testing
254
+ - |
255
+ curl -X POST "$SLACK_WEBHOOK" \
256
+ -d "{\"text\": \"Released $CI_COMMIT_TAG\"}"
257
+ rules:
258
+ - if: '$CI_COMMIT_TAG =~ /^v/'
145
259
  ```
146
260
 
261
+ ---
262
+
147
263
  ## Key Advantages
148
264
 
149
- 1. **Eliminates Environment Drift**: Staging and production always run identical versions
150
- 2. **Simplifies Operations**: DevOps teams work with versions, not branch states
151
- 3. **Improves Reliability**: Every deployment is predictable and auditable
152
- 4. **Reduces Complexity**: No need to manage multiple deployment branches
153
- 5. **Enhances Security**: Version-based deployments provide clear audit trails
154
- 6. **Facilitates Compliance**: Easy to demonstrate what version is running where
265
+ 1. **Eliminates environment drift** All environments run identical versions
266
+ 2. **Simplifies operations** Work with versions, not branch states
267
+ 3. **Enables easy rollbacks** Just redeploy a previous tag
268
+ 4. **Provides clear audit trail** Every deployment tied to a version
269
+ 5. **Decouples concerns** Versioning separate from build/deploy
270
+
271
+ ---
155
272
 
156
273
  ## Migration Path
157
274
 
158
- If you're currently using a traditional branch-based approach:
275
+ If using a traditional coupled approach:
276
+
277
+ 1. **Add AgileFlow** — Create versioning workflow
278
+ 2. **Add tag-triggered workflow** — For build/deploy
279
+ 3. **Test both workflows** — Verify tags trigger releases
280
+ 4. **Remove old logic** — Clean up version calculation from build pipeline
281
+
282
+ ---
159
283
 
160
- 1. **Start with AgileFlow**: Include the template and let it generate versions
161
- 2. **Gradually Simplify**: Remove environment-specific branches over time
162
- 3. **Update Deployments**: Modify deployment scripts to use `${VERSION}` variable
163
- 4. **Standardize Testing**: Run all tests against the versioned artifacts
164
- 5. **Document Changes**: Update runbooks to reference versions instead of branches
284
+ ## Related Documentation
165
285
 
166
- This approach transforms your CI/CD from a complex, branch-managed system into a simple, version-driven pipeline where every deployment is predictable, auditable, and reliable.
286
+ - [Getting Started](./getting-started.md) Quick start
287
+ - [Installation Guide](./installation.md) — Setup instructions
288
+ - [Branching Strategy](./branching-strategy.md) — Git workflow
289
+ - [Best Practices](./best-practices.md) — Recommended patterns
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logickernel/agileflow",
3
- "version": "0.2.2",
3
+ "version": "0.4.1",
4
4
  "description": "Automatic semantic versioning and changelog generation based on conventional commits",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -14,7 +14,8 @@
14
14
  "README.md"
15
15
  ],
16
16
  "scripts": {
17
- "test": "echo \"Error: no test specified\" && exit 1"
17
+ "test": "echo \"Error: no test specified\" && exit 1",
18
+ "prepack": "chmod +x bin/agileflow"
18
19
  },
19
20
  "keywords": [
20
21
  "semantic-versioning",
package/src/index.js CHANGED
@@ -12,9 +12,9 @@ Usage:
12
12
 
13
13
  Commands:
14
14
  <none> Prints the current version, next version, commits, and changelog
15
- push Push a semantic version tag to the remote repository (native git)
16
- gitlab Push a semantic version tag via GitLab API (for GitLab CI)
17
- github Push a semantic version tag via GitHub API (for GitHub Actions)
15
+ push Push a semantic version tag to the remote repository
16
+ gitlab Create a semantic version tag via GitLab API (for GitLab CI)
17
+ github Create a semantic version tag via GitHub API (for GitHub Actions)
18
18
 
19
19
  Options:
20
20
  --quiet Only output the next version (or empty if no bump)
@@ -26,11 +26,32 @@ For more information, visit: https://code.logickernel.com/tools/agileflow
26
26
  }
27
27
 
28
28
  /**
29
- * Parses command line arguments.
29
+ * Valid options that can be passed to commands.
30
+ */
31
+ const VALID_OPTIONS = ['--quiet', '--help', '-h', '--version', '-v'];
32
+
33
+ /**
34
+ * Valid commands.
35
+ */
36
+ const VALID_COMMANDS = ['push', 'gitlab', 'github'];
37
+
38
+ /**
39
+ * Parses command line arguments and validates them.
30
40
  * @param {Array<string>} args - Command line arguments
31
41
  * @returns {{quiet: boolean}}
42
+ * @throws {Error} If invalid options are found
32
43
  */
33
44
  function parseArgs(args) {
45
+ // Check for invalid options
46
+ for (const arg of args) {
47
+ if (arg.startsWith('--') && !VALID_OPTIONS.includes(arg)) {
48
+ throw new Error(`Unknown option: ${arg}`);
49
+ }
50
+ if (arg.startsWith('-') && !arg.startsWith('--') && !VALID_OPTIONS.includes(arg)) {
51
+ throw new Error(`Unknown option: ${arg}`);
52
+ }
53
+ }
54
+
34
55
  return {
35
56
  quiet: args.includes('--quiet'),
36
57
  };
@@ -61,7 +82,7 @@ function displayVersionInfo(info, quiet) {
61
82
  }
62
83
 
63
84
  console.log(`\nCurrent version: ${currentVersion || 'none'}`);
64
- console.log(`New version: ${newVersion || 'no bump needed'}`);
85
+ console.log(`New version: ${newVersion || 'no bump needed'}`);
65
86
  if (changelog) {
66
87
  console.log(`\nChangelog:\n\n${changelog}`);
67
88
  }
@@ -113,7 +134,17 @@ async function handlePushCommand(pushType, options) {
113
134
 
114
135
  async function main() {
115
136
  const [, , cmd, ...rest] = process.argv;
116
- const options = parseArgs(cmd ? [cmd, ...rest] : rest);
137
+
138
+ let options;
139
+ try {
140
+ options = parseArgs(cmd ? [cmd, ...rest] : rest);
141
+ } catch (err) {
142
+ console.error(`Error: ${err.message}`);
143
+ console.error();
144
+ printHelp();
145
+ process.exit(1);
146
+ return;
147
+ }
117
148
 
118
149
  // Handle help
119
150
  if (cmd === '-h' || cmd === '--help' || cmd === 'help') {
@@ -134,13 +165,22 @@ async function main() {
134
165
  }
135
166
 
136
167
  // Unknown command (not an option)
137
- if (cmd && !cmd.startsWith('--')) {
168
+ if (cmd && !cmd.startsWith('--') && !cmd.startsWith('-')) {
138
169
  console.error(`Error: Unknown command "${cmd}"`);
139
170
  console.error();
140
171
  printHelp();
141
172
  process.exit(1);
142
173
  }
143
174
 
175
+ // Invalid option (starts with -- but not valid)
176
+ if (cmd && cmd.startsWith('--') && !VALID_OPTIONS.includes(cmd)) {
177
+ console.error(`Error: Unknown option "${cmd}"`);
178
+ console.error();
179
+ printHelp();
180
+ process.exit(1);
181
+ return;
182
+ }
183
+
144
184
  // Default: show version info
145
185
  const info = await processVersionInfo();
146
186
  displayVersionInfo(info, options.quiet);
package/src/utils.js CHANGED
@@ -170,13 +170,16 @@ function extractIssueReference(message) {
170
170
  * @param {string} subject - First line of commit message
171
171
  * @param {Object} parsed - Parsed conventional commit info
172
172
  * @param {string} fullMessage - Full commit message
173
+ * @param {boolean} isBreakingSection - Whether this is for the breaking changes section
173
174
  * @returns {string} Formatted description
174
175
  */
175
- function formatChangelogDescription(subject, parsed, fullMessage) {
176
+ function formatChangelogDescription(subject, parsed, fullMessage, isBreakingSection = false) {
176
177
  if (!parsed) return subject;
177
178
  let description = parsed.description;
178
179
  const isBreaking = parsed.breaking || /BREAKING CHANGE:/i.test(fullMessage);
179
- if (isBreaking) {
180
+
181
+ // Only add BREAKING prefix if not in breaking changes section
182
+ if (isBreaking && !isBreakingSection) {
180
183
  description = `BREAKING: ${description}`;
181
184
  }
182
185
  return description;
@@ -229,13 +232,25 @@ function applyVersionBump(current, bump) {
229
232
  }
230
233
  }
231
234
 
235
+ /**
236
+ * Checks if a commit is a breaking change.
237
+ * @param {Object} commit - Commit object
238
+ * @param {Object} parsed - Parsed conventional commit info
239
+ * @returns {boolean}
240
+ */
241
+ function isBreakingChange(commit, parsed) {
242
+ if (!parsed) return false;
243
+ return parsed.breaking || /BREAKING CHANGE:/i.test(commit.message);
244
+ }
245
+
232
246
  /**
233
247
  * Analyzes commits to determine version bump requirements.
234
248
  * @param {Array} commits - Array of commit objects
235
- * @returns {{hasBreaking: boolean, hasFeat: boolean, hasPatchTypes: boolean, commitsByType: Object}}
249
+ * @returns {{hasBreaking: boolean, hasFeat: boolean, hasPatchTypes: boolean, commitsByType: Object, breakingCommits: Array}}
236
250
  */
237
251
  function analyzeCommitsForVersioning(commits) {
238
252
  const commitsByType = Object.fromEntries(TYPE_ORDER.map(t => [t, []]));
253
+ const breakingCommits = [];
239
254
  let hasBreaking = false, hasFeat = false, hasPatchTypes = false;
240
255
 
241
256
  for (const commit of commits) {
@@ -243,18 +258,23 @@ function analyzeCommitsForVersioning(commits) {
243
258
  if (!parsed) continue;
244
259
 
245
260
  const { type, breaking } = parsed;
246
- const isBreaking = breaking || /BREAKING CHANGE:/i.test(commit.message);
261
+ const isBreaking = isBreakingChange(commit, parsed);
247
262
 
248
- if (isBreaking) hasBreaking = true;
249
- if (type === 'feat') hasFeat = true;
250
- else if (PATCH_TYPES.includes(type)) hasPatchTypes = true;
251
-
252
- if (commitsByType[type]) {
253
- commitsByType[type].push(commit);
263
+ if (isBreaking) {
264
+ hasBreaking = true;
265
+ breakingCommits.push(commit);
266
+ } else {
267
+ // Only add to type sections if not breaking
268
+ if (type === 'feat') hasFeat = true;
269
+ else if (PATCH_TYPES.includes(type)) hasPatchTypes = true;
270
+
271
+ if (commitsByType[type]) {
272
+ commitsByType[type].push(commit);
273
+ }
254
274
  }
255
275
  }
256
276
 
257
- return { hasBreaking, hasFeat, hasPatchTypes, commitsByType };
277
+ return { hasBreaking, hasFeat, hasPatchTypes, commitsByType, breakingCommits };
258
278
  }
259
279
 
260
280
  /**
@@ -270,9 +290,10 @@ function capitalize(str) {
270
290
  /**
271
291
  * Generates changelog entries for a commit type section.
272
292
  * @param {Array} commits - Commits of this type
293
+ * @param {boolean} isBreakingSection - Whether this is for the breaking changes section
273
294
  * @returns {Array<string>} Changelog lines
274
295
  */
275
- function generateTypeChangelog(commits) {
296
+ function generateTypeChangelog(commits, isBreakingSection = false) {
276
297
  const byScope = {};
277
298
  const noScope = [];
278
299
 
@@ -283,7 +304,7 @@ function generateTypeChangelog(commits) {
283
304
  const subject = commit.message.split('\n')[0].trim();
284
305
  const entry = {
285
306
  scope: parsed.scope,
286
- description: formatChangelogDescription(subject, parsed, commit.message),
307
+ description: formatChangelogDescription(subject, parsed, commit.message, isBreakingSection),
287
308
  issueRef: extractIssueReference(commit.message) || '',
288
309
  };
289
310
 
@@ -323,6 +344,15 @@ function calculateNextVersionAndChangelog(expandedInfo) {
323
344
 
324
345
  // Generate changelog
325
346
  const changelogLines = [];
347
+
348
+ // Add breaking changes section first if any
349
+ if (analysis.breakingCommits.length > 0) {
350
+ changelogLines.push('BREAKING CHANGES:');
351
+ changelogLines.push(...generateTypeChangelog(analysis.breakingCommits, true));
352
+ changelogLines.push('');
353
+ }
354
+
355
+ // Add regular type sections
326
356
  for (const type of TYPE_ORDER) {
327
357
  const typeCommits = analysis.commitsByType[type];
328
358
  if (!typeCommits?.length) continue;