feat(worker): render task descriptions into the list-handler brief

Phase 0 forced a batch_get_tasks full-fetch across every task just to see
descriptions, which blew past the client's token limit on larger lists.
brief.md lives on disk and has no such limit, so descriptions now render
there directly (fenced with an extended backtick run, indented under the
list bullet, so embedded headings/lists/code fences can't break the task
list's own structure). Phase 0 now treats the brief as the primary source
and only falls back to batch_get_tasks for fields it doesn't carry.
This commit is contained in:
mika kuns
2026-08-05 10:48:37 +02:00
parent 334cf1e1d2
commit b38b0857dd
4 changed files with 122 additions and 4 deletions
@@ -206,7 +206,7 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
{
var task = await taskRepo.GetByIdAsync(id, ct)
?? throw new KeyNotFoundException($"Task not found: {id}");
briefLines.Add($"- [{task.Status}] {task.Title} (id: {task.Id})");
briefLines.Add(RenderBriefEntry(task));
}
var sessionDir = Path.Combine(Paths.AppDataRoot(), "merge-helper-sessions", Guid.NewGuid().ToString());
@@ -253,6 +253,39 @@ public sealed class InteractiveLaunchSpecService : IInteractiveLaunchSpecService
return new LaunchSpec(repoDir, resolvedClaude, args, env);
}
// Renders one task as a brief list item. A description can itself be arbitrary Markdown
// (headings, lists, fenced code) — those must not merge into the brief's own task list, so
// the description is placed in a fenced code block indented to the list item's continuation
// column (2 spaces, matching "- "). That keeps CommonMark parsing the fence as part of THIS
// bullet rather than breaking the list, while the code fence itself stops any inner heading
// or list syntax from being interpreted. The fence length is extended past the longest run of
// backticks already present in the description so an embedded ``` block can't prematurely
// close it.
private static string RenderBriefEntry(TaskEntity task)
{
var header = $"- [{task.Status}] {task.Title} (id: {task.Id})";
var description = task.Description?.Trim();
if (string.IsNullOrEmpty(description)) return header;
var fence = new string('`', Math.Max(3, LongestBacktickRun(description) + 1));
var lines = new List<string>(4) { header, $" {fence}" };
lines.AddRange(description.Replace("\r\n", "\n").Split('\n').Select(line => $" {line}"));
lines.Add($" {fence}");
return string.Join("\n", lines);
}
private static int LongestBacktickRun(string text)
{
var max = 0;
var current = 0;
foreach (var ch in text)
{
current = ch == '`' ? current + 1 : 0;
if (current > max) max = current;
}
return max;
}
// Creates the ClaudeDo task that hosts a list-handler run (Mission Control's "Let Claude
// handle it") and stamps the list repo's current HEAD as the review range's base commit.
// The handler never gets its own worktree -- it commits straight to the list's working