From b1be26399035175e3dda0ba487f1e79ab1415188 Mon Sep 17 00:00:00 2001 From: Marcus Vorwaller Date: Tue, 14 Apr 2026 03:02:05 -0700 Subject: [PATCH] feat: enrich release-notes task prompt Nightshift-Task: release-notes Nightshift-Ref: https://github.com/marcus/nightshift --- internal/orchestrator/orchestrator_test.go | 29 ++++++++++++++++++++++ internal/tasks/tasks.go | 14 ++++++++--- internal/tasks/tasks_test.go | 23 +++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/internal/orchestrator/orchestrator_test.go b/internal/orchestrator/orchestrator_test.go index 45bff5c..03a02db 100644 --- a/internal/orchestrator/orchestrator_test.go +++ b/internal/orchestrator/orchestrator_test.go @@ -468,6 +468,35 @@ func TestBuildPrompts(t *testing.T) { } } +func TestBuildPlanPrompt_ReleaseNotesIncludesDetailedInstructions(t *testing.T) { + o := New() + + def, err := tasks.GetDefinition(tasks.TaskReleaseNotes) + if err != nil { + t.Fatalf("GetDefinition(TaskReleaseNotes) error: %v", err) + } + + task := &tasks.Task{ + ID: "release-notes:/repo", + Title: def.Name, + Description: def.Description, + Type: tasks.TaskReleaseNotes, + } + + prompt := o.buildPlanPrompt(task) + + for _, want := range []string{ + "Inspect the latest tag and CHANGELOG.md first", + "Group the draft into clear user-facing sections", + "Call out breaking changes, migrations, config updates", + "state your assumptions", + } { + if !strings.Contains(prompt, want) { + t.Errorf("plan prompt missing %q\nGot:\n%s", want, prompt) + } + } +} + func TestExtractPRURL(t *testing.T) { tests := []struct { name string diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go index 2c7dabb..0ce56cb 100644 --- a/internal/tasks/tasks.go +++ b/internal/tasks/tasks.go @@ -347,10 +347,16 @@ Apply safe updates directly, and leave concise follow-ups for anything uncertain DefaultInterval: 168 * time.Hour, }, TaskReleaseNotes: { - Type: TaskReleaseNotes, - Category: CategoryPR, - Name: "Release Note Drafter", - Description: "Draft release notes from changes", + Type: TaskReleaseNotes, + Category: CategoryPR, + Name: "Release Note Drafter", + Description: `Draft Nightshift release notes from repository changes. +Inspect the latest tag and CHANGELOG.md first to understand the last published release and the existing release-note tone. +Summarize the notable user-facing changes since that release, using commits and merged PRs as supporting evidence. +Group the draft into clear user-facing sections (for example: Features, Fixes, Improvements, Breaking Changes) based on what changed. +Call out breaking changes, migrations, config updates, and operator follow-up explicitly so upgrade impact is easy to scan. +Preserve the concise tone and heading style already used in CHANGELOG.md, and skip low-signal internal churn unless it matters to users. +If the release scope is unclear, state your assumptions, note missing evidence, and include follow-up questions or validation steps before publishing.`, CostTier: CostLow, RiskLevel: RiskLow, DefaultInterval: 168 * time.Hour, diff --git a/internal/tasks/tasks_test.go b/internal/tasks/tasks_test.go index 03cdf18..d609615 100644 --- a/internal/tasks/tasks_test.go +++ b/internal/tasks/tasks_test.go @@ -1,6 +1,7 @@ package tasks import ( + "strings" "testing" "time" ) @@ -280,6 +281,28 @@ func TestRegistryCompleteness(t *testing.T) { } } +func TestReleaseNotesDefinitionIncludesDraftingGuidance(t *testing.T) { + def, err := GetDefinition(TaskReleaseNotes) + if err != nil { + t.Fatalf("GetDefinition(TaskReleaseNotes) error: %v", err) + } + + if def.Name != "Release Note Drafter" { + t.Fatalf("TaskReleaseNotes name = %q, want %q", def.Name, "Release Note Drafter") + } + + for _, want := range []string{ + "Inspect the latest tag and CHANGELOG.md first", + "Group the draft into clear user-facing sections", + "Call out breaking changes, migrations, config updates", + "state your assumptions", + } { + if !strings.Contains(def.Description, want) { + t.Errorf("TaskReleaseNotes description missing %q", want) + } + } +} + func TestAllDefinitionsHaveDefaultInterval(t *testing.T) { for _, def := range AllDefinitions() { if def.DefaultInterval == 0 {