feat(data): add merge-helper prompt templates

This commit is contained in:
mika kuns
2026-07-24 14:21:26 +02:00
parent 7517f2a9b3
commit c7d64e9c9b
2 changed files with 88 additions and 1 deletions
+51 -1
View File
@@ -2,7 +2,7 @@ using System.Text;
namespace ClaudeDo.Data; namespace ClaudeDo.Data;
public enum PromptKind { System, Planning, PlanningInitial, Retry, DailyPrep, WeeklyReport, ImprovementChild, Refine } public enum PromptKind { System, Planning, PlanningInitial, Retry, DailyPrep, WeeklyReport, ImprovementChild, Refine, MergeHelper, MergeHelperInitial }
public static class PromptFiles public static class PromptFiles
{ {
@@ -18,6 +18,8 @@ public static class PromptFiles
PromptKind.WeeklyReport => Path.Combine(Root, "weekly-report.md"), PromptKind.WeeklyReport => Path.Combine(Root, "weekly-report.md"),
PromptKind.ImprovementChild => Path.Combine(Root, "improvement-child.md"), PromptKind.ImprovementChild => Path.Combine(Root, "improvement-child.md"),
PromptKind.Refine => Path.Combine(Root, "refine.md"), PromptKind.Refine => Path.Combine(Root, "refine.md"),
PromptKind.MergeHelper => Path.Combine(Root, "merge-helper-system.md"),
PromptKind.MergeHelperInitial => Path.Combine(Root, "merge-helper-initial.md"),
_ => throw new ArgumentOutOfRangeException(nameof(kind)) _ => throw new ArgumentOutOfRangeException(nameof(kind))
}; };
@@ -63,6 +65,8 @@ public static class PromptFiles
PromptKind.WeeklyReport => WeeklyReportDefault, PromptKind.WeeklyReport => WeeklyReportDefault,
PromptKind.ImprovementChild => ImprovementChildDefault, PromptKind.ImprovementChild => ImprovementChildDefault,
PromptKind.Refine => RefineDefault, PromptKind.Refine => RefineDefault,
PromptKind.MergeHelper => MergeHelperDefault,
PromptKind.MergeHelperInitial => MergeHelperInitialDefault,
_ => "" _ => ""
}; };
@@ -224,6 +228,52 @@ public static class PromptFiles
task, stop. task, stop.
"""; """;
private const string MergeHelperDefault = """
You are the ClaudeDo Merge Helper, running as an interactive session with the user watching. Ask them questions whenever you are unsure — that is the point of this session.
Your job: take the tasks listed in the brief and drive each one to a merged, Done state, then print a summary of everything that changed. You act through the mcp__claudedo__* tools. Read the brief file first (the kickoff message gives its path); it lists each task's id, title, status, list and repo.
Handle the tasks in the order listed, one at a time. For each task, act on its current status:
- Idle or Queued: run it with run_task_now, then poll get_task until it leaves Running (it lands in WaitingForReview on success, or Failed).
- Running or WaitingForChildren: poll get_task until it surfaces for review.
- Failed: this usually needs human judgement — ask the user whether to reset_failed_task and re-run, or skip it.
- WaitingForReview: review, then merge it (below).
Reviewing and merging a WaitingForReview task:
1. Inspect the change with get_task_diff (stat first, then the full diff if it is non-trivial) and sanity-check it against the task's title/description.
2. If the change looks wrong, incomplete, or risky, STOP and ask the user before merging — offer reject_rerun (with feedback) or skip.
3. Otherwise merge it with review_task(taskId, decision="approve", leaveConflictsInTree=true).
- Clean merge → the task is Done; move on.
- Conflict (markers left in the working tree, repoPath returned) → resolve it (below).
Resolving a conflict:
- Open each conflicted file under repoPath (Read/Edit) and resolve the <<<<<<< ======= >>>>>>> markers, guided by BOTH sides' intent. Then call continue_merge(taskId). If markers remain it tells you — fix and call again. Use abort_merge(taskId) to cancel a merge you cannot safely resolve.
- For a task WITH children (a unit merge), pass the PARENT task id to continue_merge / abort_merge.
- If a resolution is non-obvious, ambiguous, or might drop someone's work, ASK THE USER before continuing.
- Prefer the MCP tools whenever they apply. Only if the MCP tools cannot reach an in-progress merge may you finish it by hand: resolve the markers, then `git add -- <the resolved paths>` and `git commit` — NEVER `git add -A` or a bare commit, because the checkout is shared with other sessions.
Rules:
- Never use raw `git merge`, `git reset`, or `git checkout` to force a merge. Drive merges through the MCP tools; hand-resolution is only for markers the tools left and cannot finish.
- Ask the user for anything ambiguous, risky, or destructive.
When every task is handled, print a SUMMARY:
- one line per task: title — final status — merge commit (if any) — conflicts resolved (if any)
- anything you skipped or left for the user, and why
- suggested follow-ups, if any.
""";
private const string MergeHelperInitialDefault = """
# Merge Helper brief
Scope: {scope}
Handle the following tasks, in order. For each, drive it to a merged/Done state per your instructions, asking me when unsure.
{tasks}
When every task is handled, print the summary.
""";
private const string WeeklyReportDefault = """ private const string WeeklyReportDefault = """
You are generating a concise weekly standup report for a software developer, You are generating a concise weekly standup report for a software developer,
covering {start} to {end}. covering {start} to {end}.
@@ -43,4 +43,41 @@ public class PromptFilesTests
{ {
Assert.EndsWith("planning-system.md", PromptFiles.PathFor(PromptKind.Planning)); Assert.EndsWith("planning-system.md", PromptFiles.PathFor(PromptKind.Planning));
} }
[Fact]
public void PathFor_merge_helper_kinds_map_to_their_files()
{
Assert.EndsWith("merge-helper-system.md", PromptFiles.PathFor(PromptKind.MergeHelper));
Assert.EndsWith("merge-helper-initial.md", PromptFiles.PathFor(PromptKind.MergeHelperInitial));
}
[Fact]
public void DefaultFor_merge_helper_is_non_empty_and_mentions_the_merge_tools()
{
var d = PromptFiles.DefaultFor(PromptKind.MergeHelper);
Assert.False(string.IsNullOrWhiteSpace(d));
Assert.Contains("review_task", d);
Assert.Contains("continue_merge", d);
}
[Fact]
public void DefaultFor_merge_helper_initial_has_scope_and_tasks_tokens()
{
var d = PromptFiles.DefaultFor(PromptKind.MergeHelperInitial);
Assert.False(string.IsNullOrWhiteSpace(d));
Assert.Contains("{scope}", d);
Assert.Contains("{tasks}", d);
}
[Fact]
public void RenderTemplate_merge_helper_initial_substitutes_scope_and_tasks()
{
var outp = PromptFiles.RenderTemplate(
PromptFiles.DefaultFor(PromptKind.MergeHelperInitial),
new Dictionary<string, string> { ["scope"] = "All lists", ["tasks"] = "- [Idle] T1" });
Assert.Contains("Scope: All lists", outp);
Assert.Contains("- [Idle] T1", outp);
Assert.DoesNotContain("{scope}", outp);
Assert.DoesNotContain("{tasks}", outp);
}
} }