From c7b3b0a6b431d1d2b155f63d340eb9a71a98dd41 Mon Sep 17 00:00:00 2001 From: Marcus Vorwaller Date: Sun, 19 Apr 2026 02:44:26 -0700 Subject: [PATCH] feat: add commit-normalize prompt guidance Nightshift-Task: commit-normalize Nightshift-Ref: https://github.com/marcus/nightshift --- internal/orchestrator/orchestrator.go | 30 +++++++++-- internal/orchestrator/orchestrator_test.go | 61 ++++++++++++++++++++++ 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 6141c95..97b63a6 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -711,12 +711,23 @@ func (o *Orchestrator) PlanPrompt(task *tasks.Task) string { return o.buildPlanPrompt(task) } +func taskSpecificPlanGuidance(task *tasks.Task) string { + switch task.Type { + case tasks.TaskCommitNormalize: + return "\n7. For commit normalization tasks, inspect recent commit subjects plus any release or changelog expectations, infer the local convention if none is documented, preserve Nightshift or other required trailers, and keep the scope to the active work branch or PR without rewriting unrelated history or installing git hooks." + default: + return "" + } +} + func (o *Orchestrator) buildPlanPrompt(task *tasks.Task) string { branchInstruction := "" if o.runMeta != nil && o.runMeta.Branch != "" { branchInstruction = fmt.Sprintf("\n Create your feature branch from `%s`.", o.runMeta.Branch) } + taskGuidance := taskSpecificPlanGuidance(task) + return fmt.Sprintf(`You are a planning agent. Create a detailed execution plan for this task. ## Task @@ -733,7 +744,7 @@ Description: %s Nightshift-Ref: https://github.com/marcus/nightshift 4. Analyze the task requirements 5. Identify files that need to be modified -6. Create step-by-step implementation plan +6. Create step-by-step implementation plan%s 7. Output only valid JSON (no markdown, no extra text). The output is read by a machine. Use this schema: { @@ -741,7 +752,16 @@ 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, task.Type, taskGuidance) +} + +func taskSpecificImplementGuidance(task *tasks.Task) string { + switch task.Type { + case tasks.TaskCommitNormalize: + return "\n5. For commit normalization tasks, inspect recent commit subjects plus any release or changelog expectations, infer the local convention if none is documented, normalize only the relevant branch or PR commit messages, preserve Nightshift or other required trailers, avoid destructive rewrites beyond the task scope, and report assumptions if the target commit range is ambiguous." + default: + return "" + } } func (o *Orchestrator) buildImplementPrompt(task *tasks.Task, plan *PlanOutput, iteration int) string { @@ -755,6 +775,8 @@ func (o *Orchestrator) buildImplementPrompt(task *tasks.Task, plan *PlanOutput, branchInstruction = fmt.Sprintf("\n Checkout `%s` before creating your feature branch.", o.runMeta.Branch) } + taskGuidance := taskSpecificImplementGuidance(task) + return fmt.Sprintf(`You are an implementation agent. Execute the plan for this task. ## Task @@ -776,14 +798,14 @@ Description: %s Nightshift-Ref: https://github.com/marcus/nightshift 2. Implement the plan step by step 3. Make all necessary code changes -4. Ensure tests pass +4. Ensure tests pass%s 5. Output a summary as JSON: { "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, task.Type, taskGuidance) } 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..0488b5d 100644 --- a/internal/orchestrator/orchestrator_test.go +++ b/internal/orchestrator/orchestrator_test.go @@ -809,6 +809,67 @@ func TestBuildImplementPrompt_WithoutBranch(t *testing.T) { } } +func TestBuildPlanPrompt_CommitNormalizeIncludesRepoAwareGuidance(t *testing.T) { + o := New() + + task := &tasks.Task{ + ID: "commit-normalize:/tmp/nightshift", + Title: "Commit Message Normalizer", + Description: "Standardize commit message format", + Type: tasks.TaskCommitNormalize, + } + + prompt := o.buildPlanPrompt(task) + + expected := []string{ + "recent commit subjects", + "release or changelog expectations", + "infer the local convention if none is documented", + "preserve Nightshift or other required trailers", + "active work branch or PR", + "without rewriting unrelated history or installing git hooks", + } + + for _, want := range expected { + if !strings.Contains(prompt, want) { + t.Errorf("plan prompt missing %q\nGot:\n%s", want, prompt) + } + } +} + +func TestBuildImplementPrompt_CommitNormalizeIncludesRepoAwareGuidance(t *testing.T) { + o := New() + + task := &tasks.Task{ + ID: "commit-normalize:/tmp/nightshift", + Title: "Commit Message Normalizer", + Description: "Standardize commit message format", + Type: tasks.TaskCommitNormalize, + } + plan := &PlanOutput{ + Steps: []string{"Inspect recent commits", "Normalize active branch commits"}, + Description: "Use the repo's recent commits to normalize the branch message style.", + } + + prompt := o.buildImplementPrompt(task, plan, 1) + + expected := []string{ + "recent commit subjects", + "release or changelog expectations", + "infer the local convention if none is documented", + "normalize only the relevant branch or PR commit messages", + "preserve Nightshift or other required trailers", + "avoid destructive rewrites beyond the task scope", + "report assumptions if the target commit range is ambiguous", + } + + for _, want := range expected { + if !strings.Contains(prompt, want) { + t.Errorf("implement prompt missing %q\nGot:\n%s", want, prompt) + } + } +} + func TestBuildMetadataBlock_WithBranch(t *testing.T) { o := New() o.SetRunMetadata(&RunMetadata{