Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions src/lib/groomer/prompts/system-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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"');
});
});
});
2 changes: 1 addition & 1 deletion src/lib/groomer/prompts/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading