Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions internal/orchestrator/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,7 @@ func (o *Orchestrator) buildPlanPrompt(task *tasks.Task) string {
if o.runMeta != nil && o.runMeta.Branch != "" {
branchInstruction = fmt.Sprintf("\n Create your feature branch from `%s`.", o.runMeta.Branch)
}
taskGuidance := o.buildTaskSpecificPlanGuidance(task)

return fmt.Sprintf(`You are a planning agent. Create a detailed execution plan for this task.

Expand All @@ -733,15 +734,15 @@ 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:

{
"steps": ["step1", "step2", ...],
"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 (o *Orchestrator) buildImplementPrompt(task *tasks.Task, plan *PlanOutput, iteration int) string {
Expand All @@ -754,6 +755,7 @@ func (o *Orchestrator) buildImplementPrompt(task *tasks.Task, plan *PlanOutput,
if o.runMeta != nil && o.runMeta.Branch != "" {
branchInstruction = fmt.Sprintf("\n Checkout `%s` before creating your feature branch.", o.runMeta.Branch)
}
taskGuidance := o.buildTaskSpecificImplementGuidance(task)

return fmt.Sprintf(`You are an implementation agent. Execute the plan for this task.

Expand All @@ -776,14 +778,51 @@ 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)
}

// buildTaskSpecificPlanGuidance appends extra planning instructions for tasks
// whose safe default behavior needs more precision than the generic template.
func (o *Orchestrator) buildTaskSpecificPlanGuidance(task *tasks.Task) string {
switch task.Type {
case tasks.TaskCommitNormalize:
return `

## Task-Specific Guidance
- Treat commit-normalize as a low-risk, prospective cleanup for new work and lightweight repo guidance.
- Inspect recent commits first and prefer the repository's existing commit style when it is clear.
- If the repository has no consistent style, use a concise conventional format for new commit messages.
- Preserve required trailers and any project-specific footer lines.
- Keep subjects concise and avoid plans that rewrite published or shared history.`
default:
return ""
}
}

// buildTaskSpecificImplementGuidance keeps built-in low-risk tasks scoped to
// safe actions. commit-normalize should standardize future work, not rewrite
// existing shared history.
func (o *Orchestrator) buildTaskSpecificImplementGuidance(task *tasks.Task) string {
switch task.Type {
case tasks.TaskCommitNormalize:
return `

## Task-Specific Guidance
- Prefer the repository's established commit message style when present; otherwise use a clear conventional format.
- Preserve required trailers and any project-specific footer lines on new commits you create.
- Keep commit messages concise.
- Standardize prospectively via new commits and lightweight repo guidance; do not rewrite published history.
- Do not rebase, force-push, or otherwise rewrite shared branch history.`
default:
return ""
}
}

func (o *Orchestrator) buildReviewPrompt(task *tasks.Task, impl *ImplementOutput) string {
Expand Down
74 changes: 74 additions & 0 deletions internal/orchestrator/orchestrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,80 @@ func TestBuildPrompts(t *testing.T) {
}
}

func TestBuildPlanPrompt_CommitNormalizeIncludesTaskSpecificGuidance(t *testing.T) {
o := New()
task := &tasks.Task{
ID: "commit-normalize:/repo",
Title: "Commit Message Normalizer",
Description: "Standardize commit message format safely",
Type: tasks.TaskCommitNormalize,
}

prompt := o.buildPlanPrompt(task)
for _, want := range []string{
"Treat commit-normalize as a low-risk, prospective cleanup",
"prefer the repository's existing commit style",
"use a concise conventional format",
"Preserve required trailers",
"avoid plans that rewrite published or shared history",
} {
if !strings.Contains(prompt, want) {
t.Errorf("plan prompt missing %q\nGot:\n%s", want, prompt)
}
}
}

func TestBuildImplementPrompt_CommitNormalizeIncludesTaskSpecificGuidance(t *testing.T) {
o := New()
task := &tasks.Task{
ID: "commit-normalize:/repo",
Title: "Commit Message Normalizer",
Description: "Standardize commit message format safely",
Type: tasks.TaskCommitNormalize,
}
plan := &PlanOutput{
Steps: []string{"Inspect recent commits", "Update prompt guidance"},
Description: "Keep the task prospective and non-destructive.",
}

prompt := o.buildImplementPrompt(task, plan, 1)
for _, want := range []string{
"Prefer the repository's established commit message style when present",
"otherwise use a clear conventional format",
"Preserve required trailers",
"Keep commit messages concise",
"do not rewrite published history",
"Do not rebase, force-push, or otherwise rewrite shared branch history.",
} {
if !strings.Contains(prompt, want) {
t.Errorf("implement prompt missing %q\nGot:\n%s", want, prompt)
}
}
}

func TestBuildPrompts_GenericTaskDoesNotIncludeCommitNormalizeGuidance(t *testing.T) {
o := New()
task := &tasks.Task{
ID: "lint-fix:/repo",
Title: "Lint Fixes",
Description: "Automatically fix linting errors and style issues",
Type: tasks.TaskLintFix,
}
plan := &PlanOutput{
Steps: []string{"Run linter"},
Description: "Generic prompt path.",
}

planPrompt := o.buildPlanPrompt(task)
implPrompt := o.buildImplementPrompt(task, plan, 1)

for _, got := range []string{planPrompt, implPrompt} {
if strings.Contains(got, "Do not rebase, force-push, or otherwise rewrite shared branch history.") {
t.Errorf("generic task prompt unexpectedly included commit-normalize guidance\nGot:\n%s", got)
}
}
}

func TestExtractPRURL(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion internal/tasks/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 commit message conventions for new work without rewriting shared history",
CostTier: CostLow,
RiskLevel: RiskLow,
DefaultInterval: 24 * time.Hour,
Expand Down
23 changes: 23 additions & 0 deletions internal/tasks/tasks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,26 @@ func TestSpecificDefaultIntervalOverrides(t *testing.T) {
}
}
}

func TestCommitNormalizeDefinition(t *testing.T) {
def, err := GetDefinition(TaskCommitNormalize)
if err != nil {
t.Fatalf("GetDefinition(%q) error: %v", TaskCommitNormalize, err)
}

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)
}
if def.DefaultInterval != 24*time.Hour {
t.Errorf("DefaultInterval = %v, want %v", def.DefaultInterval, 24*time.Hour)
}
if def.Description != "Standardize commit message conventions for new work without rewriting shared history" {
t.Errorf("Description = %q", def.Description)
}
}
2 changes: 1 addition & 1 deletion website/docs/task-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 commit message conventions for new work without rewriting shared 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 |
Expand Down
Loading