lee-spec-kit 0.9.3 → 0.9.4

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
@@ -199,6 +199,27 @@ var koCli = {
199
199
  "init.choice.docsRepo.embedded.desc": "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4",
200
200
  "init.choice.docsRepo.standalone.title": "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
201
201
  "init.choice.docsRepo.standalone.desc": "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4",
202
+ "init.prompt.workflowMode": "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
203
+ "init.choice.workflow.github.title": "GitHub - Issue\uC640 PR \uAE30\uBC18 \uC6CC\uD06C\uD50C\uB85C\uC6B0",
204
+ "init.choice.workflow.github.desc": "\uC6D0\uACA9 Issue, \uBE0C\uB79C\uCE58, PR, \uB9AC\uBDF0, \uBCD1\uD569 \uB2E8\uACC4\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4",
205
+ "init.choice.workflow.local.title": "Local - \uB85C\uCEEC \uD1B5\uD569 \uC6CC\uD06C\uD50C\uB85C\uC6B0",
206
+ "init.choice.workflow.local.desc": "GitHub \uC5C6\uC774 \uAD00\uB9AC\uD615 worktree\uC640 \uB85C\uCEEC \uAC80\uC99D\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4",
207
+ "init.prompt.workflowSetup": "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC790\uB3D9\uD654 \uBC29\uC2DD\uC744 \uC124\uC815\uD558\uC138\uC694:",
208
+ "init.choice.workflowSetup.recommended.title": "\uAD8C\uC7A5 \uC124\uC815 - \uADE0\uD615 \uC7A1\uD78C \uAE30\uBCF8\uAC12 \uC0AC\uC6A9",
209
+ "init.choice.workflowSetup.recommended.desc": "Task \uAD6C\uD604\uC744 \uC704\uC784\uD558\uACE0 Plan\uACFC Feature\uB97C \uAC80\uC218\uD569\uB2C8\uB2E4",
210
+ "init.choice.workflowSetup.custom.title": "\uC9C1\uC811 \uC124\uC815 - \uC635\uC158 \uC120\uD0DD",
211
+ "init.choice.workflowSetup.custom.desc": "Task \uC704\uC784, \uAC80\uC218 \uB2E8\uACC4, \uB85C\uCEEC \uD1B5\uD569 \uBC29\uC2DD\uC744 \uC120\uD0DD\uD569\uB2C8\uB2E4",
212
+ "init.prompt.taskAgent": "Task \uAD6C\uD604\uC744 \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8\uC5D0\uAC8C \uC704\uC784\uD560\uAE4C\uC694?",
213
+ "init.choice.taskAgent.on": "\uC0AC\uC6A9 - \uAC01 Task \uAD6C\uD604\uC744 \uC704\uC784",
214
+ "init.choice.taskAgent.off": "\uC0AC\uC6A9 \uC548 \uD568 - \uBA54\uC778 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC9C1\uC811 \uAD6C\uD604",
215
+ "init.prompt.reviews": "\uB3C5\uB9BD \uAC80\uC218 \uB2E8\uACC4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
216
+ "init.choice.review.plan": "Plan",
217
+ "init.choice.review.task": "Task",
218
+ "init.choice.review.feature": "Feature",
219
+ "init.prompt.completionStrategy": "\uB85C\uCEEC \uD1B5\uD569 \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
220
+ "init.choice.completionStrategy.localFf": "local-ff - \uAC80\uC99D\uB41C Feature \uBE0C\uB79C\uCE58\uB97C fast-forward \uD1B5\uD569",
221
+ "init.choice.completionStrategy.localSquash": "local-squash - \uAC80\uC99D\uB41C \uD558\uB098\uC758 \uCEE4\uBC0B\uC73C\uB85C \uD1B5\uD569",
222
+ "init.choice.completionStrategy.none": "none - Feature \uBE0C\uB79C\uCE58\uB97C \uD1B5\uD569\uD558\uC9C0 \uC54A\uACE0 \uC885\uB8CC",
202
223
  "init.prompt.componentRepoPath": "{component} \uCEF4\uD3EC\uB10C\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
203
224
  "init.prompt.projectRepoPath": "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
204
225
  "init.validation.enterPath": "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694",
@@ -208,11 +229,19 @@ var koCli = {
208
229
  "init.prompt.remoteUrl": "\uC6D0\uACA9 \uB808\uD3EC URL\uC744 \uC785\uB825\uD558\uC138\uC694:",
209
230
  "init.validation.enterUrl": "URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694",
210
231
  "init.prompt.overwrite": "{dir} \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
232
+ "init.log.configSummaryTitle": "\u2699\uFE0F \uC124\uC815 \uC694\uC57D",
211
233
  "init.log.creatingDocs": "\u{1F4C1} docs \uAD6C\uC870 \uC0DD\uC131 \uC911...",
212
234
  "init.log.projectLabel": "\uD504\uB85C\uC81D\uD2B8",
213
235
  "init.log.typeLabel": "\uD0C0\uC785",
214
236
  "init.log.langLabel": "\uC5B8\uC5B4",
215
237
  "init.log.pathLabel": "\uACBD\uB85C",
238
+ "init.log.workflowLabel": "\uC6CC\uD06C\uD50C\uB85C\uC6B0",
239
+ "init.log.taskAgentLabel": "Task \uAD6C\uD604",
240
+ "init.log.reviewsLabel": "\uAC80\uC218",
241
+ "init.log.completionStrategyLabel": "\uB85C\uCEEC \uD1B5\uD569",
242
+ "init.summary.taskAgent.on": "\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8",
243
+ "init.summary.taskAgent.off": "\uBA54\uC778 \uC5D0\uC774\uC804\uD2B8",
244
+ "init.summary.reviews.none": "\uC5C6\uC74C",
216
245
  "init.log.docsCreated": "\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!",
217
246
  "init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
218
247
  "init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
@@ -482,6 +511,27 @@ var enCli = {
482
511
  "init.choice.docsRepo.embedded.desc": "Pushed together with the project",
483
512
  "init.choice.docsRepo.standalone.title": "standalone - separate docs repo",
484
513
  "init.choice.docsRepo.standalone.desc": "Configure push settings separately",
514
+ "init.prompt.workflowMode": "Select workflow mode:",
515
+ "init.choice.workflow.github.title": "GitHub - Issue and pull request workflow",
516
+ "init.choice.workflow.github.desc": "Use remote Issue, branch, pull request, review, and merge gates",
517
+ "init.choice.workflow.local.title": "Local - local integration workflow",
518
+ "init.choice.workflow.local.desc": "Use managed worktrees and local verification without requiring GitHub",
519
+ "init.prompt.workflowSetup": "Configure workflow automation:",
520
+ "init.choice.workflowSetup.recommended.title": "Recommended - use balanced defaults",
521
+ "init.choice.workflowSetup.recommended.desc": "Delegate task implementation and review Plan plus Feature",
522
+ "init.choice.workflowSetup.custom.title": "Custom - choose workflow options",
523
+ "init.choice.workflowSetup.custom.desc": "Select task delegation, review gates, and local integration",
524
+ "init.prompt.taskAgent": "Delegate task implementation to a subagent?",
525
+ "init.choice.taskAgent.on": "On - delegate each task implementation",
526
+ "init.choice.taskAgent.off": "Off - implement tasks in the main agent",
527
+ "init.prompt.reviews": "Select independent review gates:",
528
+ "init.choice.review.plan": "Plan",
529
+ "init.choice.review.task": "Task",
530
+ "init.choice.review.feature": "Feature",
531
+ "init.prompt.completionStrategy": "Select local integration strategy:",
532
+ "init.choice.completionStrategy.localFf": "local-ff - fast-forward the verified Feature branch",
533
+ "init.choice.completionStrategy.localSquash": "local-squash - integrate as one verified commit",
534
+ "init.choice.completionStrategy.none": "none - finish without integrating the Feature branch",
485
535
  "init.prompt.componentRepoPath": 'Enter repository path for component "{component}":',
486
536
  "init.prompt.projectRepoPath": "Enter project repository path:",
487
537
  "init.validation.enterPath": "Please enter a path",
@@ -491,11 +541,19 @@ var enCli = {
491
541
  "init.prompt.remoteUrl": "Enter remote repository URL:",
492
542
  "init.validation.enterUrl": "Please enter a URL",
493
543
  "init.prompt.overwrite": "{dir} already exists. Overwrite?",
544
+ "init.log.configSummaryTitle": "\u2699\uFE0F Configuration summary",
494
545
  "init.log.creatingDocs": "\u{1F4C1} Creating docs structure...",
495
546
  "init.log.projectLabel": "Project",
496
547
  "init.log.typeLabel": "Type",
497
548
  "init.log.langLabel": "Lang",
498
549
  "init.log.pathLabel": "Path",
550
+ "init.log.workflowLabel": "Workflow",
551
+ "init.log.taskAgentLabel": "Task implementation",
552
+ "init.log.reviewsLabel": "Reviews",
553
+ "init.log.completionStrategyLabel": "Local integration",
554
+ "init.summary.taskAgent.on": "subagent",
555
+ "init.summary.taskAgent.off": "main agent",
556
+ "init.summary.reviews.none": "none",
499
557
  "init.log.docsCreated": "\u2705 Docs structure created!",
500
558
  "init.log.nextStepsTitle": "Next steps:",
501
559
  "init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
@@ -1295,7 +1353,7 @@ Before taking the next workflow step:
1295
1353
  3. When relevant, also read \`issue.md\` and \`pr.md\`
1296
1354
  4. Run \`npx lee-spec-kit workflow-stage <feature-ref> --json\` and follow only the returned \`nextAction\`
1297
1355
  5. If \`workflow-stage --json\` returns \`primaryActionLabel\` and \`actionOptions\`, treat \`primaryActionLabel\` as the default option label and present the exact \`actionOptions[*].reply\` tokens to the user before continuing
1298
- 6. Do not start implementation unless \`stage === "implementation"\` and \`implementationAllowed === true\`
1356
+ 6. Do not modify implementation code unless \`implementationAllowed === true\`; normal task work uses \`stage === "implementation"\`, while \`task_review_fix\`, \`feature_review_fix\`, and \`feature_remediation\` are the only review/remediation exceptions
1299
1357
  7. Treat stages before implementation as hard gates:
1300
1358
  - spec approval plus plan / tasks readiness
1301
1359
  - issue preparation / issue creation
@@ -1306,10 +1364,14 @@ Before taking the next workflow step:
1306
1364
  10. In standalone mode, do not hand-write \`git worktree add\`; run the exact \`nextAction.command\` from \`workflow-stage\` so the managed workspace path, stale directory cleanup, and \`.env\` / \`.env.*\` copy step stay consistent
1307
1365
  11. Keep docs and code synchronized; if code changes materially, update the active feature docs in the same turn before stopping
1308
1366
  12. When docs are synced to code, keep exactly one explicit marker like \`<!-- lee-spec-kit:workflow-sync 2026-04-16T12:34:56.789Z -->\` in a single active feature doc (prefer \`tasks.md\` or \`decisions.md\`): replace an existing marker timestamp or remove duplicates instead of appending another marker, so \`workflow-audit\` can prove the sync happened after the latest code change
1309
- 13. When \`workflow-stage --json\` returns \`nextAction.category === "pre_pr_review"\` and \`nextAction.executor === "subagent"\`, delegate a fresh read-only review using the returned \`model\`, \`reasoningEffort\`, and \`onUnavailable\` policy; do not select or require a named review skill
1310
- 14. The Pre-PR review subagent returns findings without modifying code; the main agent remediates findings and records the actual reviewer metadata, reviewed diff scope, evidence, and final decision
1311
- 15. 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\`; \`feature_remediation\` explicitly permits Feature worktree fixes
1312
- 16. 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
1367
+ 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 evidence, decision, reviewer metadata, and both hashes, and any later spec/plan content change requires a fresh review
1368
+ 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
1369
+ 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
1370
+ 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 evidence and moves the task from \`REVIEW\` to \`DONE\` only after an approve decision
1371
+ 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\`, and review target metadata; do not select or require a named review skill
1372
+ 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
1373
+ 19. 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
1374
+ 20. 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
1313
1375
 
1314
1376
  Approval and remote actions:
1315
1377
 
@@ -1898,7 +1960,7 @@ var DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES = [
1898
1960
  "implementation_approve",
1899
1961
  "local_merge"
1900
1962
  ];
1901
- var PRE_PR_REVIEW_REASONING_EFFORTS = [
1963
+ var AGENT_REVIEW_REASONING_EFFORTS = [
1902
1964
  "low",
1903
1965
  "medium",
1904
1966
  "high",
@@ -1913,7 +1975,7 @@ function createDefaultApprovalConfig() {
1913
1975
  requireCheckCategories: [...DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES]
1914
1976
  };
1915
1977
  }
1916
- function createDefaultPrePrReviewerConfig() {
1978
+ function createDefaultAgentReviewerConfig() {
1917
1979
  return {
1918
1980
  type: "subagent",
1919
1981
  model: "inherit",
@@ -1921,6 +1983,15 @@ function createDefaultPrePrReviewerConfig() {
1921
1983
  onUnavailable: "inherit"
1922
1984
  };
1923
1985
  }
1986
+ function createDefaultAgentExecutionTaskConfig() {
1987
+ return {
1988
+ enabled: true,
1989
+ type: "subagent",
1990
+ model: "inherit",
1991
+ reasoningEffort: "high",
1992
+ onUnavailable: "inherit"
1993
+ };
1994
+ }
1924
1995
 
1925
1996
  // src/commands/init.ts
1926
1997
  function checkGitRepo(cwd) {
@@ -2005,6 +2076,35 @@ function hasStandaloneProjectRepoOutsideWorkspaceGitRoot(workspaceGitRoot, proje
2005
2076
  return !!projectGitRoot && path8.resolve(projectGitRoot) !== path8.resolve(workspaceGitRoot);
2006
2077
  });
2007
2078
  }
2079
+ var INIT_REVIEW_PHASES = ["plan", "task", "feature"];
2080
+ function parseInitReviews(value) {
2081
+ if (typeof value === "undefined") return void 0;
2082
+ const normalized = value.split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean);
2083
+ if (normalized.length === 1 && normalized[0] === "none") return [];
2084
+ if (normalized.length === 0 || normalized.includes("none") || normalized.some(
2085
+ (entry) => !INIT_REVIEW_PHASES.includes(entry)
2086
+ )) {
2087
+ throw createCliError(
2088
+ "INVALID_ARGUMENT",
2089
+ "`--reviews` must be a comma-separated subset of `plan,task,feature`, or `none`."
2090
+ );
2091
+ }
2092
+ return INIT_REVIEW_PHASES.filter((phase) => normalized.includes(phase));
2093
+ }
2094
+ function assertValidInitWorkflowOptions(options) {
2095
+ if (options.taskAgent && !["on", "off"].includes(options.taskAgent)) {
2096
+ throw createCliError(
2097
+ "INVALID_ARGUMENT",
2098
+ "`--task-agent` must be `on` or `off`."
2099
+ );
2100
+ }
2101
+ if (options.completionStrategy && !["local-ff", "local-squash", "none"].includes(options.completionStrategy)) {
2102
+ throw createCliError(
2103
+ "INVALID_ARGUMENT",
2104
+ "`--completion-strategy` must be `local-ff`, `local-squash`, or `none`."
2105
+ );
2106
+ }
2107
+ }
2008
2108
  function initCommand(program2) {
2009
2109
  program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option(
2010
2110
  "-t, --type <type>",
@@ -2012,7 +2112,13 @@ function initCommand(program2) {
2012
2112
  ).option(
2013
2113
  "--components <list>",
2014
2114
  "Component list for multi (comma-separated, e.g. app,api,worker)"
2015
- ).option("-l, --lang <lang>", "Language: ko | en (default: en)").option("--workflow <mode>", "Workflow mode: github | local").option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("--docs-repo <mode>", "Docs repository mode: embedded | standalone").option(
2115
+ ).option("-l, --lang <lang>", "Language: ko | en (default: en)").option("--workflow <mode>", "Workflow mode: github | local").option("--task-agent <mode>", "Task implementation agent: on | off").option(
2116
+ "--reviews <list>",
2117
+ "Review gates: comma-separated plan,task,feature | none"
2118
+ ).option(
2119
+ "--completion-strategy <strategy>",
2120
+ "Local completion: local-ff | local-squash | none"
2121
+ ).option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("--docs-repo <mode>", "Docs repository mode: embedded | standalone").option(
2016
2122
  "--project-root <path>",
2017
2123
  "Project root path (standalone single) or JSON map for standalone multi"
2018
2124
  ).option(
@@ -2042,6 +2148,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
2042
2148
  });
2043
2149
  }
2044
2150
  async function runInit(options) {
2151
+ assertValidInitWorkflowOptions(options);
2045
2152
  const cwd = process.cwd();
2046
2153
  const defaultName = path8.basename(cwd);
2047
2154
  let projectName = options.name || defaultName;
@@ -2049,6 +2156,9 @@ async function runInit(options) {
2049
2156
  let components = parseComponentsOption(options.components);
2050
2157
  let lang = options.lang || "en";
2051
2158
  let workflowMode = options.workflow || "github";
2159
+ let taskAgentEnabled = options.taskAgent !== "off";
2160
+ let enabledReviews = parseInitReviews(options.reviews) ?? ["plan", "feature"];
2161
+ let completionStrategy = options.completionStrategy || "local-ff";
2052
2162
  let docsRepo = options.docsRepo || "embedded";
2053
2163
  let pushDocs = typeof options.pushDocs === "boolean" ? options.pushDocs : void 0;
2054
2164
  let docsRemote = options.docsRemote;
@@ -2168,6 +2278,24 @@ async function runInit(options) {
2168
2278
  }
2169
2279
  ],
2170
2280
  initial: 0
2281
+ },
2282
+ {
2283
+ type: options.workflow ? null : "select",
2284
+ name: "workflowMode",
2285
+ message: tr(lang, "cli", "init.prompt.workflowMode"),
2286
+ choices: [
2287
+ {
2288
+ title: tr(lang, "cli", "init.choice.workflow.github.title"),
2289
+ value: "github",
2290
+ description: tr(lang, "cli", "init.choice.workflow.github.desc")
2291
+ },
2292
+ {
2293
+ title: tr(lang, "cli", "init.choice.workflow.local.title"),
2294
+ value: "local",
2295
+ description: tr(lang, "cli", "init.choice.workflow.local.desc")
2296
+ }
2297
+ ],
2298
+ initial: 0
2171
2299
  }
2172
2300
  ],
2173
2301
  {
@@ -2179,6 +2307,7 @@ async function runInit(options) {
2179
2307
  projectName = response.projectName || projectName;
2180
2308
  projectType = response.projectType || projectType;
2181
2309
  docsRepo = response.docsRepo || "embedded";
2310
+ workflowMode = response.workflowMode || workflowMode;
2182
2311
  if (docsRepo === "standalone") {
2183
2312
  const resolvedType = normalizeProjectType(
2184
2313
  String(projectType || response.projectType || "multi")
@@ -2290,6 +2419,128 @@ async function runInit(options) {
2290
2419
  docsRemote = remoteResponse.docsRemote || docsRemote;
2291
2420
  }
2292
2421
  }
2422
+ const hasExplicitWorkflowSetup = typeof options.taskAgent !== "undefined" || typeof options.reviews !== "undefined" || typeof options.completionStrategy !== "undefined";
2423
+ let customizeWorkflow = hasExplicitWorkflowSetup;
2424
+ if (!hasExplicitWorkflowSetup) {
2425
+ const setupResponse = await prompts(
2426
+ [
2427
+ {
2428
+ type: "select",
2429
+ name: "workflowSetup",
2430
+ message: tr(lang, "cli", "init.prompt.workflowSetup"),
2431
+ choices: [
2432
+ {
2433
+ title: tr(
2434
+ lang,
2435
+ "cli",
2436
+ "init.choice.workflowSetup.recommended.title"
2437
+ ),
2438
+ value: "recommended",
2439
+ description: tr(
2440
+ lang,
2441
+ "cli",
2442
+ "init.choice.workflowSetup.recommended.desc"
2443
+ )
2444
+ },
2445
+ {
2446
+ title: tr(
2447
+ lang,
2448
+ "cli",
2449
+ "init.choice.workflowSetup.custom.title"
2450
+ ),
2451
+ value: "custom",
2452
+ description: tr(
2453
+ lang,
2454
+ "cli",
2455
+ "init.choice.workflowSetup.custom.desc"
2456
+ )
2457
+ }
2458
+ ],
2459
+ initial: 0
2460
+ }
2461
+ ],
2462
+ {
2463
+ onCancel: () => {
2464
+ throw new Error("canceled");
2465
+ }
2466
+ }
2467
+ );
2468
+ customizeWorkflow = setupResponse.workflowSetup === "custom";
2469
+ }
2470
+ if (customizeWorkflow) {
2471
+ const workflowResponse = await prompts(
2472
+ [
2473
+ {
2474
+ type: typeof options.taskAgent === "undefined" ? "select" : null,
2475
+ name: "taskAgent",
2476
+ message: tr(lang, "cli", "init.prompt.taskAgent"),
2477
+ choices: [
2478
+ {
2479
+ title: tr(lang, "cli", "init.choice.taskAgent.on"),
2480
+ value: true
2481
+ },
2482
+ {
2483
+ title: tr(lang, "cli", "init.choice.taskAgent.off"),
2484
+ value: false
2485
+ }
2486
+ ],
2487
+ initial: 0
2488
+ },
2489
+ {
2490
+ type: typeof options.reviews === "undefined" ? "multiselect" : null,
2491
+ name: "reviews",
2492
+ message: tr(lang, "cli", "init.prompt.reviews"),
2493
+ choices: INIT_REVIEW_PHASES.map((phase) => ({
2494
+ title: tr(lang, "cli", `init.choice.review.${phase}`),
2495
+ value: phase,
2496
+ selected: phase !== "task"
2497
+ }))
2498
+ },
2499
+ {
2500
+ type: workflowMode === "local" && typeof options.completionStrategy === "undefined" ? "select" : null,
2501
+ name: "completionStrategy",
2502
+ message: tr(lang, "cli", "init.prompt.completionStrategy"),
2503
+ choices: [
2504
+ {
2505
+ title: tr(
2506
+ lang,
2507
+ "cli",
2508
+ "init.choice.completionStrategy.localFf"
2509
+ ),
2510
+ value: "local-ff"
2511
+ },
2512
+ {
2513
+ title: tr(
2514
+ lang,
2515
+ "cli",
2516
+ "init.choice.completionStrategy.localSquash"
2517
+ ),
2518
+ value: "local-squash"
2519
+ },
2520
+ {
2521
+ title: tr(lang, "cli", "init.choice.completionStrategy.none"),
2522
+ value: "none"
2523
+ }
2524
+ ],
2525
+ initial: 0
2526
+ }
2527
+ ],
2528
+ {
2529
+ onCancel: () => {
2530
+ throw new Error("canceled");
2531
+ }
2532
+ }
2533
+ );
2534
+ if (typeof workflowResponse.taskAgent === "boolean") {
2535
+ taskAgentEnabled = workflowResponse.taskAgent;
2536
+ }
2537
+ if (Array.isArray(workflowResponse.reviews)) {
2538
+ enabledReviews = INIT_REVIEW_PHASES.filter(
2539
+ (phase) => workflowResponse.reviews.includes(phase)
2540
+ );
2541
+ }
2542
+ completionStrategy = workflowResponse.completionStrategy || completionStrategy;
2543
+ }
2293
2544
  }
2294
2545
  if (!projectType) {
2295
2546
  projectType = "multi";
@@ -2315,6 +2566,12 @@ async function runInit(options) {
2315
2566
  tr(lang, "cli", "validation.context.workflowMode"),
2316
2567
  lang
2317
2568
  );
2569
+ if (workflowMode !== "local" && options.completionStrategy) {
2570
+ throw createCliError(
2571
+ "INVALID_ARGUMENT",
2572
+ "`--completion-strategy` can only be used with `--workflow local`."
2573
+ );
2574
+ }
2318
2575
  if (projectType === "single") {
2319
2576
  if (components.length > 0) {
2320
2577
  throw createCliError(
@@ -2478,7 +2735,7 @@ async function runInit(options) {
2478
2735
  }
2479
2736
  }
2480
2737
  console.log();
2481
- console.log(chalk.blue(tr(lang, "cli", "init.log.creatingDocs")));
2738
+ console.log(chalk.blue(tr(lang, "cli", "init.log.configSummaryTitle")));
2482
2739
  console.log(
2483
2740
  chalk.gray(
2484
2741
  ` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`
@@ -2493,6 +2750,35 @@ async function runInit(options) {
2493
2750
  console.log(
2494
2751
  chalk.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
2495
2752
  );
2753
+ console.log(
2754
+ chalk.gray(
2755
+ ` ${tr(lang, "cli", "init.log.workflowLabel")}: ${workflowMode}`
2756
+ )
2757
+ );
2758
+ console.log(
2759
+ chalk.gray(
2760
+ ` ${tr(lang, "cli", "init.log.taskAgentLabel")}: ${tr(
2761
+ lang,
2762
+ "cli",
2763
+ taskAgentEnabled ? "init.summary.taskAgent.on" : "init.summary.taskAgent.off"
2764
+ )}`
2765
+ )
2766
+ );
2767
+ const reviewSummary = enabledReviews.length > 0 ? enabledReviews.map((phase) => tr(lang, "cli", `init.choice.review.${phase}`)).join(", ") : tr(lang, "cli", "init.summary.reviews.none");
2768
+ console.log(
2769
+ chalk.gray(
2770
+ ` ${tr(lang, "cli", "init.log.reviewsLabel")}: ${reviewSummary}`
2771
+ )
2772
+ );
2773
+ if (workflowMode === "local") {
2774
+ console.log(
2775
+ chalk.gray(
2776
+ ` ${tr(lang, "cli", "init.log.completionStrategyLabel")}: ${completionStrategy}`
2777
+ )
2778
+ );
2779
+ }
2780
+ console.log();
2781
+ console.log(chalk.blue(tr(lang, "cli", "init.log.creatingDocs")));
2496
2782
  console.log();
2497
2783
  const templatesDir = getTemplatesDir();
2498
2784
  const commonPath = path8.join(templatesDir, lang, "common");
@@ -2541,14 +2827,33 @@ async function runInit(options) {
2541
2827
  taskCommitGate: "warn",
2542
2828
  ...workflowMode === "local" ? {
2543
2829
  baseBranch: resolveInitialBaseBranch(cwd, docsRepo, projectRoot),
2544
- completionStrategy: "local-ff",
2830
+ completionStrategy,
2545
2831
  deleteFeatureBranchAfterMerge: true,
2546
2832
  featureChecks: [],
2547
2833
  postMergeChecks: []
2548
2834
  } : {},
2549
- prePrReview: {
2550
- evidenceMode: "path_required",
2551
- reviewer: createDefaultPrePrReviewerConfig()
2835
+ agentExecution: {
2836
+ task: {
2837
+ ...createDefaultAgentExecutionTaskConfig(),
2838
+ enabled: taskAgentEnabled
2839
+ }
2840
+ },
2841
+ agentReview: {
2842
+ plan: {
2843
+ enabled: enabledReviews.includes("plan"),
2844
+ evidenceMode: "path_required",
2845
+ reviewer: createDefaultAgentReviewerConfig()
2846
+ },
2847
+ task: {
2848
+ enabled: enabledReviews.includes("task"),
2849
+ evidenceMode: "path_required",
2850
+ reviewer: createDefaultAgentReviewerConfig()
2851
+ },
2852
+ feature: {
2853
+ enabled: enabledReviews.includes("feature"),
2854
+ evidenceMode: "path_required",
2855
+ reviewer: createDefaultAgentReviewerConfig()
2856
+ }
2552
2857
  }
2553
2858
  },
2554
2859
  pr: {
@@ -2765,17 +3070,12 @@ function sanitizeTasksForLocal(content, lang) {
2765
3070
  const lines = next.split("\n");
2766
3071
  const filtered = [];
2767
3072
  for (const line of lines) {
2768
- if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태|Pre-PR Review|PR 리뷰)\*\*\s*:/.test(
2769
- line
2770
- )) {
2771
- continue;
2772
- }
2773
- if (/^\s*-\s*\*\*(Pre-PR Evidence|PR 전 리뷰 Evidence)\*\*\s*:/.test(
3073
+ if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태|PR Review|PR 리뷰|PR Review Evidence|PR 리뷰 Evidence)\*\*\s*:/.test(
2774
3074
  line
2775
3075
  )) {
2776
3076
  continue;
2777
3077
  }
2778
- if (/^\s*-\s*\*\*(Pre-PR Decision|PR Review Decision|PR 리뷰 Decision|PR 리뷰 Decision)\*\*\s*:/.test(
3078
+ if (/^\s*-\s*\*\*(PR Review Decision|PR 리뷰 Decision)\*\*\s*:/.test(
2779
3079
  line
2780
3080
  )) {
2781
3081
  continue;
@@ -2789,9 +3089,17 @@ function sanitizeTasksForLocal(content, lang) {
2789
3089
  if (/^\s*-\s*Example:\s*review note link/i.test(line)) continue;
2790
3090
  if (/^\s*-\s*사전 리뷰 최종 결과/.test(line)) continue;
2791
3091
  if (/^\s*-\s*예:\s*리뷰 노트 링크/.test(line)) continue;
3092
+ if (/^\s*-\s*PR creation requires/i.test(line)) continue;
3093
+ if (/^\s*-\s*PR 생성 전/.test(line)) continue;
3094
+ if (/agents\/skills\/create-pr\.md/.test(line)) continue;
3095
+ if (/^\s*-\s*Mark .*PR review handoff/i.test(line)) continue;
3096
+ if (/^\s*-\s*PR 리뷰 handoff/.test(line)) continue;
3097
+ if (/^\s*-\s*Record why\/how review comments/i.test(line)) continue;
3098
+ if (/^\s*-\s*리뷰 지적사항을/.test(line)) continue;
2792
3099
  filtered.push(line);
2793
3100
  }
2794
3101
  next = filtered.join("\n");
3102
+ next = lang === "ko" ? next.replaceAll("**PR \uC804 \uB9AC\uBDF0**", "**Feature \uB9AC\uBDF0**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Evidence**", "**Feature \uB9AC\uBDF0 Evidence**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Decision**", "**Feature \uB9AC\uBDF0 Decision**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Head**", "**Feature \uB9AC\uBDF0 Head**").replaceAll("**PR \uC804 \uB9AC\uBDF0 Tree**", "**Feature \uB9AC\uBDF0 Tree**").replaceAll("pre-PR \uB9AC\uBDF0 handoff", "Feature \uB9AC\uBDF0 handoff") : next.replaceAll("**Pre-PR Review**", "**Feature Review**").replaceAll("**Pre-PR Evidence**", "**Feature Review Evidence**").replaceAll("**Pre-PR Decision**", "**Feature Review Decision**").replaceAll("**Pre-PR Reviewed Head**", "**Feature Reviewed Head**").replaceAll("**Pre-PR Reviewed Tree**", "**Feature Reviewed Tree**").replaceAll("pre-PR review handoff", "Feature review handoff");
2795
3103
  next = next.replace(/feat\/\{issue-number\}-/g, "feat/").replace(/feat\/\{이슈번호\}-/g, "feat/").replace(/feat\/-/g, "feat/");
2796
3104
  return normalizeTrailingBlankLines(next);
2797
3105
  }
@@ -3798,52 +4106,120 @@ async function backfillMissingConfigDefaults(cwd, docsDir) {
3798
4106
  }
3799
4107
  }
3800
4108
  }
3801
- if (!isPlainObject(workflow.prePrReview)) {
3802
- workflow.prePrReview = {};
3803
- changedPaths.push("workflow.prePrReview");
4109
+ if (!isPlainObject(workflow.agentExecution)) {
4110
+ workflow.agentExecution = {};
4111
+ changedPaths.push("workflow.agentExecution");
4112
+ }
4113
+ const agentExecution = workflow.agentExecution;
4114
+ if (!isPlainObject(agentExecution.task)) {
4115
+ agentExecution.task = {};
4116
+ changedPaths.push("workflow.agentExecution.task");
3804
4117
  }
3805
- const prePrReview = workflow.prePrReview;
4118
+ const taskExecution = agentExecution.task;
4119
+ const defaultTaskExecution = createDefaultAgentExecutionTaskConfig();
3806
4120
  setIfMissing(
3807
- prePrReview,
3808
- "evidenceMode",
3809
- "path_required",
3810
- "workflow.prePrReview.evidenceMode"
3811
- );
3812
- if (prePrReview.evidenceMode !== void 0 && prePrReview.evidenceMode !== "path_required" && prePrReview.evidenceMode !== "any") {
3813
- prePrReview.evidenceMode = "path_required";
3814
- changedPaths.push("workflow.prePrReview.evidenceMode");
3815
- }
3816
- if ("findings" in prePrReview) {
3817
- delete prePrReview.findings;
3818
- changedPaths.push("workflow.prePrReview.findings");
3819
- }
3820
- const defaultReviewer = createDefaultPrePrReviewerConfig();
3821
- if (!isPlainObject(prePrReview.reviewer)) {
3822
- prePrReview.reviewer = defaultReviewer;
3823
- changedPaths.push("workflow.prePrReview.reviewer");
3824
- } else {
3825
- const reviewer = prePrReview.reviewer;
4121
+ taskExecution,
4122
+ "enabled",
4123
+ defaultTaskExecution.enabled,
4124
+ "workflow.agentExecution.task.enabled"
4125
+ );
4126
+ if (typeof taskExecution.enabled !== "boolean") {
4127
+ taskExecution.enabled = defaultTaskExecution.enabled;
4128
+ changedPaths.push("workflow.agentExecution.task.enabled");
4129
+ }
4130
+ if (taskExecution.type !== "subagent") {
4131
+ taskExecution.type = defaultTaskExecution.type;
4132
+ changedPaths.push("workflow.agentExecution.task.type");
4133
+ }
4134
+ if (typeof taskExecution.model !== "string" || !taskExecution.model.trim()) {
4135
+ taskExecution.model = defaultTaskExecution.model;
4136
+ changedPaths.push("workflow.agentExecution.task.model");
4137
+ } else if (taskExecution.model !== taskExecution.model.trim()) {
4138
+ taskExecution.model = taskExecution.model.trim();
4139
+ changedPaths.push("workflow.agentExecution.task.model");
4140
+ }
4141
+ if (typeof taskExecution.reasoningEffort !== "string" || !AGENT_REVIEW_REASONING_EFFORTS.includes(
4142
+ taskExecution.reasoningEffort
4143
+ )) {
4144
+ taskExecution.reasoningEffort = defaultTaskExecution.reasoningEffort;
4145
+ changedPaths.push("workflow.agentExecution.task.reasoningEffort");
4146
+ }
4147
+ if (taskExecution.onUnavailable !== "inherit" && taskExecution.onUnavailable !== "error") {
4148
+ taskExecution.onUnavailable = defaultTaskExecution.onUnavailable;
4149
+ changedPaths.push("workflow.agentExecution.task.onUnavailable");
4150
+ }
4151
+ const legacyPrePrReview = isPlainObject(workflow.prePrReview) ? { ...workflow.prePrReview } : null;
4152
+ if (!isPlainObject(workflow.agentReview)) {
4153
+ workflow.agentReview = {};
4154
+ changedPaths.push("workflow.agentReview");
4155
+ }
4156
+ const agentReview = workflow.agentReview;
4157
+ const normalizeAgentReviewPhase = (key, enabledDefault, legacySeed = null) => {
4158
+ const phasePath = `workflow.agentReview.${key}`;
4159
+ if (!isPlainObject(agentReview[key])) {
4160
+ agentReview[key] = legacySeed ? { ...legacySeed } : {};
4161
+ changedPaths.push(phasePath);
4162
+ }
4163
+ const phase = agentReview[key];
4164
+ setIfMissing(phase, "enabled", enabledDefault, `${phasePath}.enabled`);
4165
+ if (typeof phase.enabled !== "boolean") {
4166
+ phase.enabled = enabledDefault;
4167
+ changedPaths.push(`${phasePath}.enabled`);
4168
+ }
4169
+ setIfMissing(
4170
+ phase,
4171
+ "evidenceMode",
4172
+ "path_required",
4173
+ `${phasePath}.evidenceMode`
4174
+ );
4175
+ if (phase.evidenceMode !== "path_required" && phase.evidenceMode !== "any") {
4176
+ phase.evidenceMode = "path_required";
4177
+ changedPaths.push(`${phasePath}.evidenceMode`);
4178
+ }
4179
+ if ("findings" in phase) {
4180
+ delete phase.findings;
4181
+ changedPaths.push(`${phasePath}.findings`);
4182
+ }
4183
+ const defaultReviewer = createDefaultAgentReviewerConfig();
4184
+ if (!isPlainObject(phase.reviewer)) {
4185
+ phase.reviewer = defaultReviewer;
4186
+ changedPaths.push(`${phasePath}.reviewer`);
4187
+ return;
4188
+ }
4189
+ const reviewer = phase.reviewer;
3826
4190
  if (reviewer.type !== "subagent") {
3827
4191
  reviewer.type = defaultReviewer.type;
3828
- changedPaths.push("workflow.prePrReview.reviewer.type");
4192
+ changedPaths.push(`${phasePath}.reviewer.type`);
3829
4193
  }
3830
4194
  if (typeof reviewer.model !== "string" || !reviewer.model.trim()) {
3831
4195
  reviewer.model = defaultReviewer.model;
3832
- changedPaths.push("workflow.prePrReview.reviewer.model");
4196
+ changedPaths.push(`${phasePath}.reviewer.model`);
3833
4197
  } else if (reviewer.model !== reviewer.model.trim()) {
3834
4198
  reviewer.model = reviewer.model.trim();
3835
- changedPaths.push("workflow.prePrReview.reviewer.model");
4199
+ changedPaths.push(`${phasePath}.reviewer.model`);
3836
4200
  }
3837
- if (typeof reviewer.reasoningEffort !== "string" || !PRE_PR_REVIEW_REASONING_EFFORTS.includes(
4201
+ if (typeof reviewer.reasoningEffort !== "string" || !AGENT_REVIEW_REASONING_EFFORTS.includes(
3838
4202
  reviewer.reasoningEffort
3839
4203
  )) {
3840
4204
  reviewer.reasoningEffort = defaultReviewer.reasoningEffort;
3841
- changedPaths.push("workflow.prePrReview.reviewer.reasoningEffort");
4205
+ changedPaths.push(`${phasePath}.reviewer.reasoningEffort`);
3842
4206
  }
3843
4207
  if (reviewer.onUnavailable !== "inherit" && reviewer.onUnavailable !== "error") {
3844
4208
  reviewer.onUnavailable = defaultReviewer.onUnavailable;
3845
- changedPaths.push("workflow.prePrReview.reviewer.onUnavailable");
4209
+ changedPaths.push(`${phasePath}.reviewer.onUnavailable`);
3846
4210
  }
4211
+ };
4212
+ normalizeAgentReviewPhase("plan", true);
4213
+ normalizeAgentReviewPhase("task", false);
4214
+ const legacyFeatureEnabled = typeof legacyPrePrReview?.enabled === "boolean" ? legacyPrePrReview.enabled : workflow.mode !== "local";
4215
+ normalizeAgentReviewPhase(
4216
+ "feature",
4217
+ legacyFeatureEnabled,
4218
+ legacyPrePrReview
4219
+ );
4220
+ if (hasOwnKey2(workflow, "prePrReview")) {
4221
+ delete workflow.prePrReview;
4222
+ changedPaths.push("workflow.prePrReview");
3847
4223
  }
3848
4224
  if (!isPlainObject(raw.pr)) {
3849
4225
  raw.pr = {};
@@ -4980,7 +5356,7 @@ function extractTaskTitles(tasksContent) {
4980
5356
  return uniqItems(
4981
5357
  tasksContent.split("\n").map((line) => {
4982
5358
  const match = line.match(
4983
- /^\s*-\s*\[(?:TODO|DOING|DONE)\][^\n]*?\s+(?:T-[A-Z0-9-]+\s+)?(.+?)\s*$/
5359
+ /^\s*-\s*\[(?:TODO|DOING|REVIEW|DONE)\][^\n]*?\s+(?:T-[A-Z0-9-]+\s+)?(.+?)\s*$/
4984
5360
  );
4985
5361
  if (!match) return void 0;
4986
5362
  return sanitizeDraftItem(match[1]);
@@ -7120,7 +7496,11 @@ function formatTaskBlock(input) {
7120
7496
  " - Acceptance:",
7121
7497
  ...input.acceptanceItems.map((item) => ` - ${item}`),
7122
7498
  " - Checklist:",
7123
- ...input.checklistItems.map((item) => ` - [ ] ${item}`)
7499
+ ...input.checklistItems.map((item) => ` - [ ] ${item}`),
7500
+ " - Review Evidence: -",
7501
+ " - Review Decision: -",
7502
+ " - Reviewed Head: -",
7503
+ " - Reviewed Tree: -"
7124
7504
  ];
7125
7505
  }
7126
7506
  async function runTaskAdd(featureName, options) {
@@ -7548,7 +7928,7 @@ function registerCodexHooksIntegration(parent) {
7548
7928
  removeLeeSpecKitCodexHooks,
7549
7929
  resolveCodexHooksRepoRoot,
7550
7930
  upsertLeeSpecKitCodexHooks
7551
- } = await import('./hooks-373Z6JG2.js');
7931
+ } = await import('./hooks-67CU3GZC.js');
7552
7932
  const workflowRoot = config.docsRepo === "standalone" ? resolveConfiguredStandaloneWorkspaceRoot(config) : resolveCodexHooksRepoRoot(process.cwd());
7553
7933
  if (!workflowRoot) {
7554
7934
  throw createCliError(
@@ -7953,9 +8333,57 @@ var ISSUE_LABELS = ["Issue", "Issue Number", "\uC774\uC288", "\uC774\uC288 \uBC8
7953
8333
  var BRANCH_LABELS3 = ["Branch", "\uBE0C\uB79C\uCE58"];
7954
8334
  var PR_LABELS = ["PR", "Pull Request"];
7955
8335
  var PR_STATUS_LABELS = ["PR Status", "PR \uC0C1\uD0DC"];
7956
- var PRE_PR_REVIEW_LABELS = ["Pre-PR Review", "PR \uC804 \uB9AC\uBDF0"];
7957
- var PRE_PR_EVIDENCE_LABELS = ["Pre-PR Evidence", "PR \uC804 \uB9AC\uBDF0 Evidence"];
7958
- var PRE_PR_DECISION_LABELS = ["Pre-PR Decision", "PR \uC804 \uB9AC\uBDF0 Decision"];
8336
+ var PRE_PR_REVIEW_LABELS = [
8337
+ "Pre-PR Review",
8338
+ "PR \uC804 \uB9AC\uBDF0",
8339
+ "Feature Review",
8340
+ "Feature \uB9AC\uBDF0"
8341
+ ];
8342
+ var PRE_PR_EVIDENCE_LABELS = [
8343
+ "Pre-PR Evidence",
8344
+ "PR \uC804 \uB9AC\uBDF0 Evidence",
8345
+ "Feature Review Evidence",
8346
+ "Feature \uB9AC\uBDF0 Evidence"
8347
+ ];
8348
+ var PRE_PR_DECISION_LABELS = [
8349
+ "Pre-PR Decision",
8350
+ "PR \uC804 \uB9AC\uBDF0 Decision",
8351
+ "Feature Review Decision",
8352
+ "Feature \uB9AC\uBDF0 Decision"
8353
+ ];
8354
+ var PRE_PR_REVIEWED_HEAD_LABELS = [
8355
+ "Pre-PR Reviewed Head",
8356
+ "PR \uC804 \uB9AC\uBDF0 Head",
8357
+ "Feature Reviewed Head",
8358
+ "Feature \uB9AC\uBDF0 Head"
8359
+ ];
8360
+ var PRE_PR_REVIEWED_TREE_LABELS = [
8361
+ "Pre-PR Reviewed Tree",
8362
+ "PR \uC804 \uB9AC\uBDF0 Tree",
8363
+ "Feature Reviewed Tree",
8364
+ "Feature \uB9AC\uBDF0 Tree"
8365
+ ];
8366
+ var PLAN_REVIEW_STATUS_LABELS = ["Plan Review", "Plan \uAC80\uC218", "Plan \uB9AC\uBDF0"];
8367
+ var PLAN_REVIEW_EVIDENCE_LABELS = [
8368
+ "Plan Review Evidence",
8369
+ "Plan \uAC80\uC218 Evidence",
8370
+ "Plan \uB9AC\uBDF0 Evidence"
8371
+ ];
8372
+ var PLAN_REVIEW_DECISION_LABELS = [
8373
+ "Plan Review Decision",
8374
+ "Plan \uAC80\uC218 Decision",
8375
+ "Plan \uB9AC\uBDF0 Decision"
8376
+ ];
8377
+ var PLAN_REVIEWED_SPEC_HASH_LABELS = [
8378
+ "Plan Reviewed Spec Hash",
8379
+ "Plan \uAC80\uC218 Spec Hash",
8380
+ "Plan \uB9AC\uBDF0 Spec Hash"
8381
+ ];
8382
+ var PLAN_REVIEWED_PLAN_HASH_LABELS = [
8383
+ "Plan Reviewed Plan Hash",
8384
+ "Plan \uAC80\uC218 Plan Hash",
8385
+ "Plan \uB9AC\uBDF0 Plan Hash"
8386
+ ];
7959
8387
  var COMPLETION_MARKERS = {
7960
8388
  allTasks: "lee-spec-kit:completion:all-tasks",
7961
8389
  tests: "lee-spec-kit:completion:tests",
@@ -7974,7 +8402,10 @@ function resolveWorkflowRequirements(config) {
7974
8402
  requirePr: workflow.requirePr ?? !isLocalWorkflow,
7975
8403
  requireReview: workflow.requireReview ?? !isLocalWorkflow,
7976
8404
  requireMerge: workflow.requireMerge ?? !isLocalWorkflow,
7977
- prePrReviewEnabled: workflow.prePrReview?.enabled ?? !isLocalWorkflow
8405
+ planReviewEnabled: workflow.agentReview?.plan?.enabled ?? true,
8406
+ taskReviewEnabled: workflow.agentReview?.task?.enabled ?? false,
8407
+ featureReviewEnabled: workflow.agentReview?.feature?.enabled ?? workflow.prePrReview?.enabled ?? !isLocalWorkflow,
8408
+ taskExecutionEnabled: workflow.agentExecution?.task?.enabled ?? true
7978
8409
  };
7979
8410
  }
7980
8411
  function parseApprovalStatus(raw) {
@@ -8025,7 +8456,62 @@ function parseCompletionCheckbox(lines, marker, legacyPattern) {
8025
8456
  (line) => legacyPattern.test(line) && parseMarkdownCheckbox(line) === true
8026
8457
  );
8027
8458
  }
8028
- function parseTasksDoc(content) {
8459
+ function parseReviewDecisionOutcome(value) {
8460
+ const match = (value || "").trim().toLowerCase().match(/\b(approve|changes_requested|blocked)\b/);
8461
+ return match?.[1] || null;
8462
+ }
8463
+ function parseWorkflowTaskLine(line, index = -1) {
8464
+ const canonical = parseTaskLine(line, index);
8465
+ if (canonical) {
8466
+ return {
8467
+ index,
8468
+ raw: line,
8469
+ status: canonical.status,
8470
+ taskId: canonical.taskId,
8471
+ title: canonical.title
8472
+ };
8473
+ }
8474
+ const legacy = line.match(
8475
+ /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\](?:\[[^\]]+\])*\s+(?:(T-[A-Za-z0-9-]+)\s+)?(.+?)\s*$/i
8476
+ );
8477
+ if (!legacy) return null;
8478
+ return {
8479
+ index,
8480
+ raw: line,
8481
+ status: legacy[1].toUpperCase(),
8482
+ taskId: legacy[2] || null,
8483
+ title: legacy[3].trim()
8484
+ };
8485
+ }
8486
+ function extractTaskReviewValue(lines, startIndex, labels) {
8487
+ let endIndex = lines.length;
8488
+ for (let index = startIndex + 1; index < lines.length; index += 1) {
8489
+ if (parseWorkflowTaskLine(lines[index]) || /^\s*##\s+/.test(lines[index])) {
8490
+ endIndex = index;
8491
+ break;
8492
+ }
8493
+ }
8494
+ for (let index = startIndex + 1; index < endIndex; index += 1) {
8495
+ for (const label of labels) {
8496
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8497
+ const match = lines[index].match(
8498
+ new RegExp(
8499
+ `^\\s*-\\s*(?:\\*\\*)?${escaped}(?:\\*\\*)?:\\s*(.*?)\\s*$`,
8500
+ "i"
8501
+ )
8502
+ );
8503
+ if (match) return sanitizeMetadataValue2(match[1]);
8504
+ }
8505
+ }
8506
+ return null;
8507
+ }
8508
+ function buildSyntheticTaskId(feature, title, sameTitleOccurrence) {
8509
+ const fingerprint = createHash("sha256").update(
8510
+ `${feature.folderName}\0${normalizeCommitTopicText(title).toLowerCase()}\0${sameTitleOccurrence}`
8511
+ ).digest("hex").slice(0, 10);
8512
+ return `T-${feature.id}-legacy-${fingerprint}`;
8513
+ }
8514
+ function parseTasksDoc(content, feature) {
8029
8515
  const issueRaw = extractFieldValue2(content, ISSUE_LABELS);
8030
8516
  const issueNumberMatch = issueRaw?.match(/^#(\d+)$/);
8031
8517
  const issueNumber = issueNumberMatch ? Number(issueNumberMatch[1]) : null;
@@ -8034,15 +8520,43 @@ function parseTasksDoc(content) {
8034
8520
  const prePrDecision = extractFieldValue2(content, PRE_PR_DECISION_LABELS);
8035
8521
  const tasks = [];
8036
8522
  const nonCodeLines = withoutFencedCodeBlocks(content);
8037
- for (const line of nonCodeLines) {
8038
- const match = line.match(
8039
- /^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\](?:\[[^\]]+\])*\s+(.+?)\s*$/i
8523
+ const legacyTitleOccurrences = /* @__PURE__ */ new Map();
8524
+ for (let index = 0; index < nonCodeLines.length; index += 1) {
8525
+ const line = nonCodeLines[index];
8526
+ const parsed = parseWorkflowTaskLine(line, index);
8527
+ if (!parsed) continue;
8528
+ const reviewDecision = extractTaskReviewValue(
8529
+ nonCodeLines,
8530
+ index,
8531
+ ["Review Decision", "Task Review Decision", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Decision"]
8040
8532
  );
8041
- if (!match) continue;
8533
+ const legacyTitleKey = normalizeCommitTopicText(parsed.title).toLowerCase();
8534
+ const sameTitleOccurrence = (legacyTitleOccurrences.get(legacyTitleKey) || 0) + 1;
8535
+ legacyTitleOccurrences.set(legacyTitleKey, sameTitleOccurrence);
8536
+ const taskId = parsed.taskId || buildSyntheticTaskId(feature, parsed.title, sameTitleOccurrence);
8042
8537
  tasks.push({
8043
8538
  raw: line,
8044
- status: match[1].toUpperCase(),
8045
- title: match[2].trim()
8539
+ status: parsed.status,
8540
+ taskId,
8541
+ taskIdSource: parsed.taskId ? "document" : "synthetic",
8542
+ title: parsed.title,
8543
+ reviewEvidence: extractTaskReviewValue(
8544
+ nonCodeLines,
8545
+ index,
8546
+ ["Review Evidence", "Task Review Evidence", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Evidence"]
8547
+ ),
8548
+ reviewDecision,
8549
+ reviewDecisionOutcome: parseReviewDecisionOutcome(reviewDecision),
8550
+ reviewedHead: extractTaskReviewValue(
8551
+ nonCodeLines,
8552
+ index,
8553
+ ["Reviewed Head", "Task Reviewed Head", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Head"]
8554
+ ),
8555
+ reviewedTree: extractTaskReviewValue(
8556
+ nonCodeLines,
8557
+ index,
8558
+ ["Reviewed Tree", "Task Reviewed Tree", "\uD0DC\uC2A4\uD06C \uB9AC\uBDF0 Tree"]
8559
+ )
8046
8560
  });
8047
8561
  }
8048
8562
  const allTasksChecked = parseCompletionCheckbox(
@@ -8073,11 +8587,7 @@ function parseTasksDoc(content) {
8073
8587
  if (value === "done") return "done";
8074
8588
  return null;
8075
8589
  })();
8076
- const prePrDecisionOutcome = (() => {
8077
- const value = (prePrDecision || "").trim().toLowerCase();
8078
- const match = value.match(/\b(approve|changes_requested|blocked)\b/);
8079
- return match?.[1] || null;
8080
- })();
8590
+ const prePrDecisionOutcome = parseReviewDecisionOutcome(prePrDecision);
8081
8591
  return {
8082
8592
  docStatus: parseApprovalStatus(
8083
8593
  extractFieldValue2(content, DOC_STATUS_LABELS) || void 0
@@ -8092,6 +8602,12 @@ function parseTasksDoc(content) {
8092
8602
  ),
8093
8603
  prePrDecision: sanitizeMetadataValue2(prePrDecision),
8094
8604
  prePrDecisionOutcome,
8605
+ prePrReviewedHead: sanitizeMetadataValue2(
8606
+ extractFieldValue2(content, PRE_PR_REVIEWED_HEAD_LABELS)
8607
+ ),
8608
+ prePrReviewedTree: sanitizeMetadataValue2(
8609
+ extractFieldValue2(content, PRE_PR_REVIEWED_TREE_LABELS)
8610
+ ),
8095
8611
  tasks,
8096
8612
  completion: {
8097
8613
  allTasksChecked,
@@ -8106,6 +8622,58 @@ function sanitizeMetadataValue2(value) {
8106
8622
  if (!trimmed || trimmed === "-") return null;
8107
8623
  return trimmed;
8108
8624
  }
8625
+ function parsePlanReview(content) {
8626
+ const statusValue = sanitizeMetadataValue2(
8627
+ extractFieldValue2(content, PLAN_REVIEW_STATUS_LABELS)
8628
+ )?.toLowerCase();
8629
+ const status = statusValue === "pending" || statusValue === "running" || statusValue === "done" ? statusValue : null;
8630
+ const evidence = sanitizeMetadataValue2(
8631
+ extractFieldValue2(content, PLAN_REVIEW_EVIDENCE_LABELS)
8632
+ );
8633
+ const decision = sanitizeMetadataValue2(
8634
+ extractFieldValue2(content, PLAN_REVIEW_DECISION_LABELS)
8635
+ );
8636
+ const reviewedSpecHash = sanitizeMetadataValue2(
8637
+ extractFieldValue2(content, PLAN_REVIEWED_SPEC_HASH_LABELS)
8638
+ );
8639
+ const reviewedPlanHash = sanitizeMetadataValue2(
8640
+ extractFieldValue2(content, PLAN_REVIEWED_PLAN_HASH_LABELS)
8641
+ );
8642
+ return {
8643
+ status,
8644
+ evidence,
8645
+ decision,
8646
+ decisionOutcome: parseReviewDecisionOutcome(decision),
8647
+ reviewedSpecHash,
8648
+ reviewedPlanHash,
8649
+ hasMetadata: PLAN_REVIEW_STATUS_LABELS.some((label) => {
8650
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8651
+ return new RegExp(`^\\s*-\\s*\\*\\*${escaped}\\*\\*:`, "mi").test(content);
8652
+ })
8653
+ };
8654
+ }
8655
+ function buildReviewDocumentHash(content, excludedLabels) {
8656
+ const excluded = new Set(
8657
+ [...excludedLabels, "Status", "\uC0C1\uD0DC"].map((label) => label.toLowerCase())
8658
+ );
8659
+ const normalized = content.replace(/\r\n/g, "\n").split("\n").filter((line) => {
8660
+ const match = line.match(/^\s*-\s*\*\*(.+?)\*\*\s*:/);
8661
+ return !match || !excluded.has(match[1].trim().toLowerCase());
8662
+ }).join("\n").trim();
8663
+ return createHash("sha256").update(normalized).digest("hex");
8664
+ }
8665
+ function buildPlanReviewTarget(specContent, planContent) {
8666
+ return {
8667
+ specHash: buildReviewDocumentHash(specContent, []),
8668
+ planHash: buildReviewDocumentHash(planContent, [
8669
+ ...PLAN_REVIEW_STATUS_LABELS,
8670
+ ...PLAN_REVIEW_EVIDENCE_LABELS,
8671
+ ...PLAN_REVIEW_DECISION_LABELS,
8672
+ ...PLAN_REVIEWED_SPEC_HASH_LABELS,
8673
+ ...PLAN_REVIEWED_PLAN_HASH_LABELS
8674
+ ])
8675
+ };
8676
+ }
8109
8677
  function normalizeCommitTopicText(value) {
8110
8678
  return value.replace(/\s+/g, " ").trim();
8111
8679
  }
@@ -8241,7 +8809,7 @@ function hasOpenTask(tasks) {
8241
8809
  function hasUncommittedChanges(gitCwd) {
8242
8810
  if (!gitCwd) return false;
8243
8811
  const status = runGitCapture(
8244
- ["status", "--porcelain", "--untracked-files=no"],
8812
+ ["status", "--porcelain", "--untracked-files=normal"],
8245
8813
  gitCwd
8246
8814
  ) || "";
8247
8815
  return status.trim().length > 0;
@@ -8305,7 +8873,7 @@ function resolveProjectCommitTopic(feature, tasks) {
8305
8873
  const activeTask = tasks.tasks.find(
8306
8874
  (task) => task.status === "DOING" || task.status === "REVIEW"
8307
8875
  );
8308
- const raw = activeTask?.title || getLastDoneTask(tasks)?.title || nextTodoTask(tasks)?.title || feature.folderName;
8876
+ const raw = activeTask?.title || getLastDoneTask(tasks)?.title || tasks.tasks.find((task) => task.status === "TODO")?.title || feature.folderName;
8309
8877
  const withoutTaskId = normalizeCommitTopicText(raw || "").replace(
8310
8878
  /^T-[A-Za-z0-9-]+\s+/,
8311
8879
  ""
@@ -8362,6 +8930,19 @@ function resolveExpectedBranch(feature, tasks) {
8362
8930
  if (!tasks.issueNumber) return null;
8363
8931
  return `feat/${tasks.issueNumber}-${feature.slug}`;
8364
8932
  }
8933
+ function buildExpectedBranchCommand(config, feature, expectedBranch, requireWorktree) {
8934
+ if (requireWorktree) {
8935
+ return buildManagedWorktreeCreateCommand(
8936
+ config,
8937
+ feature.git.projectGitCwd,
8938
+ expectedBranch
8939
+ );
8940
+ }
8941
+ return localBranchExists(
8942
+ resolveProjectRootGitCwd(config, feature),
8943
+ expectedBranch
8944
+ ) ? `git checkout ${expectedBranch}` : `git checkout -b ${expectedBranch}`;
8945
+ }
8365
8946
  function resolveProjectRootFromGitCwd2(projectGitCwd) {
8366
8947
  return runGitCapture(["rev-parse", "--show-toplevel"], projectGitCwd) || path8.resolve(projectGitCwd);
8367
8948
  }
@@ -8526,19 +9107,96 @@ function buildPostMergeCleanupSummary(state) {
8526
9107
  }
8527
9108
  return `Finish the post-merge cleanup before closing the feature: ${remaining.join(", ")}.`;
8528
9109
  }
8529
- function nextTodoTask(tasks) {
8530
- return tasks.tasks.find((task) => task.status === "DOING") || tasks.tasks.find((task) => task.status === "TODO") || null;
9110
+ function nextExecutableTask(tasks) {
9111
+ return tasks.tasks.find((task) => task.status === "DOING") || tasks.tasks.find((task) => task.status === "REVIEW") || tasks.tasks.find((task) => task.status === "TODO") || tasks.tasks.find((task) => task.status !== "DONE");
8531
9112
  }
8532
9113
  function allTasksDone(tasks) {
8533
9114
  return tasks.tasks.length > 0 && tasks.tasks.every((task) => task.status === "DONE");
8534
9115
  }
8535
- function prePrSatisfied(config, feature, tasks) {
8536
- return tasks.prePrReviewStatus === "done" && isPrePrEvidenceSatisfied({
9116
+ function resolveAgentReviewPhase(config, scope) {
9117
+ if (scope === "plan") {
9118
+ return config.workflow?.agentReview?.plan || {};
9119
+ }
9120
+ if (scope === "task") {
9121
+ return config.workflow?.agentReview?.task || {};
9122
+ }
9123
+ return config.workflow?.agentReview?.feature || config.workflow?.prePrReview || {};
9124
+ }
9125
+ function resolveProjectReviewTarget(config, projectGitCwd, scope, taskBase = null) {
9126
+ const pathArgs = ["--", "."];
9127
+ const relativeDocsDir = path8.relative(projectGitCwd, config.docsDir);
9128
+ const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
9129
+ if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
9130
+ pathArgs.push(`:(exclude)${normalizedDocsDir}/**`);
9131
+ }
9132
+ const targetSha = runGitCapture(["log", "-n", "1", "--pretty=%H", ...pathArgs], projectGitCwd) || runGitCapture(["rev-parse", "HEAD"], projectGitCwd) || "";
9133
+ if (!targetSha) return null;
9134
+ const targetTree = runGitCapture(["rev-parse", `${targetSha}^{tree}`], projectGitCwd) || "";
9135
+ if (!targetTree) return null;
9136
+ let baseSha = "";
9137
+ if (scope === "task" && taskBase) {
9138
+ const actualBaseTree = runGitCapture(
9139
+ ["rev-parse", `${taskBase.reviewedHead}^{tree}`],
9140
+ projectGitCwd
9141
+ ) || "";
9142
+ if (actualBaseTree === taskBase.reviewedTree && taskBase.reviewedHead !== targetSha && isAncestor(projectGitCwd, taskBase.reviewedHead, targetSha)) {
9143
+ baseSha = taskBase.reviewedHead;
9144
+ }
9145
+ }
9146
+ if (!baseSha) {
9147
+ const baseBranch = config.workflow?.baseBranch?.trim() || "main";
9148
+ for (const candidate of [`origin/${baseBranch}`, baseBranch]) {
9149
+ const candidateBase = runGitCapture(["merge-base", candidate, targetSha], projectGitCwd) || "";
9150
+ if (candidateBase && candidateBase !== targetSha) {
9151
+ baseSha = candidateBase;
9152
+ break;
9153
+ }
9154
+ }
9155
+ }
9156
+ if (!baseSha) {
9157
+ baseSha = runGitCapture(["rev-parse", `${targetSha}^`], projectGitCwd) || targetSha;
9158
+ }
9159
+ return { baseSha, targetSha, targetTree };
9160
+ }
9161
+ function reviewEvidenceSatisfied(config, feature, scope, evidence) {
9162
+ return isPrePrEvidenceSatisfied({
8537
9163
  docsDir: config.docsDir,
8538
9164
  featureDir: feature.path,
8539
- evidence: tasks.prePrEvidence,
8540
- evidenceMode: config.workflow?.prePrReview?.evidenceMode
8541
- }) && !!tasks.prePrDecision && tasks.prePrDecisionOutcome === "approve";
9165
+ evidence,
9166
+ evidenceMode: resolveAgentReviewPhase(config, scope).evidenceMode
9167
+ });
9168
+ }
9169
+ function planReviewSatisfied(config, feature, review, target) {
9170
+ return review.status === "done" && reviewEvidenceSatisfied(config, feature, "plan", review.evidence) && review.decisionOutcome === "approve" && review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
9171
+ }
9172
+ function featureReviewSatisfied(config, feature, tasks, target) {
9173
+ return !!target && tasks.prePrReviewStatus === "done" && reviewEvidenceSatisfied(config, feature, "feature", tasks.prePrEvidence) && !!tasks.prePrDecision && tasks.prePrDecisionOutcome === "approve" && tasks.prePrReviewedHead === target.targetSha && tasks.prePrReviewedTree === target.targetTree;
9174
+ }
9175
+ function taskReviewEvidenceSatisfied(config, feature, task, target) {
9176
+ return !!target && reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence) && task.reviewDecisionOutcome === "approve" && task.reviewedHead === target.targetSha && task.reviewedTree === target.targetTree;
9177
+ }
9178
+ function recordedTaskReviewSatisfied(config, feature, projectGitCwd, task, currentTargetSha) {
9179
+ if (!currentTargetSha || !task.reviewedHead || !task.reviewedTree || task.reviewDecisionOutcome !== "approve" || !reviewEvidenceSatisfied(config, feature, "task", task.reviewEvidence)) {
9180
+ return false;
9181
+ }
9182
+ const actualTree = runGitCapture(
9183
+ ["rev-parse", `${task.reviewedHead}^{tree}`],
9184
+ projectGitCwd
9185
+ ) || "";
9186
+ return actualTree === task.reviewedTree && isAncestor(projectGitCwd, task.reviewedHead, currentTargetSha);
9187
+ }
9188
+ function resolvePreviousTaskReviewBase(config, feature, tasks, task) {
9189
+ const taskIndex = tasks.tasks.indexOf(task);
9190
+ for (let index = taskIndex - 1; index >= 0; index -= 1) {
9191
+ const previous = tasks.tasks[index];
9192
+ if (previous.reviewDecisionOutcome === "approve" && previous.reviewedHead && previous.reviewedTree && reviewEvidenceSatisfied(config, feature, "task", previous.reviewEvidence)) {
9193
+ return {
9194
+ reviewedHead: previous.reviewedHead,
9195
+ reviewedTree: previous.reviewedTree
9196
+ };
9197
+ }
9198
+ }
9199
+ return null;
8542
9200
  }
8543
9201
  function issueExistsRemotely(issueNumber, feature) {
8544
9202
  if (!issueNumber) return false;
@@ -8558,25 +9216,26 @@ function prExistsRemotely(prRef, feature) {
8558
9216
  );
8559
9217
  return result.code === 0;
8560
9218
  }
8561
- function buildAction(category, summary, approvalRequired, command = null, reviewer) {
9219
+ function buildAction(category, summary, approvalRequired, command = null, agent, actionContext) {
8562
9220
  return {
8563
9221
  category,
8564
9222
  summary,
8565
9223
  approvalRequired,
8566
9224
  command,
8567
- ...reviewer ? {
8568
- executor: reviewer.type,
8569
- model: reviewer.model,
8570
- reasoningEffort: reviewer.reasoningEffort,
8571
- onUnavailable: reviewer.onUnavailable
8572
- } : {}
9225
+ ...agent ? {
9226
+ executor: agent.type,
9227
+ model: agent.model,
9228
+ reasoningEffort: agent.reasoningEffort,
9229
+ onUnavailable: agent.onUnavailable
9230
+ } : {},
9231
+ ...actionContext || {}
8573
9232
  };
8574
9233
  }
8575
- function resolvePrePrReviewer(config) {
8576
- const defaults = createDefaultPrePrReviewerConfig();
8577
- const configured = config.workflow?.prePrReview?.reviewer;
9234
+ function resolveAgentReviewer(config, scope) {
9235
+ const defaults = createDefaultAgentReviewerConfig();
9236
+ const configured = resolveAgentReviewPhase(config, scope).reviewer;
8578
9237
  const model = typeof configured?.model === "string" && configured.model.trim() ? configured.model.trim() : defaults.model;
8579
- const reasoningEffort = PRE_PR_REVIEW_REASONING_EFFORTS.includes(
9238
+ const reasoningEffort = AGENT_REVIEW_REASONING_EFFORTS.includes(
8580
9239
  configured?.reasoningEffort
8581
9240
  ) ? configured?.reasoningEffort : defaults.reasoningEffort;
8582
9241
  return {
@@ -8586,6 +9245,37 @@ function resolvePrePrReviewer(config) {
8586
9245
  onUnavailable: configured?.onUnavailable === "error" ? "error" : defaults.onUnavailable
8587
9246
  };
8588
9247
  }
9248
+ function resolveTaskExecutor(config) {
9249
+ const defaults = createDefaultAgentExecutionTaskConfig();
9250
+ const configured = config.workflow?.agentExecution?.task;
9251
+ const model = typeof configured?.model === "string" && configured.model.trim() ? configured.model.trim() : defaults.model;
9252
+ const reasoningEffort = AGENT_REVIEW_REASONING_EFFORTS.includes(
9253
+ configured?.reasoningEffort
9254
+ ) ? configured?.reasoningEffort : defaults.reasoningEffort;
9255
+ return {
9256
+ type: "subagent",
9257
+ model,
9258
+ reasoningEffort,
9259
+ onUnavailable: configured?.onUnavailable === "error" ? "error" : defaults.onUnavailable
9260
+ };
9261
+ }
9262
+ function createTaskWorkerContract() {
9263
+ return {
9264
+ role: "task_implementation_worker",
9265
+ executeDirectly: true,
9266
+ spawnSubagents: false,
9267
+ runWorkflowStage: false,
9268
+ editProjectCode: true,
9269
+ runTaskScopedVerification: true,
9270
+ followVerificationContract: true,
9271
+ addUnplannedDurableTests: false,
9272
+ editDocs: false,
9273
+ changeTaskState: false,
9274
+ commit: false,
9275
+ requestApproval: false,
9276
+ remoteActions: false
9277
+ };
9278
+ }
8589
9279
  function buildStageOption(label, reply, category, summary, command = null) {
8590
9280
  return {
8591
9281
  label,
@@ -8893,6 +9583,73 @@ function resolveFeatureSelectionError(selection) {
8893
9583
  blockedReasonCode: null
8894
9584
  };
8895
9585
  }
9586
+ function resolvePlanReviewPayload(config, feature, review, target) {
9587
+ const targetMatches = review.reviewedSpecHash === target.specHash && review.reviewedPlanHash === target.planHash;
9588
+ const actionContext = {
9589
+ reviewScope: "plan",
9590
+ specHash: target.specHash,
9591
+ planHash: target.planHash,
9592
+ docsDirectory: config.docsDir
9593
+ };
9594
+ if (targetMatches && review.decisionOutcome === "changes_requested") {
9595
+ return {
9596
+ status: "ok",
9597
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9598
+ docsDir: config.docsDir,
9599
+ featureRef: buildFeatureRef(feature),
9600
+ stage: "plan_review_fix",
9601
+ nextAction: buildAction(
9602
+ "plan_review_fix",
9603
+ "Address the independent Plan review findings in spec.md or plan.md, keep implementation blocked, and request a fresh review for the resulting document hashes.",
9604
+ false,
9605
+ null,
9606
+ void 0,
9607
+ actionContext
9608
+ ),
9609
+ approvalRequired: false,
9610
+ implementationAllowed: false,
9611
+ blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
9612
+ };
9613
+ }
9614
+ if (targetMatches && review.decisionOutcome === "blocked") {
9615
+ return {
9616
+ status: "ok",
9617
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9618
+ docsDir: config.docsDir,
9619
+ featureRef: buildFeatureRef(feature),
9620
+ stage: "plan_review",
9621
+ nextAction: buildAction(
9622
+ "plan_review",
9623
+ "Resolve the recorded Plan review blocker before Plan approval.",
9624
+ false,
9625
+ null,
9626
+ void 0,
9627
+ actionContext
9628
+ ),
9629
+ approvalRequired: false,
9630
+ implementationAllowed: false,
9631
+ blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
9632
+ };
9633
+ }
9634
+ return {
9635
+ status: "ok",
9636
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9637
+ docsDir: config.docsDir,
9638
+ featureRef: buildFeatureRef(feature),
9639
+ stage: "plan_review",
9640
+ nextAction: buildAction(
9641
+ "plan_review",
9642
+ "Delegate a fresh read-only review of spec.md and plan.md. Verify the Verification Contract, NONE/UPDATE/ADD decisions, requirement coverage, independent oracles, stable observation boundaries, realistic failure/rollback cases, exclusions, and focused/full verification scope. Record Plan Review status, evidence, decision, reviewer metadata, Reviewed Spec Hash, and Reviewed Plan Hash without modifying the documents.",
9643
+ false,
9644
+ null,
9645
+ resolveAgentReviewer(config, "plan"),
9646
+ actionContext
9647
+ ),
9648
+ approvalRequired: false,
9649
+ implementationAllowed: false,
9650
+ blockedReasonCode: "PLAN_REVIEW_NOT_APPROVED"
9651
+ };
9652
+ }
8896
9653
  async function collectWorkflowStage(cwd, selector, component) {
8897
9654
  const config = await getConfig(cwd);
8898
9655
  if (!config) {
@@ -8927,7 +9684,13 @@ async function collectWorkflowStage(cwd, selector, component) {
8927
9684
  const planStatus = parseApprovalStatus(
8928
9685
  extractFieldValue2(planContent || "", ["Status", "\uC0C1\uD0DC"]) || void 0
8929
9686
  );
8930
- const tasks = parseTasksDoc(tasksContent || "");
9687
+ const tasks = parseTasksDoc(tasksContent || "", feature);
9688
+ const planReview = parsePlanReview(planContent || "");
9689
+ const planReviewTarget = buildPlanReviewTarget(
9690
+ specContent || "",
9691
+ planContent || ""
9692
+ );
9693
+ const enforcePlanReview = requirements.planReviewEnabled && (tasks.docStatus !== "approved" || planReview.hasMetadata);
8931
9694
  const issueDraft = parseWorkflowDraftMetadataExtended(issueContent || "");
8932
9695
  const prDraft = parseWorkflowDraftMetadataExtended(prContent || "");
8933
9696
  const remoteReviewState = requirements.requireReview && tasks.prLink ? resolveRemotePrReviewState(tasks.prLink, feature) : "unknown";
@@ -8963,6 +9726,14 @@ async function collectWorkflowStage(cwd, selector, component) {
8963
9726
  }
8964
9727
  if (planStatus !== "approved") {
8965
9728
  const isReviewStage = planStatus === "review";
9729
+ if (isReviewStage && enforcePlanReview && !planReviewSatisfied(config, feature, planReview, planReviewTarget)) {
9730
+ return resolvePlanReviewPayload(
9731
+ config,
9732
+ feature,
9733
+ planReview,
9734
+ planReviewTarget
9735
+ );
9736
+ }
8966
9737
  const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "plan_approve", true) : false;
8967
9738
  const actionOptions = approvalRequired ? buildApprovalActionOptions({
8968
9739
  approveSummary: "Approve plan.md and continue to the tasks stage.",
@@ -8986,6 +9757,9 @@ async function collectWorkflowStage(cwd, selector, component) {
8986
9757
  blockedReasonCode: "PLAN_NOT_APPROVED"
8987
9758
  };
8988
9759
  }
9760
+ if (enforcePlanReview && !planReviewSatisfied(config, feature, planReview, planReviewTarget)) {
9761
+ return resolvePlanReviewPayload(config, feature, planReview, planReviewTarget);
9762
+ }
8989
9763
  if (tasks.tasks.length === 0 || tasks.docStatus !== "approved") {
8990
9764
  const isReviewStage = tasks.docStatus === "review";
8991
9765
  const approvalRequired = isReviewStage ? resolveActionApprovalRequired(config, "tasks_approve", true) : false;
@@ -9064,7 +9838,12 @@ async function collectWorkflowStage(cwd, selector, component) {
9064
9838
  const expectedBranch = resolveExpectedBranch(feature, tasks);
9065
9839
  const currentBranch = runGitCapture(["branch", "--show-current"], effectiveProjectGitCwd) || runGitCapture(["rev-parse", "--abbrev-ref", "HEAD"], effectiveProjectGitCwd) || null;
9066
9840
  if (expectedBranch && currentBranch !== expectedBranch) {
9067
- const branchCommand = requirements.requireWorktree ? buildManagedWorktreeCreateCommand(config, feature.git.projectGitCwd, expectedBranch) : `git checkout -b ${expectedBranch}`;
9841
+ const branchCommand = buildExpectedBranchCommand(
9842
+ config,
9843
+ feature,
9844
+ expectedBranch,
9845
+ requirements.requireWorktree
9846
+ );
9068
9847
  return {
9069
9848
  status: "ok",
9070
9849
  reasonCode: "WORKFLOW_STAGE_RESOLVED",
@@ -9087,6 +9866,151 @@ async function collectWorkflowStage(cwd, selector, component) {
9087
9866
  const lastDoneTask = getLastDoneTask(tasks);
9088
9867
  const docsDirty = hasUncommittedChanges(feature.git.docsGitCwd);
9089
9868
  const projectDirty = hasUncommittedChanges(effectiveProjectGitCwd);
9869
+ const currentTaskReviewTip = requirements.taskReviewEnabled ? resolveProjectReviewTarget(config, effectiveProjectGitCwd, "task") : null;
9870
+ const unreviewedDoneTask = requirements.taskReviewEnabled ? tasks.tasks.find(
9871
+ (task) => task.status === "DONE" && !recordedTaskReviewSatisfied(
9872
+ config,
9873
+ feature,
9874
+ effectiveProjectGitCwd,
9875
+ task,
9876
+ currentTaskReviewTip?.targetSha || null
9877
+ )
9878
+ ) || null : null;
9879
+ const reviewTask = unreviewedDoneTask || tasks.tasks.find((task) => task.status === "REVIEW") || null;
9880
+ if (requirements.taskReviewEnabled && reviewTask) {
9881
+ if (docsDirty || projectDirty) {
9882
+ return {
9883
+ status: "ok",
9884
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9885
+ docsDir: config.docsDir,
9886
+ featureRef: buildFeatureRef(feature),
9887
+ stage: "task_commit",
9888
+ nextAction: buildAction(
9889
+ "task_commit",
9890
+ buildTaskCommitSummary({
9891
+ feature,
9892
+ tasks,
9893
+ effectiveProjectGitCwd,
9894
+ docsDirty,
9895
+ projectDirty,
9896
+ gateFailureReason: "the task must have a clean checkpoint commit before independent review"
9897
+ }),
9898
+ false
9899
+ ),
9900
+ approvalRequired: false,
9901
+ implementationAllowed: false,
9902
+ blockedReasonCode: "TASK_COMMIT_REQUIRED"
9903
+ };
9904
+ }
9905
+ const reviewTarget = resolveProjectReviewTarget(
9906
+ config,
9907
+ effectiveProjectGitCwd,
9908
+ "task",
9909
+ resolvePreviousTaskReviewBase(config, feature, tasks, reviewTask)
9910
+ );
9911
+ if (!reviewTarget) {
9912
+ return {
9913
+ status: "ok",
9914
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9915
+ docsDir: config.docsDir,
9916
+ featureRef: buildFeatureRef(feature),
9917
+ stage: "task_commit",
9918
+ nextAction: buildAction(
9919
+ "task_commit",
9920
+ "Create a project commit for the task before requesting independent review.",
9921
+ false
9922
+ ),
9923
+ approvalRequired: false,
9924
+ implementationAllowed: false,
9925
+ blockedReasonCode: "TASK_COMMIT_REQUIRED"
9926
+ };
9927
+ }
9928
+ const reviewContext = {
9929
+ reviewScope: "task",
9930
+ ...reviewTask.taskId ? { taskId: reviewTask.taskId } : {},
9931
+ ...reviewTarget
9932
+ };
9933
+ const evidenceMatchesTarget = reviewTask.reviewedHead === reviewTarget.targetSha && reviewTask.reviewedTree === reviewTarget.targetTree;
9934
+ if (evidenceMatchesTarget && reviewTask.reviewDecisionOutcome === "changes_requested") {
9935
+ return {
9936
+ status: "ok",
9937
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9938
+ docsDir: config.docsDir,
9939
+ featureRef: buildFeatureRef(feature),
9940
+ stage: "task_review_fix",
9941
+ nextAction: buildAction(
9942
+ "task_review_fix",
9943
+ `Address the independent review findings for ${reviewTask.taskId || reviewTask.title}, return the task to REVIEW, commit the new code tip, and request a fresh review.`,
9944
+ false,
9945
+ null,
9946
+ void 0,
9947
+ reviewContext
9948
+ ),
9949
+ approvalRequired: false,
9950
+ implementationAllowed: true,
9951
+ blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
9952
+ };
9953
+ }
9954
+ if (evidenceMatchesTarget && reviewTask.reviewDecisionOutcome === "blocked") {
9955
+ return {
9956
+ status: "ok",
9957
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9958
+ docsDir: config.docsDir,
9959
+ featureRef: buildFeatureRef(feature),
9960
+ stage: "task_review",
9961
+ nextAction: buildAction(
9962
+ "task_review",
9963
+ `Resolve the recorded review blocker for ${reviewTask.taskId || reviewTask.title} before continuing.`,
9964
+ false,
9965
+ null,
9966
+ void 0,
9967
+ reviewContext
9968
+ ),
9969
+ approvalRequired: false,
9970
+ implementationAllowed: false,
9971
+ blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
9972
+ };
9973
+ }
9974
+ if (taskReviewEvidenceSatisfied(config, feature, reviewTask, reviewTarget)) {
9975
+ const completionSummary = 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.`;
9976
+ return {
9977
+ status: "ok",
9978
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9979
+ docsDir: config.docsDir,
9980
+ featureRef: buildFeatureRef(feature),
9981
+ stage: "task_review",
9982
+ nextAction: buildAction(
9983
+ "task_review_complete",
9984
+ completionSummary,
9985
+ false,
9986
+ null,
9987
+ void 0,
9988
+ reviewContext
9989
+ ),
9990
+ approvalRequired: false,
9991
+ implementationAllowed: false,
9992
+ blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
9993
+ };
9994
+ }
9995
+ return {
9996
+ status: "ok",
9997
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
9998
+ docsDir: config.docsDir,
9999
+ featureRef: buildFeatureRef(feature),
10000
+ stage: "task_review",
10001
+ nextAction: buildAction(
10002
+ "task_review",
10003
+ `Delegate a fresh read-only review of ${reviewTask.taskId || reviewTask.title} for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record evidence, decision, reviewer metadata, Reviewed Head, and Reviewed Tree without modifying code.`,
10004
+ false,
10005
+ null,
10006
+ resolveAgentReviewer(config, "task"),
10007
+ reviewContext
10008
+ ),
10009
+ approvalRequired: false,
10010
+ implementationAllowed: false,
10011
+ blockedReasonCode: "TASK_REVIEW_NOT_APPROVED"
10012
+ };
10013
+ }
9090
10014
  const pendingDoneTransitions = countPendingDoneTransitions(feature) || 0;
9091
10015
  const taskCommitCheckpointRequired = !activeTaskOpen && !!lastDoneTask && (projectDirty || pendingDoneTransitions > 0);
9092
10016
  if (taskCommitCheckpointRequired) {
@@ -9116,8 +10040,135 @@ async function collectWorkflowStage(cwd, selector, component) {
9116
10040
  }
9117
10041
  const committedTaskGate = taskCommitGatePolicy !== "off" && lastDoneTask ? checkTaskCommitGate(feature, effectiveProjectGitCwd, lastDoneTask) : { pass: true };
9118
10042
  const committedTaskGateRequiresCheckpoint = taskCommitGatePolicy === "strict" || committedTaskGate.reason === "DONE_TRANSITIONS_COUNT";
10043
+ if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
10044
+ return {
10045
+ status: "ok",
10046
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
10047
+ docsDir: config.docsDir,
10048
+ featureRef: buildFeatureRef(feature),
10049
+ stage: "task_commit",
10050
+ nextAction: buildAction(
10051
+ "task_commit",
10052
+ buildTaskCommitSummary({
10053
+ feature,
10054
+ tasks,
10055
+ effectiveProjectGitCwd,
10056
+ docsDirty,
10057
+ projectDirty,
10058
+ gateFailureReason: describeTaskCommitGateFailure(committedTaskGate)
10059
+ }),
10060
+ false
10061
+ ),
10062
+ approvalRequired: false,
10063
+ implementationAllowed: false,
10064
+ blockedReasonCode: "TASK_COMMIT_REQUIRED"
10065
+ };
10066
+ }
10067
+ let resolvedLocalState = null;
10068
+ if (allTasksDone(tasks) && config.workflow?.mode === "local" && resolveLocalCompletionStrategy(config) !== "none") {
10069
+ resolvedLocalState = await resolveLocalIntegrationContext(config, feature);
10070
+ }
10071
+ const remoteReviewAlreadyComplete = currentReviewState === "merged" && tasks.prStatus === "approved" && prDraft.prStatus === "approved";
10072
+ const featureReviewNeededForLifecycle = allTasksDone(tasks) && requirements.featureReviewEnabled && !resolvedLocalState?.integrationComplete && !remoteReviewAlreadyComplete;
10073
+ if (featureReviewNeededForLifecycle) {
10074
+ const expectedBranch = resolveExpectedBranch(feature, tasks);
10075
+ const currentBranch = runGitCapture(["branch", "--show-current"], effectiveProjectGitCwd) || runGitCapture(
10076
+ ["rev-parse", "--abbrev-ref", "HEAD"],
10077
+ effectiveProjectGitCwd
10078
+ ) || null;
10079
+ if (requirements.requireBranch && expectedBranch && currentBranch !== expectedBranch) {
10080
+ return {
10081
+ status: "ok",
10082
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
10083
+ docsDir: config.docsDir,
10084
+ featureRef: buildFeatureRef(feature),
10085
+ stage: "branch",
10086
+ nextAction: buildAction(
10087
+ "branch_create",
10088
+ requirements.requireWorktree ? `Restore or create the managed worktree for ${expectedBranch} before Feature review.` : `Switch back to ${expectedBranch} before Feature review.`,
10089
+ false,
10090
+ buildExpectedBranchCommand(
10091
+ config,
10092
+ feature,
10093
+ expectedBranch,
10094
+ requirements.requireWorktree
10095
+ )
10096
+ ),
10097
+ approvalRequired: false,
10098
+ implementationAllowed: false,
10099
+ blockedReasonCode: "BRANCH_NOT_READY"
10100
+ };
10101
+ }
10102
+ const reviewTarget = resolveProjectReviewTarget(
10103
+ config,
10104
+ effectiveProjectGitCwd,
10105
+ "feature"
10106
+ );
10107
+ const reviewContext = reviewTarget ? { reviewScope: "feature", ...reviewTarget } : null;
10108
+ const evidenceMatchesTarget = !!reviewTarget && tasks.prePrReviewedHead === reviewTarget.targetSha && tasks.prePrReviewedTree === reviewTarget.targetTree;
10109
+ if (reviewContext && evidenceMatchesTarget && tasks.prePrDecisionOutcome === "changes_requested") {
10110
+ return {
10111
+ status: "ok",
10112
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
10113
+ docsDir: config.docsDir,
10114
+ featureRef: buildFeatureRef(feature),
10115
+ stage: "feature_review_fix",
10116
+ nextAction: buildAction(
10117
+ "feature_review_fix",
10118
+ "Address the Feature review findings, commit the remediation, and request a fresh review of the new code tree before implementation approval.",
10119
+ false,
10120
+ null,
10121
+ void 0,
10122
+ reviewContext
10123
+ ),
10124
+ approvalRequired: false,
10125
+ implementationAllowed: true,
10126
+ blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
10127
+ };
10128
+ }
10129
+ if (reviewContext && evidenceMatchesTarget && tasks.prePrDecisionOutcome === "blocked") {
10130
+ return {
10131
+ status: "ok",
10132
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
10133
+ docsDir: config.docsDir,
10134
+ featureRef: buildFeatureRef(feature),
10135
+ stage: "pre_pr_review",
10136
+ nextAction: buildAction(
10137
+ "pre_pr_review",
10138
+ "Resolve the recorded Feature review blocker before implementation approval or integration.",
10139
+ false,
10140
+ null,
10141
+ void 0,
10142
+ reviewContext
10143
+ ),
10144
+ approvalRequired: false,
10145
+ implementationAllowed: false,
10146
+ blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
10147
+ };
10148
+ }
10149
+ if (!featureReviewSatisfied(config, feature, tasks, reviewTarget)) {
10150
+ return {
10151
+ status: "ok",
10152
+ reasonCode: "WORKFLOW_STAGE_RESOLVED",
10153
+ docsDir: config.docsDir,
10154
+ featureRef: buildFeatureRef(feature),
10155
+ stage: "pre_pr_review",
10156
+ nextAction: buildAction(
10157
+ "pre_pr_review",
10158
+ reviewTarget ? `Delegate an independent read-only Feature review to a fresh subagent for ${reviewTarget.baseSha}..${reviewTarget.targetSha}. Record 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.",
10159
+ false,
10160
+ null,
10161
+ reviewTarget ? resolveAgentReviewer(config, "feature") : void 0,
10162
+ reviewContext || void 0
10163
+ ),
10164
+ approvalRequired: false,
10165
+ implementationAllowed: false,
10166
+ blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
10167
+ };
10168
+ }
10169
+ }
9119
10170
  if (!allTasksDone(tasks)) {
9120
- const currentTask = nextTodoTask(tasks);
10171
+ const currentTask = nextExecutableTask(tasks);
9121
10172
  if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
9122
10173
  return {
9123
10174
  status: "ok",
@@ -9144,6 +10195,7 @@ async function collectWorkflowStage(cwd, selector, component) {
9144
10195
  }
9145
10196
  const commitWarning = taskCommitGatePolicy === "warn" && !committedTaskGate.pass ? `
9146
10197
  Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)}` : "";
10198
+ const delegationGuidance = requirements.taskExecutionEnabled ? " Delegate implementation and task-scoped verification to a fresh configured subagent under the returned workerContract. The worker executes directly and must not run workflow-stage or delegate again. The main agent retains docs updates, task state transitions, commits, approvals, and remote actions" : "";
9147
10199
  return {
9148
10200
  status: "ok",
9149
10201
  reasonCode: "WORKFLOW_STAGE_RESOLVED",
@@ -9152,8 +10204,20 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
9152
10204
  stage: "implementation",
9153
10205
  nextAction: buildAction(
9154
10206
  "task_execute",
9155
- currentTask ? `Continue the next implementation task: ${currentTask.title}${commitWarning}` : "Continue the active implementation task.",
9156
- false
10207
+ `Continue the next implementation task: ${currentTask.title}.${delegationGuidance}${requirements.taskReviewEnabled ? " When implementation and checks are complete, move it to REVIEW instead of DONE" : ""}${commitWarning}`,
10208
+ false,
10209
+ null,
10210
+ requirements.taskExecutionEnabled ? resolveTaskExecutor(config) : void 0,
10211
+ {
10212
+ taskId: currentTask.taskId,
10213
+ taskIdSource: currentTask.taskIdSource,
10214
+ taskTitle: currentTask.title,
10215
+ workingDirectory: effectiveProjectGitCwd,
10216
+ ...requirements.taskExecutionEnabled ? {
10217
+ docsDirectory: config.docsDir,
10218
+ workerContract: createTaskWorkerContract()
10219
+ } : {}
10220
+ }
9157
10221
  ),
9158
10222
  approvalRequired: false,
9159
10223
  implementationAllowed: true,
@@ -9217,27 +10281,8 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
9217
10281
  blockedReasonCode: "IMPLEMENTATION_APPROVAL_REQUIRED"
9218
10282
  };
9219
10283
  }
9220
- if (requirements.prePrReviewEnabled && !prePrSatisfied(config, feature, tasks)) {
9221
- return {
9222
- status: "ok",
9223
- reasonCode: "WORKFLOW_STAGE_RESOLVED",
9224
- docsDir: config.docsDir,
9225
- featureRef: buildFeatureRef(feature),
9226
- stage: "pre_pr_review",
9227
- nextAction: buildAction(
9228
- "pre_pr_review",
9229
- "Delegate an independent read-only Pre-PR review to a fresh subagent and record its findings, decision, and reviewer metadata as evidence.",
9230
- false,
9231
- null,
9232
- resolvePrePrReviewer(config)
9233
- ),
9234
- approvalRequired: false,
9235
- implementationAllowed: false,
9236
- blockedReasonCode: "PRE_PR_REVIEW_NOT_APPROVED"
9237
- };
9238
- }
9239
10284
  if (config.workflow?.mode === "local" && resolveLocalCompletionStrategy(config) !== "none") {
9240
- const localState = await resolveLocalIntegrationContext(config, feature);
10285
+ const localState = resolvedLocalState || await resolveLocalIntegrationContext(config, feature);
9241
10286
  const localVerifyCommand = `npx lee-spec-kit local verify ${buildFeatureArgs(feature)} --json`;
9242
10287
  const localMergeBaseCommand = `npx lee-spec-kit local merge ${buildFeatureArgs(feature)} --json`;
9243
10288
  if (localState.cleanedIntegrationStillValid) {