fix(engine): break Plan Review REVISE replan loop (feedback + bounded cap) (#2078)
## Problem A task whose Plan Review step returns verdict `REVISE` can loop forever: plan → plan-review REVISE → `needs-replan` → re-plan → near-identical plan → REVISE → repeat. The triage **pre-execution** Plan Review gate (`runPlanReviewBeforeExecution`) sets `status: "needs-replan"` on REVISE with **no cap and no escape to `awaiting-approval`** — unlike the executor graph path, which already has `PLAN_REVIEW_REPLAN_HARD_CAP`. Under `planApprovalMode: require-all` there is also no human exit, because the task never reaches `awaiting-approval`. Separately, replan feedback (`triage.ts`) was derived only from `task.log` comment actions + the latest user comment; it never consulted the plan-review verdict stored in `task.workflowStepResults`. ## Fix 1. **Thread plan-review feedback into replan** — when re-planning with no comment-derived feedback, seed `buildSpecificationPrompt` from the most recent `plan-review` REVISE `output` in `workflowStepResults` (existing user/AI-comment precedence preserved). 2. **Bounded cap** — new `planReviewReplanCount` counter (`types.ts`, `store.ts` column + updateTask, `db.ts` migration 146, `manual-retry-reset.ts`). After `PLAN_REVIEW_GATE_REPLAN_CAP = 3` consecutive REVISE replans the task escalates to `awaiting-approval` (`awaitingApprovalReason: "plan-review-replan-cap"`) instead of replanning. Counter resets on APPROVE. ## Tests Adds `triage-replan-feedback-from-plan-review.test.ts` and `triage-plan-review-replan-cap.test.ts`. Merge gate green locally (`verify:fast`, `test:gate` 337+63, `lint`); changeset included. Made with Claude (see `Co-Authored-By` trailer). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Prevented Plan Review “REVISE” from looping indefinitely by enforcing a bounded replan cap. * After repeated Plan Review replans, tasks now escalate to an approval-hold state with a dedicated reason. * Improved replan feedback by seeding from the latest Plan Review output when no explicit feedback is available; the counter clears when Plan Review approves. * Manual retries now reset the Plan Review replan cap counter. * **Documentation** * Added release notes describing the Plan Review replan safeguards. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
@@ -86,6 +86,7 @@ CREATE TABLE IF NOT EXISTS project.tasks (
|
||||
-- FNXC:SqliteFinalRemoval 2026-06-25: retry/stuck counters missed in initial snapshot
|
||||
stuck_kill_count integer DEFAULT 0,
|
||||
post_review_fix_count integer DEFAULT 0,
|
||||
plan_review_replan_count integer DEFAULT 0,
|
||||
verification_failure_count integer DEFAULT 0,
|
||||
branch_conflict_recovery_count integer DEFAULT 0,
|
||||
reviewer_context_retry_count integer DEFAULT 0,
|
||||
|
||||
@@ -167,6 +167,10 @@ export const EXPECTED_PROJECT_COLUMNS: ReadonlyArray<{ schema?: string; table: s
|
||||
// FNXC:WorkflowLifecycle 2026-07-12: FN-7863 execute self-requeue streak (merge port).
|
||||
{ table: "tasks", column: "execute_requeue_loop_count", type: "integer" },
|
||||
{ table: "tasks", column: "execute_requeue_loop_signature", type: "text" },
|
||||
// FNXC:PlanReviewReplan 2026-07-13: bounded triage Plan Review REVISE replan counter.
|
||||
// Additive column not present in the baseline snapshot, so existing embedded-PG
|
||||
// databases must self-heal it via ALTER TABLE ADD COLUMN IF NOT EXISTS on boot.
|
||||
{ table: "tasks", column: "plan_review_replan_count", type: "integer" },
|
||||
// distributed_task_id_state
|
||||
{ table: "distributed_task_id_state", column: "prefix", type: "text" },
|
||||
{ table: "distributed_task_id_state", column: "next_sequence", type: "integer" },
|
||||
|
||||
@@ -122,6 +122,7 @@ export const tasks = projectSchema.table("tasks", {
|
||||
*/
|
||||
stuckKillCount: integer("stuck_kill_count").default(0),
|
||||
postReviewFixCount: integer("post_review_fix_count").default(0),
|
||||
planReviewReplanCount: integer("plan_review_replan_count").default(0),
|
||||
verificationFailureCount: integer("verification_failure_count").default(0),
|
||||
branchConflictRecoveryCount: integer("branch_conflict_recovery_count").default(0),
|
||||
reviewerContextRetryCount: integer("reviewer_context_retry_count").default(0),
|
||||
|
||||
Reference in New Issue
Block a user