diff --git a/src/lib/groomer/prompts/system-prompt.test.ts b/src/lib/groomer/prompts/system-prompt.test.ts index fd545455..f28fe453 100644 --- a/src/lib/groomer/prompts/system-prompt.test.ts +++ b/src/lib/groomer/prompts/system-prompt.test.ts @@ -36,7 +36,8 @@ describe("buildGroomerSystemPrompt", () => { it("includes escalation guidance when escalationLaneId is set", () => { const prompt = buildGroomerSystemPrompt(baseParams); - expect(prompt).toContain('Choose "cloud" ONLY for genuinely hard work'); + expect(prompt).toContain('Choose "cloud" when the work requires deciding between alternatives'); + expect(prompt).toContain("Judgement, not size, is the test"); }); it("omits escalation guidance when escalationLaneId is empty", () => { @@ -110,6 +111,34 @@ describe("buildGroomerSystemPrompt", () => { it("includes default lane guidance", () => { const prompt = buildGroomerSystemPrompt(baseParams); - expect(prompt).toContain('Default to "local" for the large majority of ready work'); + expect(prompt).toContain('Most ready work belongs in "local"'); + }); + + describe("lane selection", () => { + it("lets the groomer assign the escalation lane directly", () => { + const prompt = buildGroomerSystemPrompt(baseParams); + expect(prompt).toContain("Assign it directly when the issue calls for it"); + expect(prompt).not.toContain("you never need to pre-escalate"); + expect(prompt).not.toContain("the bridge automatically escalates"); + }); + + it("keeps size out of the escalation test", () => { + const prompt = buildGroomerSystemPrompt(baseParams); + expect(prompt).toContain("do NOT escalate merely because an issue touches many files"); + expect(prompt).toContain("Judgement, not size, is the test"); + }); + + it("makes no claim about what model any lane runs", () => { + const prompt = buildGroomerSystemPrompt(baseParams); + // Lane capability comes from the operator's lane descriptions, not from + // assumptions baked into the prompt about local vs hosted models. + expect(prompt).not.toContain("The local model is a capable coding model"); + }); + + it("omits escalation guidance entirely when no escalation lane is configured", () => { + const prompt = buildGroomerSystemPrompt({ ...baseParams, escalationLaneId: "" }); + expect(prompt).not.toContain("Judgement, not size, is the test"); + expect(prompt).toContain('Most ready work belongs in "local"'); + }); }); }); diff --git a/src/lib/groomer/prompts/system-prompt.ts b/src/lib/groomer/prompts/system-prompt.ts index 143b0b98..ab2466e6 100644 --- a/src/lib/groomer/prompts/system-prompt.ts +++ b/src/lib/groomer/prompts/system-prompt.ts @@ -71,7 +71,7 @@ Rules: - Lane must be one of the configured lane ids - When actionability is "ready", lane.id MUST be a claimable worker lane (${claimableIds})${backlogLaneId ? `, NEVER "${backlogLaneId}"` : ""}. Claimable lanes: ${laneGuide} - Default to "${defaultLaneId}" for the large majority of ready work. The local model is a capable coding model — it handles bug fixes, small-to-medium features, config/YAML/docs changes, and single-module refactors well, even when the change spans a few files. Do NOT escalate just because an issue touches multiple files or looks non-trivial.${escalationLaneId ? ` Choose "${escalationLaneId}" ONLY for genuinely hard work: large cross-cutting or cross-stack changes (e.g. an auth migration spanning backend and frontend), deep architectural redesign, or a change requiring reasoning across many modules at once. When unsure, choose "${defaultLaneId}" — the bridge automatically escalates to "${escalationLaneId}" if a local attempt is exhausted, so you never need to pre-escalate a borderline case.` : ""}${backlogLaneId ? `\n- The "${backlogLaneId}" lane is non-claimable — use it only when actionability is not "ready" (needs_info/blocked/backlog/already_done). Priority (P2/P3/low) does NOT mean backlog: a low-priority but ready issue still goes to a claimable lane.` : ""} + Choose the lane the work actually needs, using the descriptions above. Most ready work belongs in "${defaultLaneId}", because most issues are determinate: the change to make is already clear from the issue and its code, and a worker only has to carry it out. Bug fixes, small-to-medium features, config/YAML/docs changes and single-module refactors are normally determinate. Size is not the test — a determinate change spanning several files is still determinate, so do NOT escalate merely because an issue touches many files or looks large.${escalationLaneId ? ` Choose "${escalationLaneId}" when the work requires deciding between alternatives rather than carrying out a decision already made: a design or architecture change, a fix whose correct approach is genuinely arguable from the issue, or work that must hold several modules in mind at once to be done safely. Judgement, not size, is the test. Assign it directly when the issue calls for it — do not route work through "${defaultLaneId}" first to see whether it copes.` : ""}${backlogLaneId ? `\n- The "${backlogLaneId}" lane is non-claimable — use it only when actionability is not "ready" (needs_info/blocked/backlog/already_done). Priority (P2/P3/low) does NOT mean backlog: a low-priority but ready issue still goes to a claimable lane.` : ""} - Be concise in summary and reason fields Title rewriting rules: