feat(worker): compose task prompt from title + description + open steps only
Resolved sub-tasks are no longer appended to the prompt. Extracted into a shared TaskPromptComposer so the UI's description preview can render the same 'what Claude gets' text. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
29
src/ClaudeDo.Data/TaskPromptComposer.cs
Normal file
29
src/ClaudeDo.Data/TaskPromptComposer.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Single source of truth for the text handed to Claude as a task prompt:
|
||||||
|
/// title + description + the OPEN sub-tasks. Resolved sub-tasks are dropped.
|
||||||
|
/// Shared by the Worker (real prompt) and the UI (the card's "what Claude gets" preview).
|
||||||
|
/// </summary>
|
||||||
|
public static class TaskPromptComposer
|
||||||
|
{
|
||||||
|
public static string Compose(string title, string? description, IEnumerable<(string Title, bool Completed)> subtasks)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder((title ?? "").Trim());
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(description))
|
||||||
|
sb.Append("\n\n").Append(description.Trim());
|
||||||
|
|
||||||
|
var open = subtasks?.Where(s => !s.Completed).ToList() ?? new List<(string, bool)>();
|
||||||
|
if (open.Count > 0)
|
||||||
|
{
|
||||||
|
sb.Append("\n\n## Sub-Tasks\n");
|
||||||
|
foreach (var s in open)
|
||||||
|
sb.Append("- [ ] ").Append(s.Title).Append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,16 +101,10 @@ public sealed class TaskRunner
|
|||||||
await _state.StartRunningAsync(task.Id, now, ct);
|
await _state.StartRunningAsync(task.Id, now, ct);
|
||||||
await _broadcaster.TaskStarted(slot, task.Id, now);
|
await _broadcaster.TaskStarted(slot, task.Id, now);
|
||||||
|
|
||||||
// Build prompt.
|
// Build prompt: title + description + only the OPEN sub-tasks (resolved ones are dropped).
|
||||||
var sb = new System.Text.StringBuilder(task.Title);
|
var prompt = TaskPromptComposer.Compose(
|
||||||
if (!string.IsNullOrWhiteSpace(task.Description)) sb.Append("\n\n").Append(task.Description.Trim());
|
task.Title, task.Description,
|
||||||
if (subtasks.Count > 0)
|
subtasks.Select(s => (s.Title, s.Completed)));
|
||||||
{
|
|
||||||
sb.Append("\n\n## Sub-Tasks\n");
|
|
||||||
foreach (var s in subtasks)
|
|
||||||
sb.Append(s.Completed ? "- [x] " : "- [ ] ").Append(s.Title).Append('\n');
|
|
||||||
}
|
|
||||||
var prompt = sb.ToString();
|
|
||||||
|
|
||||||
// Run 1.
|
// Run 1.
|
||||||
var result = await RunOnceAsync(task.Id, task.Title, slot, runDir, resolvedConfig, 1, false, prompt, ct);
|
var result = await RunOnceAsync(task.Id, task.Title, slot, runDir, resolvedConfig, 1, false, prompt, ct);
|
||||||
|
|||||||
45
tests/ClaudeDo.Data.Tests/TaskPromptComposerTests.cs
Normal file
45
tests/ClaudeDo.Data.Tests/TaskPromptComposerTests.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using ClaudeDo.Data;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Data.Tests;
|
||||||
|
|
||||||
|
public class TaskPromptComposerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Composes_title_description_and_open_steps()
|
||||||
|
{
|
||||||
|
var result = TaskPromptComposer.Compose(
|
||||||
|
"Refactor diff viewer",
|
||||||
|
"Share the row template.",
|
||||||
|
new (string, bool)[] { ("Done step", true), ("Open step", false) });
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
"Refactor diff viewer\n\nShare the row template.\n\n## Sub-Tasks\n- [ ] Open step\n",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Drops_resolved_steps_and_omits_section_when_none_open()
|
||||||
|
{
|
||||||
|
var result = TaskPromptComposer.Compose(
|
||||||
|
"Title",
|
||||||
|
"Desc",
|
||||||
|
new (string, bool)[] { ("a", true), ("b", true) });
|
||||||
|
|
||||||
|
Assert.Equal("Title\n\nDesc", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Omits_description_when_blank()
|
||||||
|
{
|
||||||
|
var result = TaskPromptComposer.Compose("Title", " ", new (string, bool)[] { ("open", false) });
|
||||||
|
|
||||||
|
Assert.Equal("Title\n\n## Sub-Tasks\n- [ ] open\n", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Title_only_when_no_description_or_steps()
|
||||||
|
{
|
||||||
|
Assert.Equal("Just a title", TaskPromptComposer.Compose("Just a title", null, System.Array.Empty<(string, bool)>()));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user