The merge commit message was hand-rolled per caller ("Merge task: <title>",
"Merge <branch>", "Merge subtask") and ignored the task's commit type. Every
caller now passes a blank message and TaskMergeService fills in
CommitMessageBuilder.BuildMerge -> {commitType}(list-slug): merge <title> plus the
ClaudeDo-Task trailer; the merge modal prefills it from GetMergeTargets.
A merge whose list has a verify command holds the MergeTask call for minutes (5m46s
on this repo), during which the modal only disabled its button - no spinner, no
message, so a landed merge looked like a dead app. TaskMergeService now broadcasts
MergeProgress(taskId, phase, elapsedSeconds) for the merging and verifying phases
(re-reported every 30s) plus a WorkerLog line when verify starts; the modal shows a
spinner and the localized phase.
65 lines
2.4 KiB
C#
65 lines
2.4 KiB
C#
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using ClaudeDo.Data.Models;
|
|
|
|
namespace ClaudeDo.Worker.Runner;
|
|
|
|
public static class CommitMessageBuilder
|
|
{
|
|
public static string Build(string commitType, string listName, string taskTitle, string? taskDescription, string taskId)
|
|
{
|
|
var slug = ToSlug(listName);
|
|
var title = Truncate(taskTitle, 60);
|
|
var header = $"{commitType}({slug}): {title}";
|
|
|
|
var sb = new StringBuilder();
|
|
sb.Append(header);
|
|
|
|
var hasDescription = !string.IsNullOrWhiteSpace(taskDescription);
|
|
if (hasDescription)
|
|
{
|
|
sb.Append("\n\n");
|
|
sb.Append(Truncate(taskDescription!.Trim(), 400));
|
|
}
|
|
|
|
// Trailer is always included.
|
|
sb.Append("\n\n");
|
|
sb.Append($"ClaudeDo-Task: {taskId}");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Message for the merge commit that lands a task branch. Same Conventional-Commits header
|
|
/// shape as the task's own commits (<see cref="Build"/>) with an explicit `merge ` verb, so
|
|
/// merge commits stay parseable *and* greppable, plus the task-id trailer. A blank commit
|
|
/// type falls back to <see cref="CommitTypeRegistry.DefaultType"/>; a list name that slugs
|
|
/// to nothing drops the scope rather than emitting an empty `()`.
|
|
/// </summary>
|
|
public static string BuildMerge(string commitType, string listName, string taskTitle, string taskId)
|
|
{
|
|
var type = string.IsNullOrWhiteSpace(commitType) ? CommitTypeRegistry.DefaultType : commitType.Trim();
|
|
var slug = ToSlug(listName);
|
|
var scope = slug.Length == 0 ? "" : $"({slug})";
|
|
var title = Truncate(taskTitle.Trim(), 60);
|
|
|
|
return $"{type}{scope}: merge {title}\n\nClaudeDo-Task: {taskId}";
|
|
}
|
|
|
|
public static string ToSlug(string name)
|
|
{
|
|
var lower = name.ToLowerInvariant();
|
|
// Replace whitespace runs with a single dash.
|
|
var dashed = Regex.Replace(lower, @"\s+", "-");
|
|
// Remove all non-alphanumeric-and-dash characters.
|
|
var cleaned = Regex.Replace(dashed, @"[^a-z0-9\-]", "");
|
|
// Collapse multiple dashes.
|
|
var collapsed = Regex.Replace(cleaned, @"-{2,}", "-");
|
|
// Trim leading/trailing dashes.
|
|
return collapsed.Trim('-');
|
|
}
|
|
|
|
private static string Truncate(string value, int maxLength) =>
|
|
value.Length <= maxLength ? value : value[..maxLength];
|
|
}
|