Skip to content

🚀 release: v0.1.1-beta#1

Merged
warengonzaga merged 23 commits intomainfrom
dev
Sep 21, 2025
Merged

🚀 release: v0.1.1-beta#1
warengonzaga merged 23 commits intomainfrom
dev

Conversation

@warengonzaga
Copy link
Owner

@warengonzaga warengonzaga commented Jul 2, 2025

Summary by CodeRabbit

  • New Features
    • Added two expert chat modes: "WG Code Alchemist" (clean-code guidance) and "WG Code Sentinel" (security reviews).
  • Chores
    • Bumped package version to 0.1.1-beta; dependency updates and changelog metadata additions.
  • Enhancements
    • Smarter changelog workflow: scenario-aware updates, incremental analysis, append-only mode, batching/caching of descriptions, refined Unreleased handling, and safer backup/restore writes.
  • Bug Fixes
    • Miscellaneous changelog generation fixes and lint/security tweaks.
  • New Tools
    • New utility to resolve Git commit hashes by reference and updated README demo/banner assets.

- Fix double blank lines between changelog version sections
- Ensure generated changelogs end with single newline for markdownlint
- Remove duplicate backup file creation (now creates only timestamped backup)
- Improve overall changelog formatting consistency

Fixes spacing issues that were causing markdownlint failures and
eliminates redundant backup files that were being created during
changelog generation.
Copilot AI review requested due to automatic review settings July 2, 2025 16:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR bumps the package version and introduces advanced changelog generation and parsing enhancements, including scenario-based merging, backup/rollback support, and optimized repository analysis.

  • Added a getCommitHash method in GitService.
  • Refactored changelog generation in KeepChangelogGenerator with scenario-based merging and newline trimming.
  • Enhanced ChangelogParser and MagicRelease with magicr detection, dynamic imports, and safe write with backups.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/core/git/GitService.ts Added getCommitHash method
src/core/generator/KeepChangelogGenerator.ts Replaced merge logic with processEntriesWithExistingChangelog and updated generate
src/core/generator/ChangelogParser.ts Introduced methods for magicr detection, section extraction, and raw parsing
src/core/MagicRelease.ts Added scenario-based processing, safeWriteChangelog, and backup/rollback logic
package.json Bumped version from 0.1.0-beta to 0.1.1-beta
CHANGELOG.md Inserted magicr HTML comment marker and new release entries

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 3, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds two new chatmode configurations; implements scenario-aware, incremental and append-only changelog parsing/generation with caching, batch AI processing, deduplication, changelog integrity checks, and safe write with timestamped backups and rollback in MagicRelease; updates CHANGELOG header and package metadata; and adds a Git utility to resolve refs to commit hashes.

Changes

Cohort / File(s) Change Summary
Chat modes
.github/chatmodes/WG Code Alchemist.chatmode.md, .github/chatmodes/WG Code Sentinel.chatmode.md
Added two new chat mode configuration files defining expert personas for code quality (Code Alchemist) and security review (Code Sentinel) with metadata, missions, domains, transformation/review approaches, communication style, and clarification protocols.
Changelog & package metadata
CHANGELOG.md, package.json
Inserted Magic Release (magicr) generated header and expanded Unreleased entries and metadata in CHANGELOG.md; bumped package version to 0.1.1-beta and updated zod dependency to ^3.25.76; ensured trailing newline.
Core changelog engine
src/core/MagicRelease.ts
Added ConversionScenario model and scenario detection/execution; split analysis into analyzeFromChangelog (incremental) and analyzeRepositoryFull (full fallback); added commit deduplication, documented-commit detection, description caching, batch AI processing (optimizeAIProcessing, batchProcessDescriptions), changelog integrity validation, multi-entry scenario-based generation, and robust safe write flow with timestamped backups, rollback and cleanup.
Changelog parsing utilities
src/core/generator/ChangelogParser.ts
Enhanced parsing to preserve commit-hash references and added utilities: isMagicrGenerated, getLatestVersionFromChangelog, getDocumentedCommits, getUnreleasedContent, extractUnreleasedRaw, convertUnreleasedToVersion, and removeUnreleasedSection.
Keep Changelog generator
src/core/generator/KeepChangelogGenerator.ts
Added GeneratorOptions.appendOnly?: boolean; replaced naive merge with processEntriesWithExistingChangelog and scenario-aware processEntriesWithScenario; added append-only flow (generateAppendOnly) with separate handlers for magicr vs non-magicr changelogs and helpers (appendToMagicrChangelog, appendToNonMagicrChangelog, updateUnreleasedSection, insertUnreleasedAtTop, insertVersionSection, updateVersionSection); removed previous mergeEntries; trimmed local backup logic (backup now handled in MagicRelease).
Git helper
src/core/git/GitService.ts
Added public getCommitHash(ref?: string): string to resolve a Git ref via git rev-parse, trimming output and throwing GitError with logging on failure.
Docs / assets
README.md, .github/assets/*
Replaced external banner with local asset .github/assets/banner.png; added demo GIF .github/assets/demo.gif and a Demo section to README.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MagicRelease
    participant ChangelogParser
    participant GitService
    participant KeepChangelogGenerator

    Note over MagicRelease,ChangelogParser: Start generation
    User->>MagicRelease: generate(options)
    MagicRelease->>ChangelogParser: parse existing changelog (if present)
    ChangelogParser-->>MagicRelease: documented commits, unreleased raw/content, latest version
    alt existing changelog present
        MagicRelease->>MagicRelease: deduplicate commits vs documented set
        MagicRelease->>GitService: getCommitHash(HEAD) / fetch commits
        GitService-->>MagicRelease: head hash / commit list
        MagicRelease->>MagicRelease: detectConversionScenario -> currentScenario
        MagicRelease->>MagicRelease: optimizeAIProcessing (batch, cache)
        MagicRelease->>KeepChangelogGenerator: processEntriesWithExistingChangelog(entries, existingContent, ...)
    else no usable changelog
        MagicRelease->>GitService: fetch repo commits
        GitService-->>MagicRelease: commit list
        MagicRelease->>MagicRelease: analyzeRepositoryFull -> categorize commits
        MagicRelease->>KeepChangelogGenerator: generate(entries, ...)
    end
    KeepChangelogGenerator-->>MagicRelease: merged/updated changelog content
    MagicRelease->>MagicRelease: create timestamped backup
    MagicRelease->>MagicRelease: safeWriteChangelog (write, rollback on failure, cleanup old backups)
    MagicRelease-->>User: updated changelog or error + restore
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibble lines and tidy notes,
I turn Unreleased into neat quotes,
I cache each crumb, I batch the song,
Backups snug if something goes wrong.
Hops and builds — the changelog's strong. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "🚀 release: v0.1.1-beta" is concise and accurately describes the primary purpose of the changeset — a release bump to v0.1.1-beta (matching the package.json version and CHANGELOG updates), so it summarizes the main change clearly for reviewers. The emoji is decorative but does not make the title misleading or unrelated to the diff.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/core/generator/KeepChangelogGenerator.ts (1)

149-151: Repeated dynamic import() hurts performance

processEntriesWithExistingChangelog imports ChangelogParser on every invocation.
Instantiate the parser once at the top-level (there’s already a past comment about this).

🧹 Nitpick comments (4)
.github/chatmodes/WG Code Sentinel.chatmode.md (1)

68-68: Remove trailing blank line for consistency.

-
.github/chatmodes/WG Code Alchemist.chatmode.md (1)

74-74: Remove trailing blank line for consistency.

-
src/core/generator/ChangelogParser.ts (1)

199-206: Case-sensitive magicr indicator may yield false negatives

content.includes('Generated by Magic Release (magicr)') fails if the comment is lower-cased or reformatted.
Use a case-insensitive regex to be resilient to manual edits.

-return content.includes(magicrIndicator);
+return new RegExp(magicrIndicator, 'i').test(content);
src/core/generator/KeepChangelogGenerator.ts (1)

606-615: Redundant case 'no-conversion'

switch already has a default; keeping an explicit no-conversion branch triggers Biome’s
noUselessSwitchCase warning.

-      case 'no-conversion':
-      default:
+      default:
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d286786 and a03b3e4.

📒 Files selected for processing (8)
  • .github/chatmodes/WG Code Alchemist.chatmode.md (1 hunks)
  • .github/chatmodes/WG Code Sentinel.chatmode.md (1 hunks)
  • CHANGELOG.md (3 hunks)
  • package.json (1 hunks)
  • src/core/MagicRelease.ts (11 hunks)
  • src/core/generator/ChangelogParser.ts (2 hunks)
  • src/core/generator/KeepChangelogGenerator.ts (5 hunks)
  • src/core/git/GitService.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/core/generator/ChangelogParser.ts (2)
src/utils/logger.ts (1)
  • logger (319-319)
src/types/interfaces.ts (1)
  • ChangelogEntry (140-151)
src/core/generator/KeepChangelogGenerator.ts (3)
src/types/interfaces.ts (1)
  • ChangelogEntry (140-151)
src/utils/logger.ts (1)
  • logger (319-319)
src/core/generator/ChangelogParser.ts (1)
  • isMagicrGenerated (203-206)
🪛 LanguageTool
.github/chatmodes/WG Code Alchemist.chatmode.md

[style] ~33-~33: The word ‘gonna’ is informal.
Context: ...n't Repeat Yourself), YAGNI (You Aren't Gonna Need It), and KISS (Keep It Simple, Stu...

(GONNA)


[typographical] ~34-~34: Except for inverted sentences, ‘Need It’ requires a question mark at the end of the sentence.
Context: ...toring patterns, and architectural best practices Code Transformation Approach: 1. ...

(MD_PRP_QUESTION_MARK)


[misspelling] ~43-~43: This word is normally spelled as one.
Context: ...eeply**: Identify specific code smells, anti-patterns, and improvement opportunities 3. **Exp...

(EN_COMPOUNDS_ANTI_PATTERNS)

🪛 Biome (1.9.4)
src/core/generator/KeepChangelogGenerator.ts

[error] 610-610: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

src/core/MagicRelease.ts

[error] 1085-1085: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1085-1090: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1118-1118: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

🔇 Additional comments (8)
.github/chatmodes/WG Code Sentinel.chatmode.md (1)

1-67: Excellent implementation of the security-focused chat mode.

The WG Code Sentinel configuration is comprehensive and well-structured. The security domains, review approach, and JARVIS-inspired communication style are clearly defined and professionally presented. This will provide valuable security review capabilities.

.github/chatmodes/WG Code Alchemist.chatmode.md (1)

1-73: Excellent implementation of the Clean Code-focused chat mode.

The WG Code Alchemist configuration perfectly complements the Code Sentinel mode. The coverage of Clean Code principles, SOLID design, and the transformation approach is comprehensive and well-articulated. The JARVIS-inspired communication style maintains consistency across both chat modes.

Note: The static analysis hints flagging "gonna" and "anti-patterns" are false positives - these are acceptable usage in the context of explaining YAGNI acronym and standard software terminology.

package.json (1)

3-3: Version bump looks correct for the v0.1.1-beta release.

The version update from "0.1.0-beta" to "0.1.1-beta" properly follows semantic versioning conventions and aligns with the PR objectives.

src/core/git/GitService.ts (1)

300-315: Well-implemented method that follows established patterns.

The getCommitHash method is properly structured with:

  • Consistent use of the existing execGit helper
  • Appropriate error handling and logging
  • Comprehensive JSDoc documentation
  • Sensible default parameter ('HEAD')

This addition enhances the Git service capabilities for the changelog generation workflow.

CHANGELOG.md (3)

2-6: Good addition of the Magic Release header comment.

The comment header properly identifies the file as managed by Magic Release and includes appropriate usage notes. This will help with automated changelog management.


16-23: Changelog entries are well-structured and properly categorized.

The new Unreleased entries follow the Keep a Changelog format correctly, with appropriate categorization into "Added" and "Fixed" sections and proper commit hash references for traceability.


24-27: New version section follows consistent formatting.

The 0.1.0-beta version section maintains consistency with the established changelog format and includes the proper date format.

src/core/MagicRelease.ts (1)

880-917: Deduplication – unnecessary O(N²) pass

You already iterate gitCommits once to build documentedHashLookup.
Filtering again over the full array doubles work. Build the lookup first, then produce
newCommits in a single pass.

[ suggest_optional_refactor ]

- Fix double blank lines between changelog version sections
- Ensure generated changelogs end with single newline for markdownlint
- Remove duplicate backup file creation (now creates only timestamped backup)
- Improve overall changelog formatting consistency

Fixes spacing issues that were causing markdownlint failures and
eliminates redundant backup files that were being created during
changelog generation.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
src/core/generator/KeepChangelogGenerator.ts (5)

460-470: Direct file write without backup risks data loss

The writeChangelog method writes directly to the file without creating a backup first. If the write operation fails partway through, the original changelog could be corrupted.

Consider implementing atomic write operations or creating a backup before writing:

 async writeChangelog(content: string, workingDir: string): Promise<void> {
   const filename = this.config.changelog?.filename ?? 'CHANGELOG.md';
   const changelogPath = path.join(workingDir, filename);

   try {
+    // Write to temporary file first
+    const tempPath = `${changelogPath}.tmp`;
+    writeFileSync(tempPath, content, 'utf8');
+    
+    // Atomically rename temp file to actual file
+    const fs = await import('fs/promises');
+    await fs.rename(tempPath, changelogPath);
+    
     logger.info(`Changelog written to: ${changelogPath}`);
   } catch (error) {
     throw new ChangelogError(`Failed to write changelog: ${error}`);
   }
 }

27-28: appendOnly flag is never used in the main generation flow

The appendOnly option is declared but the generate() method doesn't check this flag to decide whether to use generateAppendOnly(). This renders the append-only feature inaccessible.

Apply this fix to honor the appendOnly flag:

 async generate(entries: ChangelogEntry[], workingDir: string): Promise<string> {
   logger.debug('Generating Keep a Changelog format', {
     entriesCount: entries.length,
     includeLinks: this.options,
   });

   const existingChangelog = await this.loadExistingChangelog(workingDir);

+  // Use append-only mode if flag is set and existing changelog exists
+  if (this.options.appendOnly && existingChangelog) {
+    return this.generateAppendOnly(entries, existingChangelog, workingDir);
+  }
+
   // Handle the changelog generation with proper merging logic
   const finalEntries = await this.processEntriesWithExistingChangelog(
     entries,
     existingChangelog,
     workingDir
   );

Also applies to: 65-105


84-96: Missing blank lines between changelog entries

The formatEntry() calls don't add blank line separators between entries, which could cause them to merge together and reduce readability.

Add blank line separators after each entry:

 // Add unreleased section if needed
 const unreleasedEntry = finalEntries.find(entry => entry.version === 'Unreleased');
 if (unreleasedEntry && this.hasChanges(unreleasedEntry)) {
-  content += this.formatEntry(unreleasedEntry);
+  content += this.formatEntry(unreleasedEntry) + '\n';
 }

 // Add released versions (sorted by version, newest first)
 const releasedEntries = finalEntries
   .filter(entry => entry.version !== 'Unreleased')
   .sort((a, b) => this.compareVersions(b.version, a.version));

 for (const entry of releasedEntries) {
-  content += this.formatEntry(entry);
+  content += this.formatEntry(entry) + '\n';
 }

149-149: Dynamic imports cause unnecessary runtime overhead

Using dynamic import() inside frequently called methods introduces performance overhead. Each call re-imports the module.

Import the ChangelogParser once at the top of the file:

+import ChangelogParser from './ChangelogParser.js';
 import type { ChangelogEntry, Change, ChangeType, MagicReleaseConfig } from '../../types/index.js';

Then replace all dynamic imports:

-    const parser = new (await import('./ChangelogParser.js')).default();
+    const parser = new ChangelogParser();

Also applies to: 476-476, 500-500, 633-633, 741-741


137-238: Significant overlap between changelog processing methods

Both processEntriesWithExistingChangelog and processEntriesWithScenario handle similar changelog merging logic with different approaches. This duplication increases maintenance complexity.

Consider consolidating the merging logic into a single, flexible method that can handle all scenarios. This would reduce code duplication and simplify maintenance.

Also applies to: 480-611

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a03b3e4 and 2ffcc7e.

📒 Files selected for processing (1)
  • src/core/generator/KeepChangelogGenerator.ts (5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/core/generator/KeepChangelogGenerator.ts (3)
src/types/interfaces.ts (1)
  • ChangelogEntry (140-151)
src/utils/logger.ts (1)
  • logger (319-319)
src/core/generator/ChangelogParser.ts (1)
  • isMagicrGenerated (203-206)
🪛 Biome (1.9.4)
src/core/generator/KeepChangelogGenerator.ts

[error] 606-606: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

🔇 Additional comments (1)
src/core/generator/KeepChangelogGenerator.ts (1)

265-269: Good addition of magicr identification header

The new header comment effectively identifies magicr-generated changelogs, enabling intelligent updates in append-only mode.

dependabot bot and others added 10 commits July 22, 2025 11:55
Bumps the npm_and_yarn group with 1 update in the / directory: [form-data](https://github.com/form-data/form-data).


Updates `form-data` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](form-data/form-data@v4.0.3...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Snyk has created this PR to upgrade zod from 3.25.67 to 3.25.76.

See this package in yarn:
zod

See this project in Snyk:
https://app.snyk.io/org/warengonzaga/project/7547e199-9ed8-4239-a65b-ff592ddd4c79?utm_source=github&utm_medium=referral&page=upgrade-pr
…nd_yarn-e04d5d616f

Bump form-data from 4.0.3 to 4.0.4 in the npm_and_yarn group across 1 directory
…5444b0caba1833e2

[Snyk] Upgrade zod from 3.25.67 to 3.25.76
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@warengonzaga warengonzaga self-assigned this Sep 21, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/core/generator/KeepChangelogGenerator.ts (2)

329-353: PR/issue/commit “links” are emitted without reference definitions

Currently you output [#123] and [abc1234] but never define them; they render as plain text. Add reference link generation at the bottom.

@@
   if (this.options.includeCompareLinks && releasedEntries.length > 0) {
     content += this.generateCompareLinks(releasedEntries);
   }
+
+  // Reference links for PRs, issues, and commits
+  if (this.options.includePRLinks || this.options.includeIssueLinks || this.options.includeCommitLinks) {
+    const allEntries = [
+      ...(unreleasedEntry && this.hasChanges(unreleasedEntry) ? [unreleasedEntry] : []),
+      ...releasedEntries,
+    ];
+    content += this.generateReferenceLinks(allEntries);
+  }
@@
   private generateCompareLinks(entries: ChangelogEntry[]): string {
@@
     return links;
   }
+
+  /** Generate reference links for PRs, issues, and commits */
+  private generateReferenceLinks(entries: ChangelogEntry[]): string {
+    const repoUrl = this.getRepositoryUrl();
+    if (!repoUrl) return '';
+
+    const prs = new Set<number>();
+    const issues = new Set<number>();
+    const commits = new Set<string>();
+
+    for (const e of entries) {
+      for (const [, changes] of e.sections) {
+        for (const ch of changes) {
+          if (this.options.includePRLinks && typeof ch.pr === 'number') prs.add(ch.pr);
+          if (this.options.includeIssueLinks && Array.isArray(ch.issues)) {
+            for (const i of ch.issues) if (typeof i === 'number') issues.add(i);
+          }
+          if (this.options.includeCommitLinks && Array.isArray(ch.commits) && ch.commits.length > 0) {
+            const hash = ch.commits[0]?.hash?.slice(0, 7);
+            if (hash) commits.add(hash);
+          }
+        }
+      }
+    }
+
+    if (prs.size === 0 && issues.size === 0 && commits.size === 0) return '';
+
+    let out = '\n<!-- Reference Links -->\n';
+    for (const pr of prs) out += `[#${pr}]: ${repoUrl}/pull/${pr}\n`;
+    for (const is of issues) out += `[#${is}]: ${repoUrl}/issues/${is}\n`;
+    for (const c of commits) out += `[\`${c}\`]: ${repoUrl}/commit/${c}\n`;
+    return out;
+  }

