Update the shorthand from "v" to "V" in the changelog command and adjust the corresponding test assertion.
144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/spf13/cobra"
|
|
"gmgauthier.com/grokkit/config"
|
|
"gmgauthier.com/grokkit/internal/git"
|
|
)
|
|
|
|
var changelogCmd = &cobra.Command{
|
|
Use: "changelog",
|
|
Short: "Generate CHANGELOG.md section from git history for Gitea releases",
|
|
Long: `AI-generated changelog using only Added/Changed/Fixed. Designed so the output can be pasted directly into Gitea release notes.`,
|
|
Run: runChangelog,
|
|
}
|
|
|
|
func init() {
|
|
changelogCmd.Flags().String("since", "", "Start from this tag/ref (default: previous tag)")
|
|
changelogCmd.Flags().StringP("version", "V", "", "Override version for header (default: latest git tag)")
|
|
changelogCmd.Flags().Bool("stdout", false, "Print ONLY the new section (ideal for Gitea release notes)")
|
|
changelogCmd.Flags().Bool("commit", false, "After writing, remind to run grokkit commit")
|
|
rootCmd.AddCommand(changelogCmd)
|
|
}
|
|
|
|
func runChangelog(cmd *cobra.Command, args []string) {
|
|
stdout, _ := cmd.Flags().GetBool("stdout")
|
|
doCommit, _ := cmd.Flags().GetBool("commit")
|
|
version, _ := cmd.Flags().GetString("version")
|
|
since, _ := cmd.Flags().GetString("since")
|
|
|
|
if !git.IsRepo() {
|
|
color.Red("Not inside a git repository")
|
|
return
|
|
}
|
|
|
|
// Version from tag you set (or override)
|
|
if version == "" {
|
|
var err error
|
|
version, err = git.LatestTag()
|
|
if err != nil {
|
|
color.Red("No git tags found. Create one first: git tag vX.Y.Z")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Since ref (defaults to previous tag)
|
|
if since == "" {
|
|
if prev, err := git.PreviousTag(version); err == nil {
|
|
since = prev
|
|
} else {
|
|
since = "HEAD~100" // safe first-release fallback
|
|
}
|
|
}
|
|
|
|
logOutput, err := git.LogSince(since)
|
|
if err != nil || strings.TrimSpace(logOutput) == "" {
|
|
color.Yellow("No new commits since last tag.")
|
|
return
|
|
}
|
|
|
|
// Grok generation (strong prompt for your exact format)
|
|
client := newGrokClient()
|
|
messages := buildChangelogMessages(logOutput, version)
|
|
model := config.GetModel("changelog", "")
|
|
|
|
color.Yellow("Asking Grok to categorize changes...")
|
|
section := client.Stream(messages, model)
|
|
|
|
date := time.Now().Format("2006-01-02")
|
|
newSection := fmt.Sprintf("## [%s] - %s\n\n%s\n", version, date, section)
|
|
|
|
if stdout {
|
|
fmt.Print(newSection)
|
|
return
|
|
}
|
|
|
|
// Build full file (prepend or create)
|
|
content := buildFullChangelog(newSection)
|
|
|
|
// Preview + safety confirm (exactly like commit/review)
|
|
color.Cyan("\n--- Proposed CHANGELOG.md update ---\n%s\n--------------------------------", content)
|
|
var confirm string
|
|
color.Yellow("Write this to CHANGELOG.md? (y/n): ")
|
|
_, _ = fmt.Scanln(&confirm) // we don't care about scan errors here
|
|
|
|
if confirm != "y" && confirm != "Y" {
|
|
color.Yellow("Aborted.")
|
|
return
|
|
}
|
|
|
|
if err := os.WriteFile("CHANGELOG.md", []byte(content), 0644); err != nil {
|
|
color.Red("Failed to write CHANGELOG.md")
|
|
return
|
|
}
|
|
|
|
color.Green("✅ CHANGELOG.md updated with version %s", version)
|
|
|
|
if doCommit {
|
|
color.Yellow("Run `grokkit commit` (or `git add CHANGELOG.md && git commit`) to stage it.")
|
|
}
|
|
}
|
|
|
|
func buildChangelogMessages(log, version string) []map[string]string {
|
|
return []map[string]string{
|
|
{
|
|
"role": "system",
|
|
"content": `You are an expert technical writer.
|
|
Generate a changelog section using **only** these headings (include only if content exists):
|
|
### Added
|
|
### Changed
|
|
### Fixed
|
|
|
|
Rules:
|
|
- Start directly with the section headings (no extra header — we add it).
|
|
- One optional short summary sentence at the very top (light humour OK here only).
|
|
- Every bullet must be factual, imperative, one clear action per line.
|
|
- Be concise. No marketing language or explanations.
|
|
Output ONLY clean markdown.`,
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": fmt.Sprintf("Version: %s\n\nCommit history:\n%s", version, log),
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildFullChangelog(newSection string) string {
|
|
existing, err := os.ReadFile("CHANGELOG.md")
|
|
if err != nil {
|
|
// File doesn't exist (or unreadable) → create a new changelog with a header
|
|
return `# Changelog
|
|
|
|
All notable changes to this project will be documented in this file.
|
|
|
|
` + newSection
|
|
}
|
|
return newSection + string(existing)
|
|
}
|