diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 6b614173a..43a2fbfc4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -41,6 +41,7 @@ - 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 the Bash tool hanging when in-process commands read process substitution operands such as `<(cmd)` ([#5557](https://github.com/can1357/oh-my-pi/issues/5557)). - Fixed `/share` and `/export` web views rendering inline Markdown inside list items as literal text ([#5567](https://github.com/can1357/oh-my-pi/issues/5567)). +- 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 7ecff66ba..e03e0dadc 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. Write your plan's ``/title as plain text to `xd://propose` 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}}