diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 71e86facb..287b92158 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -16,6 +16,7 @@ - Fixed Bash internal URLs remaining unresolved when used as unquoted arguments inside command substitutions ([#5535](https://github.com/can1357/oh-my-pi/issues/5535)). - Fixed the built-in `fd` printing `fd: Broken pipe (os error 32)` when a downstream pipeline reader exited early (e.g. `fd … | head`); it now exits silently with 141 (128+SIGPIPE), matching real fd. - Fixed prewalk repeatedly continuing after a bash-only task such as `commit` had already completed ([#5551](https://github.com/can1357/oh-my-pi/issues/5551)). +- Fixed plan-mode re-entry dropping a new plan request when a prior plan artifact existed: the re-entry prompt led with the old plan and contradicted the plan-file guidance, so weak models only reconciled the incomplete previous plan. Re-entry now anchors on the new request and folds any old-plan corrections into it ([#5576](https://github.com/can1357/oh-my-pi/issues/5576)). ## [16.5.2] - 2026-07-14 diff --git a/packages/coding-agent/src/plan-mode/reentry-prompt.test.ts b/packages/coding-agent/src/plan-mode/reentry-prompt.test.ts new file mode 100644 index 000000000..2566c799e --- /dev/null +++ b/packages/coding-agent/src/plan-mode/reentry-prompt.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "bun:test"; +import { prompt } from "@oh-my-pi/pi-utils"; +import planModeActivePrompt from "../prompts/system/plan-mode-active.md" with { type: "text" }; + +const BASE = { + planFilePath: "local://old-feature-plan.md", + askToolName: "ask", + writeToolName: "write", + editToolName: "edit", + isHashlineEditMode: false, + iterative: false, +} as const; + +function render(overrides: { reentry: boolean; planExists: boolean }): string { + return prompt.render(planModeActivePrompt, { ...BASE, ...overrides }); +} + +describe("plan-mode re-entry prompt", () => { + it("only emits the Re-entry section when re-entering", () => { + expect(render({ reentry: false, planExists: true })).not.toContain("## Re-entry"); + expect(render({ reentry: true, planExists: true })).toContain("## Re-entry"); + }); + + it("anchors the turn on the new request, not the old plan", () => { + const rendered = render({ reentry: true, planExists: true }); + const reentry = rendered.slice(rendered.indexOf("## Re-entry")); + // The new request is the primary input; the old plan is reference only. + expect(reentry).toMatch(/NEW request[\s\S]*primary input/); + // Corrections to an incomplete old plan must be folded into the new plan, + // never substituted for it (the reported failure: dropping the new request). + expect(reentry).toMatch(/combine, never substitute/); + }); + + it("does not contradict the planExists guidance on a different task", () => { + const rendered = render({ reentry: true, planExists: true }); + // planExists branch: different task -> leave old plan, write a fresh file. + expect(rendered).toContain("leave that plan in place and start a fresh"); + // Re-entry must agree; the old "Different task -> overwrite it" directive is gone. + expect(rendered).not.toMatch(/[Dd]ifferent task → overwrite/); + }); +}); diff --git a/packages/coding-agent/src/prompts/system/plan-mode-active.md b/packages/coding-agent/src/prompts/system/plan-mode-active.md index 5056ff0be..0d25a8f94 100644 --- a/packages/coding-agent/src/prompts/system/plan-mode-active.md +++ b/packages/coding-agent/src/prompts/system/plan-mode-active.md @@ -48,11 +48,14 @@ Every question MUST change the plan or settle a load-bearing choice. Batch them. {{#if reentry}} ## Re-entry +You are re-entering plan mode with a NEW request. That new request is the primary input and MUST be planned; the existing plan is only reference. You NEVER narrow the turn to reconciling the old plan and drop the new request. + -1. Read the existing plan. -2. Compare the new request against it. -3. Different task → overwrite it. Same task continuing → update it and delete outdated sections. -4. Call `resolve` with `action: "apply"` and `extra: { title }` when complete. +1. Read the new request and make it the plan you build this turn. +2. Read the existing plan as reference only. +3. Same task continuing → update that plan with `{{editToolName}}` and delete outdated sections. Different task → leave that plan in place and write a fresh `local://-plan.md` for the new request. +4. If the old plan has unfinished or broken work the new request depends on, fold those corrections INTO the new plan — combine, never substitute the old fix for the new request. +5. Call `resolve` with `action: "apply"` and `extra: { title }` when the new request is decision-complete. {{/if}}