ReviewOS

stacks/logsmith

Generated CHANGELOG.md skips heading levels (H1 → H3) when no version is set

#3392
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Summary

When logsmith generates a changelog for an unreleased state (no version provided), the section headings (### 🐛 Bug Fixes, ### 🧹 Chores, ### Contributors, etc.) are emitted at H3 directly under the file's # Changelog H1 — skipping H2 entirely. This violates markdown/heading-increment and breaks lint in any consuming repo.

Repro

In any repo using logsmith, run:

bunx --bun logsmith --output CHANGELOG.md

When there are unreleased commits and no version tag, the generated file looks like:

# Changelog

[Compare changes](https://github.com/owner/repo/compare/v0.13.2...HEAD)

### 🐛 Bug Fixes
- ...

### 🧹 Chores
- ...

### Contributors
- ...

Then run any markdown linter (e.g. pickier with markdown/heading-increment: error):

CHANGELOG.md
  4:1  error  Heading level should not skip from h1 to h3   markdown/heading-increment

Expected

Every section header should be reachable by walking heading levels one step at a time. With no version (unreleased) the sections should sit at H2, not H3:

# Changelog

[Compare changes](...)

## 🐛 Bug Fixes
- ...

## 🧹 Chores
- ...

## Contributors
- ...

When a version is present, the existing structure is correct — the version goes at H2 and sections at H3:

# Changelog

## v0.13.3
[Compare changes](...)

### 🐛 Bug Fixes
- ...

Where it's coming from

packages/logsmith/src/utils.ts → generateChangelogContent() around lines 369–470:

if (changelog.version) {
  lines.push(`## ${config.versionPrefix}${changelog.version}`)  // H2
  ...
}

// ...later, unconditionally:
for (const section of changelog.sections) {
  lines.push(`### ${section.title}`)  // H3 — but no H2 above when version is empty
  ...
}

if (changelog.contributors.length > 0 && !config.excludeEmail) {
  lines.push(`### ${getLabel('contributors', config.language)}`)  // same issue
  ...
}

When changelog.version is falsy, the H2 line is skipped, but the H3 sections still emit. The outer wrapper in packages/logsmith/src/changelog.ts (line ~148) then prepends # Changelog as H1 — producing the H1→H3 jump.

Real-world impact

This breaks CI lint across our 95 repos that use logsmith + pickier. We've been demoting H3→H2 manually in each affected CHANGELOG.md, but those edits don't survive the next bunx logsmith run.

Suggested fix

In generateChangelogContent, derive the section level from whether a version header was emitted:

const sectionLevel = changelog.version ? '###' : '##'

for (const section of changelog.sections) {
  lines.push(`${sectionLevel} ${section.title}`)
  ...
}

// same for contributors:
lines.push(`${sectionLevel} ${getLabel('contributors', config.language)}`)

Alternatively, always emit an H2 anchor — e.g. ## Unreleased when no version — but the dynamic-level approach is less intrusive to existing CHANGELOG layouts.

Older entries in some long-lived CHANGELOGs (generated by an earlier logsmith) used H2 for sections (## 🧹 Chores). The recent regression is when this changed to H3-without-an-H2-parent for the unreleased block.

Version

logsmith — installed via bunx logsmith (latest at time of report)

9 days ago

Fixed in e294891 (on main). generateChangelogContent now derives the section level from whether a version header is emitted: '##' for the unreleased block (sits directly under the '# Changelog' H1) and '###' when nested under an H2 version header — no more H1→H3 skip / markdown/heading-increment failures.

Sign in to comment on this issue.