From ebc11c1fca5a85161e01b9e337983842b5f7c20f Mon Sep 17 00:00:00 2001 From: Marcus Vorwaller Date: Sat, 9 May 2026 02:32:04 -0700 Subject: [PATCH] Normalize generated commit guidance Nightshift-Task: commit-normalize Nightshift-Ref: https://github.com/marcus/nightshift --- internal/orchestrator/orchestrator.go | 20 +++++--- internal/orchestrator/orchestrator_test.go | 57 ++++++++++++++++++++++ internal/tasks/tasks.go | 2 +- internal/tasks/tasks_test.go | 19 ++++++++ website/docs/task-reference.md | 2 +- 5 files changed, 90 insertions(+), 10 deletions(-) diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 6141c95..34ebb42 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -706,6 +706,14 @@ func (o *Orchestrator) Run(ctx context.Context) error { // Prompt builders +const commitMessageInstructionTemplate = `If you create commits, use a concise subject and include these git trailers: + Nightshift-Task: %s + Nightshift-Ref: https://github.com/marcus/nightshift` + +func commitMessageInstruction(taskType tasks.TaskType) string { + return fmt.Sprintf(commitMessageInstructionTemplate, taskType) +} + // PlanPrompt returns the planning prompt for a task. func (o *Orchestrator) PlanPrompt(task *tasks.Task) string { return o.buildPlanPrompt(task) @@ -728,9 +736,7 @@ Description: %s 0. You are running autonomously. If the task is broad or ambiguous, choose a concrete, minimal scope that delivers value and state any assumptions in the description. 1. Work on a new branch and plan to submit a PR. Never work directly on the primary branch.%s 2. Before creating your branch, record the current branch name and plan to switch back after the PR is opened. -3. If you create commits, include a concise message with these git trailers: - Nightshift-Task: %s - Nightshift-Ref: https://github.com/marcus/nightshift +3. %s 4. Analyze the task requirements 5. Identify files that need to be modified 6. Create step-by-step implementation plan @@ -741,7 +747,7 @@ Description: %s "files": ["file1.go", "file2.go", ...], "description": "overall approach" } -`, task.ID, task.Title, task.Description, branchInstruction, task.Type) +`, task.ID, task.Title, task.Description, branchInstruction, commitMessageInstruction(task.Type)) } func (o *Orchestrator) buildImplementPrompt(task *tasks.Task, plan *PlanOutput, iteration int) string { @@ -771,9 +777,7 @@ Description: %s ## Instructions 0. Before creating your branch, record the current branch name. Create and work on a new branch. Never modify or commit directly to the primary branch.%s When finished, open a PR. After the PR is submitted, switch back to the original branch. If you cannot open a PR, leave the branch and explain next steps. -1. If you create commits, include a concise message with these git trailers: - Nightshift-Task: %s - Nightshift-Ref: https://github.com/marcus/nightshift +1. %s 2. Implement the plan step by step 3. Make all necessary code changes 4. Ensure tests pass @@ -783,7 +787,7 @@ Description: %s "files_modified": ["file1.go", ...], "summary": "what was done" } -`, task.ID, task.Title, task.Description, plan.Description, plan.Steps, iterationNote, branchInstruction, task.Type) +`, task.ID, task.Title, task.Description, plan.Description, plan.Steps, iterationNote, branchInstruction, commitMessageInstruction(task.Type)) } func (o *Orchestrator) buildReviewPrompt(task *tasks.Task, impl *ImplementOutput) string { diff --git a/internal/orchestrator/orchestrator_test.go b/internal/orchestrator/orchestrator_test.go index 45bff5c..8d1bc9f 100644 --- a/internal/orchestrator/orchestrator_test.go +++ b/internal/orchestrator/orchestrator_test.go @@ -468,6 +468,63 @@ func TestBuildPrompts(t *testing.T) { } } +func TestBuildPrompts_NormalizedCommitInstructionsWithBranch(t *testing.T) { + o := New() + o.SetRunMetadata(&RunMetadata{Branch: "develop"}) + + task := &tasks.Task{ + ID: "commit-normalize:/repo", + Title: "Commit Message Normalizer", + Description: "Standardize future generated commit guidance", + Type: tasks.TaskCommitNormalize, + } + plan := &PlanOutput{ + Steps: []string{"step1"}, + Description: "test plan", + } + + tests := []struct { + name string + prompt string + branchInstruction string + }{ + { + name: "plan", + prompt: o.buildPlanPrompt(task), + branchInstruction: "Create your feature branch from `develop`.", + }, + { + name: "implement", + prompt: o.buildImplementPrompt(task, plan, 1), + branchInstruction: "Checkout `develop` before creating your feature branch.", + }, + } + + expectedCommitInstruction := `If you create commits, use a concise subject and include these git trailers: + Nightshift-Task: commit-normalize + Nightshift-Ref: https://github.com/marcus/nightshift` + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := strings.Count(tt.prompt, expectedCommitInstruction); got != 1 { + t.Fatalf("normalized commit instruction count = %d, want 1\nGot:\n%s", got, tt.prompt) + } + for _, want := range []string{ + "Nightshift-Task: commit-normalize", + "Nightshift-Ref: https://github.com/marcus/nightshift", + tt.branchInstruction, + } { + if got := strings.Count(tt.prompt, want); got != 1 { + t.Errorf("%q count = %d, want 1\nGot:\n%s", want, got, tt.prompt) + } + } + if strings.Contains(tt.prompt, "include a concise message with these git trailers") { + t.Errorf("prompt contains old commit instruction wording\nGot:\n%s", tt.prompt) + } + }) + } +} + func TestExtractPRURL(t *testing.T) { tests := []struct { name string diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go index 2c7dabb..bd5d86d 100644 --- a/internal/tasks/tasks.go +++ b/internal/tasks/tasks.go @@ -332,7 +332,7 @@ Apply safe updates directly, and leave concise follow-ups for anything uncertain Type: TaskCommitNormalize, Category: CategoryPR, Name: "Commit Message Normalizer", - Description: "Standardize commit message format", + Description: "Standardize future Nightshift-generated commit guidance without rewriting history", CostTier: CostLow, RiskLevel: RiskLow, DefaultInterval: 24 * time.Hour, diff --git a/internal/tasks/tasks_test.go b/internal/tasks/tasks_test.go index 03cdf18..685d36b 100644 --- a/internal/tasks/tasks_test.go +++ b/internal/tasks/tasks_test.go @@ -104,6 +104,25 @@ func TestGetDefinition(t *testing.T) { } } +func TestCommitNormalizeDefinitionScope(t *testing.T) { + def, err := GetDefinition(TaskCommitNormalize) + if err != nil { + t.Fatalf("GetDefinition(TaskCommitNormalize) returned error: %v", err) + } + if def.Description != "Standardize future Nightshift-generated commit guidance without rewriting history" { + t.Errorf("Description = %q", def.Description) + } + if def.Category != CategoryPR { + t.Errorf("Category = %d, want %d", def.Category, CategoryPR) + } + if def.CostTier != CostLow { + t.Errorf("CostTier = %d, want %d", def.CostTier, CostLow) + } + if def.RiskLevel != RiskLow { + t.Errorf("RiskLevel = %d, want %d", def.RiskLevel, RiskLow) + } +} + func TestGetCostEstimate(t *testing.T) { // Low cost task min, max, err := GetCostEstimate(TaskLintFix) diff --git a/website/docs/task-reference.md b/website/docs/task-reference.md index cb241df..ef50a0b 100644 --- a/website/docs/task-reference.md +++ b/website/docs/task-reference.md @@ -23,7 +23,7 @@ Fully formed, review-ready artifacts. These tasks create branches and open pull | `backward-compat` | Backward-Compatibility Checks | Check and ensure backward compatibility | Medium | Low | 7d | | `build-optimize` | Build Time Optimization | Optimize build configuration for faster builds | High | Medium | 7d | | `docs-backfill` | Documentation Backfiller | Generate missing documentation | Low | Low | 7d | -| `commit-normalize` | Commit Message Normalizer | Standardize commit message format | Low | Low | 24h | +| `commit-normalize` | Commit Message Normalizer | Standardize future Nightshift-generated commit guidance without rewriting history | Low | Low | 24h | | `changelog-synth` | Changelog Synthesizer | Generate changelog from commits | Low | Low | 7d | | `release-notes` | Release Note Drafter | Draft release notes from changes | Low | Low | 7d | | `adr-draft` | ADR Drafter | Draft Architecture Decision Records | Medium | Low | 7d |