refactor(prompts): split the list-handler prompt per session phase

Both merge-helper ConPTY sessions loaded PromptKind.MergeHelper, so the
post-handoff session received the phase 0-2 dedupe/enhance instructions and was
told to ignore them by its brief alone.

Split into MergeHelperTriage (phases 0-2 + handoff) and MergeHelperExecute
(phases 3-5), so each session carries only its own phases. Consolidated the
generic ask-the-user rule to one place per prompt, scoped Phase 5's summary to
what the execute session actually knows, and moved the dedupe/enhance bilanz to
the triage handoff. Regression guards assert neither prompt carries the other's
phase headings and that the shared-checkout git rule stays in execute.
This commit is contained in:
mika kuns
2026-08-06 21:00:41 +02:00
parent d5730a8405
commit a7ff1b3a2f
5 changed files with 129 additions and 35 deletions
@@ -905,7 +905,9 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
var appendIdx = args.IndexOf("--append-system-prompt-file");
var systemPromptPath = args[appendIdx + 1];
Assert.Equal(Path.Combine(sessionDir, "system-prompt.md"), systemPromptPath);
Assert.Equal(PromptFiles.ReadOrDefault(PromptKind.MergeHelper), File.ReadAllText(systemPromptPath));
// The handoff session gets the Execute prompt (phases 3-5), NOT the Triage prompt the
// first session ran with — otherwise it carries dedupe/enhance instructions to ignore.
Assert.Equal(PromptFiles.ReadOrDefault(PromptKind.MergeHelperExecute), File.ReadAllText(systemPromptPath));
var kickoff = args[^1];
var handoffPath = Path.Combine(sessionDir, "handoff.md");
@@ -970,6 +972,40 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
Assert.Equal(_worktreeDir, spec.Cwd);
}
// Regression guard for the bug where both merge-helper builders wrote the SAME system prompt,
// so the handoff session received the phase 0-2 (dedupe/enhance) instructions and was told to
// ignore them by its brief alone. Each session must carry only its own phases.
[Fact]
public async Task MergeHelperBuilders_WriteDifferentSystemPrompts_EachScopedToItsOwnPhases()
{
var repo = Path.Combine(_tempDir, "repoPromptSplit");
Directory.CreateDirectory(repo);
var listId = await SeedListAsync(workingDir: repo, name: "Alpha");
var handlerTaskId = Guid.NewGuid().ToString();
await SeedTaskAsync(handlerTaskId, listId, TaskStatus.Idle, title: "Handler");
var survivor = Guid.NewGuid().ToString();
await SeedTaskAsync(survivor, listId, TaskStatus.WaitingForReview, title: "Survivor");
var svc = BuildService();
var triageSpec = await svc.BuildForMergeHelperAsync(new[] { survivor }, listId, CancellationToken.None);
var triageDir = TrackSessionDir(triageSpec);
var executeSpec = await svc.BuildForMergeHelperHandoffAsync(handlerTaskId, new[] { survivor }, CancellationToken.None);
var executeDir = TrackSessionDir(executeSpec);
var triagePrompt = File.ReadAllText(Path.Combine(triageDir, "system-prompt.md"));
var executePrompt = File.ReadAllText(Path.Combine(executeDir, "system-prompt.md"));
Assert.NotEqual(triagePrompt, executePrompt);
Assert.Contains("## Phase 1", triagePrompt);
Assert.DoesNotContain("## Phase 4", triagePrompt);
Assert.Contains("## Phase 4", executePrompt);
Assert.DoesNotContain("## Phase 1", executePrompt);
}
// Regression guard for the bug where BuildForMergeHelperHandoffAsync set MCP_TOOL_TIMEOUT to
// an older, shorter value (200000) than every other ConPTY spec (930000) after a parallel
// merge landed the two changes independently. Every spec this service builds must carry the