Also applies to: 98-102, 358-390


460-469: Non-atomic write risks data loss; add backup + atomic rename

Write to a temp file, make a timestamped backup if file exists, then atomic rename. Update imports accordingly.

@@
-import { readFileSync, writeFileSync, existsSync } from 'fs';
+import { readFileSync, writeFileSync, existsSync, copyFileSync, renameSync } from 'fs';
@@
   async writeChangelog(content: string, workingDir: string): Promise<void> {
@@
-    try {
-      writeFileSync(changelogPath, content, 'utf8');
+    try {
+      const ts = new Date().toISOString().replace(/[:.]/g, '-');
+      const tmpPath = `${changelogPath}.tmp-${ts}`;
+      const backupPath = `${changelogPath}.bak-${ts}`;
+      if (existsSync(changelogPath)) {
+        copyFileSync(changelogPath, backupPath);
+      }
+      writeFileSync(tmpPath, content, 'utf8');
+      renameSync(tmpPath, changelogPath);
       logger.info(`Changelog written to: ${changelogPath}`);
     } catch (error) {
       throw new ChangelogError(`Failed to write changelog: ${error}`);
     }

Also applies to: 15-16

🧹 Nitpick comments (7)
src/core/generator/KeepChangelogGenerator.ts (7)

239-258: Reuse parser.extractVersions to avoid regex duplication

You already have a robust extractor in ChangelogParser. Delegate to it for consistency and fewer edge cases.

-  extractDocumentedVersions(changelog?: string): Set<string> {
-    const versions = new Set<string>();
-    if (!changelog) {
-      return versions;
-    }
-    // Match version headers: ## [1.0.0] - 2023-01-01 or ## [Unreleased]
-    const versionRegex = /^##\s*\[([^\]]+)\]/gm;
-    let match;
-    while ((match = versionRegex.exec(changelog)) !== null) {
-      if (match[1]) {
-        versions.add(match[1]);
-      }
-    }
-    logger.debug(`Found ${versions.size} documented versions`, Array.from(versions));
-    return versions;
-  }
+  async extractDocumentedVersions(changelog?: string): Promise<Set<string>> {
+    if (!changelog) return new Set<string>();
+    const parser = new (await import('./ChangelogParser.js')).default();
+    const versions = parser.extractVersions(changelog);
+    logger.debug(`Found ${versions.size} documented versions`, Array.from(versions));
+    return versions;
+  }

And update the caller:

-    const documentedVersions = this.extractDocumentedVersions(existingChangelog);
+    const documentedVersions = await this.extractDocumentedVersions(existingChangelog);

595-610: Redundant 'no-conversion' switch case removed — good; drop it from the union type too

Type still lists 'no-conversion' though default handles it.

-      | 'no-conversion',
+      // 'no-conversion' handled by default

811-845: Always-true condition; simplify insertion logic

The condition foundUnreleased || !foundUnreleased is tautological. Insert before the first version encountered (or just below Unreleased).

@@
-        // If we've passed unreleased, this is where we insert
-        if (foundUnreleased || !foundUnreleased) {
-          insertionIndex = i;
-          break;
-        }
+        // Insert before the first version (below Unreleased if present)
+        insertionIndex = i;
+        break;

650-700: Normalize trailing newline in appendToNonMagicrChangelog()

Ensure single trailing newline to match generate() behavior.

-    return result.join('\n');
+    return `${result.join('\n').trimEnd()}\n`;

332-338: Guard optional commits array

If Change.commits can be undefined, this throws. Use optional chaining.

-    if (this.options.includeCommitLinks && change.commits.length > 0) {
+    if (this.options.includeCommitLinks && change.commits?.length > 0) {

861-862: Remove leftover placeholder comment

Dead comment; drop for cleanliness.

-  // ...existing code...

434-455: Pre-release ordering nuance

compareVersions ignores pre-release tags; 1.0.0-beta will sort equal to 1.0.0. If you care about semver precedence, incorporate pre-release comparison.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc23a57 and 2c36adf.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • src/core/generator/KeepChangelogGenerator.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/core/generator/KeepChangelogGenerator.ts (2)
src/types/interfaces.ts (1)
  • ChangelogEntry (140-151)
src/core/generator/ChangelogParser.ts (2)
  • ChangelogParser (13-393)
  • isMagicrGenerated (203-206)
🔇 Additional comments (4)
src/core/generator/KeepChangelogGenerator.ts (4)

83-87: Unreleased separation looks good; keep consistency for released entries

You added a spacer after Unreleased. Do the same for released sections to avoid merged blocks.

@@
-    for (const entry of releasedEntries) {
-      content += this.formatEntry(entry);
-    }
+    for (const entry of releasedEntries) {
+      content += this.formatEntry(entry) + '\n';
+    }

103-104: Consistent single trailing newline — LGTM


472-479: Avoid repeated dynamic imports; initialize parser once

Hoist to a top‑level import or cache a single instance on the class for use across methods. Minor perf/complexity cleanup.


27-28: Append-only option is defined but never honored in generate()

Wire the flag to short‑circuit into append‑only path when an existing changelog is present.

@@
     const existingChangelog = await this.loadExistingChangelog(workingDir);
 
-    // Handle the changelog generation with proper merging logic
-    const finalEntries = await this.processEntriesWithExistingChangelog(
-      entries,
-      existingChangelog,
-      workingDir
-    );
+    // Append-only short‑circuit
+    if (this.options.appendOnly && existingChangelog) {
+      return await this.generateAppendOnly(entries, existingChangelog, workingDir);
+    }
+
+    // Handle the changelog generation with proper merging logic
+    const finalEntries = await this.processEntriesWithExistingChangelog(
+      entries,
+      existingChangelog,
+      workingDir
+    );

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
README.md (1)

115-124: Avoid showing API keys via CLI args (shell history leak).

Document a safer alternative (env var or interactive prompt) to prevent leaking secrets in shell history.

Add a caution note after the block:

+> [!CAUTION]
+> Passing API keys via CLI flags may store them in shell history. Prefer environment variables or interactive prompts:
+>
+> ```bash
+> export MAGICR_API_KEY=your-api-key
+> npx magicr --provider anthropic
+> ```
src/core/generator/KeepChangelogGenerator.ts (2)

333-351: Commit/PR/issue “links” are emitted as bare brackets, not clickable.

Inline-link them when repo URL is known; otherwise fall back to code-formatted hash/IDs.

Apply this diff:

-    if (this.options.includeCommitLinks && change.commits.length > 0) {
-      // Only include first commit to avoid clutter
-      const firstCommit = change.commits[0];
-      if (firstCommit?.hash) {
-        links.push(`[\`${firstCommit.hash.substring(0, 7)}\`]`);
-      }
-    }
+    if (this.options.includeCommitLinks && change.commits.length > 0) {
+      const firstCommit = change.commits[0];
+      const baseUrl = this.getRepositoryUrl();
+      if (firstCommit?.hash) {
+        const short = firstCommit.hash.substring(0, 7);
+        links.push(baseUrl ? `[\`${short}\`](${baseUrl}/commit/${firstCommit.hash})` : `\`${short}\``);
+      }
+    }
@@
-    if (this.options.includePRLinks && change.pr) {
-      links.push(`[#${change.pr}]`);
-    }
+    if (this.options.includePRLinks && change.pr) {
+      const baseUrl = this.getRepositoryUrl();
+      links.push(baseUrl ? `[#${change.pr}](${baseUrl}/pull/${change.pr})` : `#${change.pr}`);
+    }
@@
-    if (this.options.includeIssueLinks && change.issues && change.issues.length > 0) {
-      links.push(...change.issues.map(issue => `[#${issue}]`));
-    }
+    if (this.options.includeIssueLinks && change.issues && change.issues.length > 0) {
+      const baseUrl = this.getRepositoryUrl();
+      links.push(
+        ...change.issues.map(issue =>
+          baseUrl ? `[#${issue}](${baseUrl}/issues/${issue})` : `#${issue}`
+        )
+      );
+    }

15-16: Add missing fs imports for atomic write.

Needed by the previous change.

Apply this diff:

-import { readFileSync, writeFileSync, existsSync } from 'fs';
+import { readFileSync, writeFileSync, existsSync, renameSync, copyFileSync } from 'fs';
CHANGELOG.md (1)

12-12: Add v0.1.1-beta release section to CHANGELOG.md

CHANGELOG.md currently lacks a '0.1.1-beta' release section (rg output: "Missing v0.1.1-beta section"). Add a "## [0.1.1-beta]" entry with date and release notes or convert the existing "## [Unreleased]" block to that release before publishing.
Location: CHANGELOG.md (around line 12).

🧹 Nitpick comments (8)
README.md (2)

11-14: Consolidate duplicate Demo sections.

You added a Demo GIF at Lines 11-14 but there’s another “😎 Demo” section at Lines 69-72 claiming “Coming soon!”. Keep only one to avoid conflicting messaging.

Apply this diff to remove the second section:

@@
-## 😎 Demo
-
-> 📹 Coming soon! In the meantime, try it out in your project.
-

Also applies to: 69-72


30-31: Update the beta version in the Important notice.

README still says v0.1.0-beta, while this PR is v0.1.1-beta. Align the text.

Apply this diff:

-> This project is currently in beta (v0.1.0-beta). While fully functional, expect occasional improvements and refinements. Report any issues you encounter - your feedback helps make it better! 🙏
+> This project is currently in beta (v0.1.1-beta). While fully functional, expect occasional improvements and refinements. Report any issues you encounter - your feedback helps make it better! 🙏
CHANGELOG.md (1)

36-38: Deduplicate repeated tokens in a single entry.

Example shows “([#4], [#4])”. Collapse duplicates during generation to avoid noisy output.

src/core/generator/KeepChangelogGenerator.ts (5)

461-471: Use atomic write with backup to avoid partial writes/corruption.

Write to a temp file, optionally back up existing, then rename.

Apply this diff (and update imports per next comment):

   async writeChangelog(content: string, workingDir: string): Promise<void> {
     const filename = this.config.changelog?.filename ?? 'CHANGELOG.md';
     const changelogPath = path.join(workingDir, filename);
 
     try {
-      writeFileSync(changelogPath, content, 'utf8');
+      const tmpPath = `${changelogPath}.tmp`;
+      const bakPath = `${changelogPath}.${Date.now()}.bak`;
+      if (existsSync(changelogPath)) {
+        copyFileSync(changelogPath, bakPath);
+      }
+      writeFileSync(tmpPath, content, 'utf8');
+      renameSync(tmpPath, changelogPath);
       logger.info(`Changelog written to: ${changelogPath}`);
     } catch (error) {
       throw new ChangelogError(`Failed to write changelog: ${error}`);
     }
   }

812-836: Always-true condition; simplify insertion logic.

This block inserts at the first non-Unreleased version; the condition is redundant.

Apply this diff:

-        // If we've passed unreleased, this is where we insert
-        if (foundUnreleased || !foundUnreleased) {
-          insertionIndex = i;
-          break;
-        }
+        insertionIndex = i;
+        break;

476-479: Avoid mixed import styles for ChangelogParser.

Use the existing static import instead of dynamic imports here to reduce overhead and keep consistency.

Apply this diff:

-  static async isMagicrGenerated(content: string): Promise<boolean> {
-    const parser = new (await import('./ChangelogParser.js')).default();
+  static async isMagicrGenerated(content: string): Promise<boolean> {
+    const parser = new ChangelogParser();
     return parser.isMagicrGenerated(content);
   }
-    // Create parser instance for this method
-    const parser = new (await import('./ChangelogParser.js')).default();
+    // Reuse static import
+    const parser = new ChangelogParser();

Also applies to: 740-743


438-456: SemVer comparison ignores pre-release identifiers.

For correct ordering of pre-releases (e.g., 1.0.0-beta < 1.0.0), consider using a semver library or parsing pre-release/build metadata.


240-259: Leverage parser.extractVersions to avoid duplication.

You re-implement version extraction; reuse ChangelogParser.extractVersions for consistency and fewer regex divergences.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c36adf and b93a8c1.

⛔ Files ignored due to path filters (2)
  • .github/assets/banner.png is excluded by !**/*.png
  • .github/assets/demo.gif is excluded by !**/*.gif
📒 Files selected for processing (3)
  • CHANGELOG.md (5 hunks)
  • README.md (1 hunks)
  • src/core/generator/KeepChangelogGenerator.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/core/generator/KeepChangelogGenerator.ts (2)
src/types/interfaces.ts (1)
  • ChangelogEntry (140-151)
src/core/generator/ChangelogParser.ts (2)
  • ChangelogParser (13-393)
  • isMagicrGenerated (203-206)
🪛 markdownlint-cli2 (0.18.1)
CHANGELOG.md

40-40: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (3)
src/core/generator/KeepChangelogGenerator.ts (3)

95-97: Add a blank line between formatted released entries.

Prevents merged sections and improves readability.

Apply this diff:

-    for (const entry of releasedEntries) {
-      content += this.formatEntry(entry);
-    }
+    for (const entry of releasedEntries) {
+      content += this.formatEntry(entry) + '\n';
+    }

72-80: appendOnly flag is declared but not honored in generate().

If options.appendOnly is true and a changelog exists, branch to generateAppendOnly().

Apply this diff:

     const existingChangelog = await this.loadExistingChangelog(workingDir);
 
-    // Handle the changelog generation with proper merging logic
+    // Append-only path preserves existing content with minimal edits
+    if (this.options.appendOnly && existingChangelog) {
+      return await this.generateAppendOnly(entries, existingChangelog, workingDir);
+    }
+
+    // Handle normal generation with merging logic
     const finalEntries = await this.processEntriesWithExistingChangelog(
       entries,
       existingChangelog,
       workingDir
     );

166-184: Converted Unreleased content is preserved again, duplicating changes.

When converting Unreleased to a version (Scenario 1), the preserve loop re-adds the old Unreleased. Track conversion and skip preserving Unreleased.

Apply this diff:

-    const finalEntries: ChangelogEntry[] = [];
+    const finalEntries: ChangelogEntry[] = [];
+    let convertedUnreleased = false;
@@
-          finalEntries.push(convertedEntry);
+          finalEntries.push(convertedEntry);
+          convertedUnreleased = true;
@@
-      if (!isDuplicate) {
+      if (!isDuplicate) {
         // Only preserve if it has content
         if (this.hasChanges(existingEntry)) {
-          logger.debug(`Preserving existing entry: ${existingEntry.version}`);
-          finalEntries.push(existingEntry);
+          if (existingEntry.version === 'Unreleased' && convertedUnreleased) {
+            logger.debug('Skipping existing Unreleased after conversion');
+          } else {
+            logger.debug(`Preserving existing entry: ${existingEntry.version}`);
+            finalEntries.push(existingEntry);
+          }

Also applies to: 207-225

@warengonzaga warengonzaga merged commit d9b70ba into main Sep 21, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants