lee-spec-kit 0.9.7 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -215,7 +215,7 @@ var koCli = {
215
215
  "init.choice.taskAgent.on": "\uC0AC\uC6A9 - \uAC01 Task \uAD6C\uD604\uC744 \uC704\uC784",
216
216
  "init.choice.taskAgent.off": "\uC0AC\uC6A9 \uC548 \uD568 - \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC9C1\uC811 \uAD6C\uD604",
217
217
  "init.prompt.reviews": "\uB3C5\uB9BD \uAC80\uC218 \uB2E8\uACC4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
218
- "init.prompt.maxReviewRounds": "\uC794\uC5EC \uC704\uD5D8\uC744 \uAE30\uB85D\uD558\uACE0 \uACC4\uC18D\uD558\uAE30 \uC804 \uB9AC\uBDF0 \uC9C0\uC801 \uC790\uB3D9 \uBC18\uC601 \uCD5C\uB300 \uD69F\uC218:",
218
+ "init.prompt.maxReviewRounds": "\uC794\uC5EC \uC704\uD5D8\uC744 \uAE30\uB85D\uD558\uACE0 \uACC4\uC18D\uD558\uAE30 \uC804 fresh \uB9AC\uBDF0 \uCD5C\uB300 \uC2E4\uD589 \uD69F\uC218:",
219
219
  "init.choice.review.plan": "Plan",
220
220
  "init.choice.review.task": "Task",
221
221
  "init.choice.review.feature": "Feature",
@@ -241,7 +241,7 @@ var koCli = {
241
241
  "init.log.workflowLabel": "\uC6CC\uD06C\uD50C\uB85C\uC6B0",
242
242
  "init.log.taskAgentLabel": "Task \uAD6C\uD604",
243
243
  "init.log.reviewsLabel": "\uAC80\uC218",
244
- "init.log.maxReviewRoundsLabel": "\uB9AC\uBDF0 \uC9C0\uC801 \uC790\uB3D9 \uBC18\uC601 \uD69F\uC218",
244
+ "init.log.maxReviewRoundsLabel": "fresh \uB9AC\uBDF0 \uCD5C\uB300 \uC2E4\uD589 \uD69F\uC218",
245
245
  "init.log.completionStrategyLabel": "\uB85C\uCEEC \uD1B5\uD569",
246
246
  "init.summary.taskAgent.on": "\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8",
247
247
  "init.summary.taskAgent.off": "\uBA54\uC778 \uC5D0\uC774\uC804\uD2B8",
@@ -531,7 +531,7 @@ var enCli = {
531
531
  "init.choice.taskAgent.on": "On - delegate each task implementation",
532
532
  "init.choice.taskAgent.off": "Off - implement tasks in the main agent",
533
533
  "init.prompt.reviews": "Select independent review gates:",
534
- "init.prompt.maxReviewRounds": "Maximum automatic finding remediation rounds before continuing with residual risks:",
534
+ "init.prompt.maxReviewRounds": "Maximum fresh review rounds before continuing with residual risks:",
535
535
  "init.choice.review.plan": "Plan",
536
536
  "init.choice.review.task": "Task",
537
537
  "init.choice.review.feature": "Feature",
@@ -557,7 +557,7 @@ var enCli = {
557
557
  "init.log.workflowLabel": "Workflow",
558
558
  "init.log.taskAgentLabel": "Task implementation",
559
559
  "init.log.reviewsLabel": "Reviews",
560
- "init.log.maxReviewRoundsLabel": "Review remediation rounds",
560
+ "init.log.maxReviewRoundsLabel": "Maximum fresh review rounds",
561
561
  "init.log.completionStrategyLabel": "Local integration",
562
562
  "init.summary.taskAgent.on": "subagent",
563
563
  "init.summary.taskAgent.off": "main agent",
@@ -1375,13 +1375,13 @@ Before taking the next workflow step:
1375
1375
  13. When \`workflow-stage --json\` returns \`nextAction.category === "plan_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review of \`spec.md\` and \`plan.md\` using the returned model settings and exact \`specHash\` / \`planHash\`; the main agent records the returned reviewRound, evidence, decision, reviewer metadata, and both hashes, and any later spec/plan content change requires a fresh review
1376
1376
  14. When \`workflow-stage --json\` returns \`nextAction.category === "task_execute"\` with \`executor === "subagent"\`, mark exactly the returned \`taskId\` as active and delegate its implementation plus task-scoped verification to a fresh subagent in the returned \`workingDirectory\`, using the returned \`model\`, \`reasoningEffort\`, \`onUnavailable\`, and exact \`workerContract\`; no named execution skill is required
1377
1377
  15. The task implementation worker executes directly: it must follow the approved Verification Contract, must not add unplanned durable tests, and must not run \`workflow-stage\`, spawn another subagent, edit lee-spec-kit docs, change task state, commit, request approvals, or perform remote/destructive actions. It may modify project code and run task-scoped checks only. The main agent inspects the result, synchronizes docs and task state, and owns every commit and workflow transition; official hooks block commits while \`task_execute\` is still active
1378
- 16. When \`workflow-stage --json\` returns \`nextAction.category === "task_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review for the returned \`taskId\`, \`baseSha\`, \`targetSha\`, and \`targetTree\`; the main agent records the returned reviewRound and evidence and moves the task from \`REVIEW\` to \`DONE\` only after an approve decision
1378
+ 16. When \`workflow-stage --json\` returns \`nextAction.category === "task_review"\` with \`executor === "subagent"\`, delegate a fresh read-only review for the returned \`taskId\`, \`baseSha\`, \`targetSha\`, and \`targetTree\`; the main agent records the returned reviewRound and evidence and moves the task from \`REVIEW\` to \`DONE\` after an approve decision or the automatic exhausted-\`changes_requested\` path described below
1379
1379
  17. When \`workflow-stage --json\` returns \`nextAction.category === "pre_pr_review"\` and \`nextAction.executor === "subagent"\`, delegate a fresh read-only Feature review using the returned \`model\`, \`reasoningEffort\`, \`onUnavailable\`, reviewRound, and review target metadata; do not select or require a named review skill
1380
1380
  18. Review subagents return findings without modifying code; the main agent remediates findings and records the actual reviewer metadata, reviewed scope, evidence, decision, and exact hash/SHA/tree target metadata
1381
1381
  19. After delegating to a subagent, wait until it returns a terminal outcome: completed, explicit failure, cancellation, or an approval/user-input request that requires action
1382
1382
  20. While the subagent remains running, use repeated bounded waits, preferably longer waits. A bounded wait that returns no update, a lack of status messages, or a lack of file changes means only that the subagent is still pending; none is evidence of failure or stalled work. Read-only review subagents are expected not to modify files
1383
1383
  21. Do not interrupt, replace, or abandon a running subagent solely because it has been quiet or has not changed files. Stop it only after an explicit user request, a terminal failure/cancellation, or an unrecoverable runtime status
1384
- 22. workflow.agentReview.maxRounds counts automatic finding-remediation passes, not the initial review. After those passes are exhausted, keep the latest changes_requested findings as residual risks and automatically complete that Plan/task/Feature review gate without asking the user for a review-approval token; blocked decisions never auto-complete
1384
+ 22. workflow.agentReview.maxRounds is the maximum number of fresh reviews for each Plan/task/Feature gate. A changes_requested decision on the final allowed review is remediated once, but the changed target is not reviewed again; keep remaining findings and the post-review target change as residual risks and automatically complete the gate without asking the user for a review-approval token. For example, maxRounds=1 means review round 1, remediate once, then continue with no round 2; blocked decisions never auto-complete
1385
1385
  23. For a local workflow, do not report completion directly after implementation approval; follow the exact returned \`local verify\` / \`local merge\` / \`local cleanup\` commands until \`workflow-stage\` proves verification, integration, and cleanup and returns \`done\`; review-fix and \`feature_remediation\` stages explicitly permit scoped fixes
1386
1386
  24. In a \`local-ff\` or \`local-squash\` workflow, keep implementation approval and local merge approval distinct when \`local_merge\` is required: the first accepts the implementation, and the second authorizes the configured integration strategy, post-merge checks, and local cleanup
1387
1387
 
@@ -2139,7 +2139,7 @@ function initCommand(program2) {
2139
2139
  "Review gates: comma-separated plan,task,feature | none"
2140
2140
  ).option(
2141
2141
  "--max-review-rounds <count>",
2142
- "Automatic review finding remediation rounds before continuing with residual risks (default: 1)"
2142
+ "Maximum fresh review rounds before continuing with residual risks (default: 1)"
2143
2143
  ).option(
2144
2144
  "--completion-strategy <strategy>",
2145
2145
  "Local completion: local-ff | local-squash | none"
@@ -4558,7 +4558,7 @@ function configCommand(program2) {
4558
4558
  "Review gates: comma-separated plan,task,feature | none"
4559
4559
  ).option(
4560
4560
  "--max-review-rounds <count>",
4561
- "Automatic review finding remediation rounds before continuing with residual risks"
4561
+ "Maximum fresh review rounds before continuing with residual risks"
4562
4562
  ).option(
4563
4563
  "--completion-strategy <strategy>",
4564
4564
  "Local completion: local-ff | local-squash | none"
@@ -8847,8 +8847,8 @@ function resolveMaxReviewRounds(config) {
8847
8847
  const configured = config.workflow?.agentReview?.maxRounds;
8848
8848
  return typeof configured === "number" && Number.isInteger(configured) && configured > 0 ? configured : 1;
8849
8849
  }
8850
- function reviewRemediationLimitReached(config, reviewRound, decisionOutcome) {
8851
- return decisionOutcome === "changes_requested" && (reviewRound || 1) > resolveMaxReviewRounds(config);
8850
+ function reviewRoundLimitReached(config, reviewRound, decisionOutcome) {
8851
+ return decisionOutcome === "changes_requested" && (reviewRound || 1) >= resolveMaxReviewRounds(config);
8852
8852
  }
8853
8853
  function parseWorkflowTaskLine(line, index = -1) {
8854
8854
  const canonical = parseTaskLine(line, index);
@@ -9573,28 +9573,34 @@ function reviewEvidenceSatisfied(config, feature, scope, evidence) {
9573
9573
  });
9574
9574
  }
9575
9575
  function planReviewSatisfied(config, feature, review, target) {
9576
- return review.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", review.evidence) && (review.decisionOutcome === "approve" || reviewRemediationLimitReached(
9576
+ const reviewLimitReached = reviewRoundLimitReached(
9577
9577
  config,
9578
9578
  review.reviewRound,
9579
9579
  review.decisionOutcome
9580
- )) && review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
9580
+ );
9581
+ const targetMatches = review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
9582
+ return review.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", review.evidence) && (review.decisionOutcome === "approve" && targetMatches || reviewLimitReached && !targetMatches);
9581
9583
  }
9582
9584
  function featureReviewSatisfied(config, feature, tasks, target) {
9583
- return !!target && tasks.prePrReviewStatus === "done" && reviewEvidenceSatisfied(config, feature, "feature", tasks.prePrEvidence) && !!tasks.prePrDecision && (tasks.prePrDecisionOutcome === "approve" || reviewRemediationLimitReached(
9585
+ const reviewLimitReached = reviewRoundLimitReached(
9584
9586
  config,
9585
9587
  tasks.prePrReviewRound,
9586
9588
  tasks.prePrDecisionOutcome
9587
- )) && tasks.prePrReviewedHead === target.targetSha && tasks.prePrReviewedTree === target.targetTree;
9589
+ );
9590
+ const targetMatches = !!target && tasks.prePrReviewedHead === target.targetSha && tasks.prePrReviewedTree === target.targetTree;
9591
+ return !!target && tasks.prePrReviewStatus === "done" && reviewEvidenceSatisfied(config, feature, "feature", tasks.prePrEvidence) && !!tasks.prePrDecision && (tasks.prePrDecisionOutcome === "approve" && targetMatches || reviewLimitReached && !targetMatches);
9588
9592
  }
9589
9593
  function taskReviewEvidenceSatisfied(config, feature, task, target) {
9590
- return !!target && reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence) && (task.reviewDecisionOutcome === "approve" || reviewRemediationLimitReached(
9594
+ const reviewLimitReached = reviewRoundLimitReached(
9591
9595
  config,
9592
9596
  task.reviewRound,
9593
9597
  task.reviewDecisionOutcome
9594
- )) && task.reviewedHead === target.targetSha && task.reviewedTree === target.targetTree;
9598
+ );
9599
+ const targetMatches = !!target && task.reviewedHead === target.targetSha && task.reviewedTree === target.targetTree;
9600
+ return !!target && reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence) && (task.reviewDecisionOutcome === "approve" && targetMatches || reviewLimitReached && !targetMatches);
9595
9601
  }
9596
9602
  function recordedTaskReviewSatisfied(config, feature, projectGitCwd, task, currentTargetSha) {
9597
- if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve" && !reviewRemediationLimitReached(
9603
+ if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve" && !reviewRoundLimitReached(
9598
9604
  config,
9599
9605
  task.reviewRound,
9600
9606
  task.reviewDecisionOutcome
@@ -9605,7 +9611,7 @@ function recordedTaskReviewSatisfied(config, feature, projectGitCwd, task, curre
9605
9611
  ["rev-parse", `${task.reviewedHead}^{tree}`],
9606
9612
  projectGitCwd
9607
9613
  ) || "";
9608
- return actualTree === task.reviewedTree && isAncestor(projectGitCwd, task.reviewedHead, currentTargetSha);
9614
+ return actualTree === task.reviewedTree && isAncestor(projectGitCwd, task.reviewedHead, currentTargetSha) && (task.reviewDecisionOutcome === "approve" || task.reviewedHead !== currentTargetSha);
9609
9615
  }
9610
9616
  function resolvePreviousTaskReviewBase(config, feature, tasks, task) {
9611
9617
  const taskIndex = tasks.tasks.indexOf(task);
@@ -10009,7 +10015,8 @@ function resolvePlanReviewPayload(config, feature, review, target) {
10009
10015
  const targetMatches = review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
10010
10016
  const maxReviewRounds = resolveMaxReviewRounds(config);
10011
10017
  const reviewRound = review.reviewRound || 1;
10012
- const nextReviewRound = !targetMatches && review.decisionOutcome ? reviewRound + 1 : reviewRound;
10018
+ const requestedReviewRound = !targetMatches && review.decisionOutcome ? reviewRound + 1 : reviewRound;
10019
+ const nextReviewRound = Math.min(requestedReviewRound, maxReviewRounds);
10013
10020
  const actionContext = {
10014
10021
  reviewScope: "plan",
10015
10022
  reviewRound: nextReviewRound,
@@ -10019,7 +10026,8 @@ function resolvePlanReviewPayload(config, feature, review, target) {
10019
10026
  docsDirectory: config.docsDir
10020
10027
  };
10021
10028
  if (review.decisionOutcome === "changes_requested") {
10022
- if (targetMatches) {
10029
+ if (targetMatches && reviewRound <= maxReviewRounds) {
10030
+ const finalReviewRound = reviewRound >= maxReviewRounds;
10023
10031
  return {
10024
10032
  status: "ok",
10025
10033
  reasonCode: "WORKFLOW_STAGE_RESOLVED",
@@ -10028,7 +10036,7 @@ function resolvePlanReviewPayload(config, feature, review, target) {
10028
10036
  stage: "plan_review_fix",
10029
10037
  nextAction: buildAction(
10030
10038
  "plan_review_fix",
10031
- `Address the independent Plan review findings in spec.md or plan.md, record review round ${reviewRound + 1}, keep implementation blocked, and request a fresh review for the resulting document hashes.`,
10039
+ finalReviewRound ? `Address the independent Plan review findings in spec.md or plan.md, keep review round ${reviewRound}, and do not request another review. The resulting document hash changes will be preserved with the remaining findings as residual risks before automatic Plan promotion.` : `Address the independent Plan review findings in spec.md or plan.md, record review round ${reviewRound + 1}, keep implementation blocked, and request a fresh review for the resulting document hashes.`,
10032
10040
  false,
10033
10041
  null,
10034
10042
  void 0,
@@ -10040,6 +10048,26 @@ function resolvePlanReviewPayload(config, feature, review, target) {
10040
10048
  };
10041
10049
  }
10042
10050
  }
10051
+ if (review.decisionOutcome === "changes_requested" && reviewRound >= maxReviewRounds) {
10052
+ return {
10053
+ status: "ok",
10054
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
10055
+ docsDir: config.docsDir,
10056
+ featureRef: buildFeatureRef(feature),
10057
+ stage: "plan_review",
10058
+ nextAction: buildAction(
10059
+ "plan_review",
10060
+ `The Plan review round limit was reached at round ${reviewRound}. Do not delegate another review. Repair or record the final-round evidence if needed, preserve the remaining findings and any post-review hash changes as residual risks, and continue through automatic Plan promotion.`,
10061
+ false,
10062
+ null,
10063
+ void 0,
10064
+ { ...actionContext, reviewRound: maxReviewRounds }
10065
+ ),
10066
+ approvalRequired: false,
10067
+ implementationAllowed: false,
10068
+ blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
10069
+ };
10070
+ }
10043
10071
  if (targetMatches && review.decisionOutcome === "blocked") {
10044
10072
  return {
10045
10073
  status: "ok",
@@ -10119,11 +10147,11 @@ async function collectWorkflowStage(cwd, selector, component) {
10119
10147
  specContent || "",
10120
10148
  planContent || ""
10121
10149
  );
10122
- const planReviewAutoCompleted = planReview.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", planReview.evidence) && reviewRemediationLimitReached(
10150
+ const planReviewAutoCompleted = planReview.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", planReview.evidence) && reviewRoundLimitReached(
10123
10151
  config,
10124
10152
  planReview.reviewRound,
10125
10153
  planReview.decisionOutcome
10126
- ) && planReview.reviewedSpecHash === planReviewTarget.specHash && planReview.reviewedPlanHash === planReviewTarget.planHash;
10154
+ ) && (planReview.reviewedSpecHash !== planReviewTarget.specHash || planReview.reviewedPlanHash !== planReviewTarget.planHash);
10127
10155
  const enforcePlanReview = requirements.planReviewEnabled && (tasks.docStatus !== "approved" || planReview.hasMetadata);
10128
10156
  const issueDraft = parseWorkflowDraftMetadataExtended(issueContent || "");
10129
10157
  const prDraft = parseWorkflowDraftMetadataExtended(prContent || "");
@@ -10181,7 +10209,7 @@ async function collectWorkflowStage(cwd, selector, component) {
10181
10209
  stage: "plan",
10182
10210
  nextAction: buildAction(
10183
10211
  isReviewStage ? "plan_approve" : "plan_write",
10184
- isReviewStage ? planReviewAutoCompleted ? "The Plan review remediation limit was reached. Preserve the remaining findings as residual risks, promote plan.md from Review to Approved, and continue automatically without asking the user for review approval." : approvalRequired ? "Get user approval and update plan.md status to Approved." : "Promote plan.md from Review to Approved and continue automatically." : "Write or refine plan.md until it is ready for approval.",
10212
+ isReviewStage ? planReviewAutoCompleted ? "The Plan review round limit was reached. Preserve the remaining findings and post-review hash changes as residual risks, promote plan.md from Review to Approved, and continue automatically without asking the user for review approval." : approvalRequired ? "Get user approval and update plan.md status to Approved." : "Promote plan.md from Review to Approved and continue automatically." : "Write or refine plan.md until it is ready for approval.",
10185
10213
  approvalRequired
10186
10214
  ),
10187
10215
  approvalRequired,
@@ -10369,6 +10397,7 @@ async function collectWorkflowStage(cwd, selector, component) {
10369
10397
  const evidenceMatchesTarget = reviewTask.reviewedHead === reviewTarget.targetSha && reviewTask.reviewedTree === reviewTarget.targetTree;
10370
10398
  if (reviewTask.reviewDecisionOutcome === "changes_requested") {
10371
10399
  if (evidenceMatchesTarget && reviewContext.reviewRound <= reviewContext.maxReviewRounds) {
10400
+ const finalReviewRound = reviewContext.reviewRound >= reviewContext.maxReviewRounds;
10372
10401
  return {
10373
10402
  status: "ok",
10374
10403
  reasonCode: "WORKFLOW_STAGE_RESOLVED",
@@ -10377,7 +10406,7 @@ async function collectWorkflowStage(cwd, selector, component) {
10377
10406
  stage: "task_review_fix",
10378
10407
  nextAction: buildAction(
10379
10408
  "task_review_fix",
10380
- `Address the independent review findings for ${reviewTask.taskId || reviewTask.title}, return the task to REVIEW, commit the new code tip, and request fresh review round ${reviewContext.reviewRound + 1}.`,
10409
+ finalReviewRound ? `Address the independent review findings for ${reviewTask.taskId || reviewTask.title}, return the task to REVIEW, and commit the new code tip without requesting another review. Preserve any remaining findings and the post-review target change as residual risks before automatic task completion.` : `Address the independent review findings for ${reviewTask.taskId || reviewTask.title}, return the task to REVIEW, commit the new code tip, and request fresh review round ${reviewContext.reviewRound + 1}.`,
10381
10410
  false,
10382
10411
  null,
10383
10412
  void 0,
@@ -10410,7 +10439,7 @@ async function collectWorkflowStage(cwd, selector, component) {
10410
10439
  };
10411
10440
  }
10412
10441
  if (taskReviewEvidenceSatisfied(config, feature, reviewTask, reviewTarget)) {
10413
- const completionSummary = reviewTask.reviewDecisionOutcome === "approve" ? reviewTask.status === "DONE" ? `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Commit the review evidence before continuing.` : `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Mark the task DONE and commit the review evidence before starting another task.` : reviewTask.status === "DONE" ? `The review remediation limit was reached for ${reviewTask.taskId || reviewTask.title}. Keep the remaining findings as residual risks and commit the review evidence before continuing automatically.` : `The review remediation limit was reached for ${reviewTask.taskId || reviewTask.title}. Keep the remaining findings as residual risks, mark the task DONE, and commit the review evidence before continuing automatically.`;
10442
+ const completionSummary = reviewTask.reviewDecisionOutcome === "approve" ? reviewTask.status === "DONE" ? `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Commit the review evidence before continuing.` : `The independent review approved ${reviewTask.taskId || reviewTask.title} at the current code tree. Mark the task DONE and commit the review evidence before starting another task.` : reviewTask.status === "DONE" ? `The review round limit was reached for ${reviewTask.taskId || reviewTask.title}. Keep the remaining findings${evidenceMatchesTarget ? "" : " and any post-review unreviewed target changes"} as residual risks and commit the review evidence before continuing automatically.` : `The review round limit was reached for ${reviewTask.taskId || reviewTask.title}. Keep the remaining findings${evidenceMatchesTarget ? "" : " and any post-review unreviewed target changes"} as residual risks, mark the task DONE, and commit the review evidence before continuing automatically.`;
10414
10443
  return {
10415
10444
  status: "ok",
10416
10445
  reasonCode: "WORKFLOW_STAGE_RESOLVED",
@@ -10430,6 +10459,26 @@ async function collectWorkflowStage(cwd, selector, component) {
10430
10459
  blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
10431
10460
  };
10432
10461
  }
10462
+ if (reviewTask.reviewDecisionOutcome === "changes_requested" && reviewContext.reviewRound >= reviewContext.maxReviewRounds) {
10463
+ return {
10464
+ status: "ok",
10465
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
10466
+ docsDir: config.docsDir,
10467
+ featureRef: buildFeatureRef(feature),
10468
+ stage: "task_review",
10469
+ nextAction: buildAction(
10470
+ "task_review",
10471
+ `The review round limit was reached for ${reviewTask.taskId || reviewTask.title} at round ${reviewContext.reviewRound}. Do not delegate another review. Repair or record the final-round evidence if needed and preserve the remaining findings and any post-review target changes as residual risks before completing the task automatically.`,
10472
+ false,
10473
+ null,
10474
+ void 0,
10475
+ reviewContext
10476
+ ),
10477
+ approvalRequired: false,
10478
+ implementationAllowed: false,
10479
+ blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
10480
+ };
10481
+ }
10433
10482
  return {
10434
10483
  status: "ok",
10435
10484
  reasonCode: "WORKFLOW_STAGE_RESOLVED",
@@ -10438,13 +10487,16 @@ async function collectWorkflowStage(cwd, selector, component) {
10438
10487
  stage: "task_review",
10439
10488
  nextAction: buildAction(
10440
10489
  "task_review",
10441
- `Delegate fresh read-only review round ${evidenceMatchesTarget ? reviewContext.reviewRound : reviewTask.reviewDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext.reviewRound} of ${reviewTask.taskId || reviewTask.title} for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record Review Round, evidence, decision, reviewer metadata, Reviewed Head, and Reviewed Tree without modifying code.`,
10490
+ `Delegate fresh read-only review round ${evidenceMatchesTarget ? reviewContext.reviewRound : reviewTask.reviewDecisionOutcome ? Math.min(reviewContext.reviewRound + 1, reviewContext.maxReviewRounds) : reviewContext.reviewRound} of ${reviewTask.taskId || reviewTask.title} for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record Review Round, evidence, decision, reviewer metadata, Reviewed Head, and Reviewed Tree without modifying code.`,
10442
10491
  false,
10443
10492
  null,
10444
10493
  resolveAgentReviewer(config, "task"),
10445
10494
  {
10446
10495
  ...reviewContext,
10447
- reviewRound: !evidenceMatchesTarget && reviewTask.reviewDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext.reviewRound
10496
+ reviewRound: !evidenceMatchesTarget && reviewTask.reviewDecisionOutcome ? Math.min(
10497
+ reviewContext.reviewRound + 1,
10498
+ reviewContext.maxReviewRounds
10499
+ ) : reviewContext.reviewRound
10448
10500
  }
10449
10501
  ),
10450
10502
  approvalRequired: false,
@@ -10554,6 +10606,7 @@ async function collectWorkflowStage(cwd, selector, component) {
10554
10606
  const evidenceMatchesTarget = !!reviewTarget && tasks.prePrReviewedHead === reviewTarget.targetSha && tasks.prePrReviewedTree === reviewTarget.targetTree;
10555
10607
  if (reviewContext && tasks.prePrDecisionOutcome === "changes_requested") {
10556
10608
  if (evidenceMatchesTarget && reviewContext.reviewRound <= reviewContext.maxReviewRounds) {
10609
+ const finalReviewRound = reviewContext.reviewRound >= reviewContext.maxReviewRounds;
10557
10610
  return {
10558
10611
  status: "ok",
10559
10612
  reasonCode: "WORKFLOW_STAGE_RESOLVED",
@@ -10562,7 +10615,7 @@ async function collectWorkflowStage(cwd, selector, component) {
10562
10615
  stage: "feature_review_fix",
10563
10616
  nextAction: buildAction(
10564
10617
  "feature_review_fix",
10565
- `Address the Feature review findings, commit the remediation, record review round ${reviewContext.reviewRound + 1}, and request a fresh review of the new code tree before implementation approval.`,
10618
+ finalReviewRound ? `Address the Feature review findings and commit the remediation without requesting another review. Preserve any remaining findings and the post-review target change as residual risks before continuing automatically.` : `Address the Feature review findings, commit the remediation, record review round ${reviewContext.reviewRound + 1}, and request a fresh review of the new code tree before implementation approval.`,
10566
10619
  false,
10567
10620
  null,
10568
10621
  void 0,
@@ -10595,7 +10648,12 @@ async function collectWorkflowStage(cwd, selector, component) {
10595
10648
  };
10596
10649
  }
10597
10650
  if (!featureReviewSatisfied(config, feature, tasks, reviewTarget)) {
10598
- const requestedRound = reviewContext && !evidenceMatchesTarget && tasks.prePrDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext?.reviewRound;
10651
+ const rawRequestedRound = reviewContext && !evidenceMatchesTarget && tasks.prePrDecisionOutcome ? reviewContext.reviewRound + 1 : reviewContext?.reviewRound;
10652
+ const requestedRound = reviewContext && typeof rawRequestedRound === "number" ? Math.min(
10653
+ rawRequestedRound,
10654
+ reviewContext.maxReviewRounds
10655
+ ) : rawRequestedRound;
10656
+ const reviewLimitExhausted = !!reviewContext && tasks.prePrDecisionOutcome === "changes_requested" && reviewContext.reviewRound >= reviewContext.maxReviewRounds;
10599
10657
  return {
10600
10658
  status: "ok",
10601
10659
  reasonCode: "WORKFLOW_STAGE_RESOLVED",
@@ -10604,10 +10662,10 @@ async function collectWorkflowStage(cwd, selector, component) {
10604
10662
  stage: "pre_pr_review",
10605
10663
  nextAction: buildAction(
10606
10664
  "pre_pr_review",
10607
- reviewTarget ? `Delegate independent read-only Feature review round ${requestedRound} to a fresh subagent for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record Review Round, findings, decision, reviewer metadata, Pre-PR Reviewed Head, and Pre-PR Reviewed Tree as evidence.` : "Create a project commit before requesting the independent Feature review.",
10665
+ reviewLimitExhausted ? `The Feature review round limit was reached at round ${reviewContext.reviewRound}. Do not delegate another review. Repair or record the final-round evidence if needed and preserve the remaining findings and any post-review target changes as residual risks before continuing automatically.` : reviewTarget ? `Delegate independent read-only Feature review round ${requestedRound} to a fresh subagent for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record Review Round, findings, decision, reviewer metadata, Pre-PR Reviewed Head, and Pre-PR Reviewed Tree as evidence.` : "Create a project commit before requesting the independent Feature review.",
10608
10666
  false,
10609
10667
  null,
10610
- reviewTarget ? resolveAgentReviewer(config, "feature") : void 0,
10668
+ reviewTarget && !reviewLimitExhausted ? resolveAgentReviewer(config, "feature") : void 0,
10611
10669
  reviewContext ? { ...reviewContext, reviewRound: requestedRound } : void 0
10612
10670
  ),
10613
10671
  approvalRequired: false,