lee-spec-kit 0.6.33 → 0.6.35

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
@@ -8,7 +8,7 @@ import chalk8 from 'chalk';
8
8
  import { spawn, spawnSync, execFileSync, execSync } from 'child_process';
9
9
  import os from 'os';
10
10
  import { createHash, randomUUID } from 'crypto';
11
- import fs8 from 'fs';
11
+ import fs9 from 'fs';
12
12
  import { Buffer as Buffer$1 } from 'buffer';
13
13
 
14
14
  var getFilename = () => fileURLToPath(import.meta.url);
@@ -512,6 +512,10 @@ var koContext = {
512
512
  "context.actionDetail.codeReviewRemoteBlocked": "\uC6D0\uACA9 PR \uCC28\uB2E8 \uC0AC\uC720\uB97C \uD574\uC18C\uD55C \uB4A4 \uBA38\uC9C0\uB97C \uC9C4\uD589\uD558\uC138\uC694",
513
513
  "context.actionDetail.codeReviewMergeAfterOk": "\uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 PR\uC744 \uBA38\uC9C0\uD558\uC138\uC694",
514
514
  "context.actionDetail.codeReviewRequestReview": "\uB9AC\uBDF0 \uC694\uCCAD\uC744 \uC9C4\uD589\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC720\uC9C0\uD558\uC138\uC694",
515
+ "context.actionDetail.featureScopeSplit": "\uC774 Feature\uB97C \uB354 \uC791\uC740 \uC774\uC288 \uB2E8\uC704\uB85C \uBD84\uB9AC\uD560\uC9C0 \uAC80\uD1A0\uD558\uC138\uC694",
516
+ "context.actionDetail.featureScopeSplitKeep": "\uBD84\uD560 \uAC00\uC774\uB4DC\uB97C \uD655\uC778\uD55C \uB4A4 \uD604\uC7AC \uC774\uC288 \uBC94\uC704\uB97C \uC720\uC9C0\uD558\uACE0 \uC9C4\uD589\uD558\uC138\uC694",
517
+ "context.actionDetail.featureScopeSplitTwo": "\uACB0\uD569\uB3C4/\uD30C\uC77C\uACB9\uCE68/\uD14C\uC2A4\uD2B8/\uBC30\uD3EC \uAE30\uC900\uC73C\uB85C 2\uAC1C \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uC138\uC694",
518
+ "context.actionDetail.featureScopeSplitFour": "\uAE30\uC900 \uAE30\uBC18\uC73C\uB85C 4\uAC1C \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uACE0 \uC758\uC874 \uC21C\uC11C\uB300\uB85C PR\uC744 \uBA38\uC9C0\uD558\uC138\uC694",
515
519
  "context.actionDetail.worktreeCleanup": "\uC644\uB8CC\uB41C feature worktree\uB97C \uC815\uB9AC\uD558\uC138\uC694",
516
520
  "context.actionDetail.prMetadataMigrate": "tasks.md\uC758 PR \uD56D\uBAA9 \uD615\uC2DD\uC744 \uCD5C\uC2E0 \uD15C\uD50C\uB9BF\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694",
517
521
  "context.actionDetail.prMetadataMigratePrFields": "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694",
@@ -595,6 +599,8 @@ var koMessages = {
595
599
  reviewFixCommitGuidance: "PR \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uB9AC\uBDF0 \uBC18\uC601 \uD30C\uC77C\uB9CC \uC2A4\uD14C\uC774\uC9D5\uD55C \uB4A4 `fix(review): <review-fix-summary>` \uD615\uC2DD\uC73C\uB85C \uCEE4\uBC0B\uD558\uC138\uC694. `<review-fix-summary>`\uC5D0\uB294 \uC774\uBC88 \uCEE4\uBC0B\uC5D0\uC11C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uD56D\uBAA9 \uC694\uC57D\uC744 \uC791\uC131\uD558\uC138\uC694. (\uD0DC\uC2A4\uD06C \uC81C\uBAA9 \uC7AC\uC0AC\uC6A9 \uAE08\uC9C0)",
596
600
  standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
597
601
  createBranch: 'cd "{projectGitCwd}" && mkdir -p .worktrees && (git worktree add ".worktrees/feat-{issueNumber}-{slug}" "feat/{issueNumber}-{slug}" || git worktree add -b "feat/{issueNumber}-{slug}" ".worktrees/feat-{issueNumber}-{slug}") && WT="{projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}" && for f in .env .env.local .env.development .env.development.local .env.test .env.test.local .env.production .env.production.local; do [ -f "{projectGitCwd}/$f" ] && [ ! -e "$WT/$f" ] && cp "{projectGitCwd}/$f" "$WT/$f" || true; done && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
602
+ moveToExistingWorktree: '\uD574\uB2F9 feature\uC6A9 worktree\uAC00 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 `cd "{worktreePath}"`\uB85C \uC774\uB3D9\uD55C \uB4A4 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.',
603
+ worktreeRequiredFromMainBranch: "`workflow.requireWorktree`\uAC00 \uD65C\uC131\uD654\uB418\uC5B4 \uC788\uC9C0\uB9CC \uD604\uC7AC feature \uBE0C\uB79C\uCE58\uAC00 \uBA54\uC778 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uCCB4\uD06C\uC544\uC6C3\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uBA54\uC778 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uAE30\uBCF8 \uBE0C\uB79C\uCE58\uB85C \uB418\uB3CC\uB9B0 \uB4A4 `.worktrees/feat-{issueNumber}-{slug}`\uB97C \uC0DD\uC131/\uC7AC\uC0AC\uC6A9\uD558\uACE0 \uADF8 worktree\uC5D0\uC11C \uACC4\uC18D \uC9C4\uD589\uD558\uC138\uC694.",
598
604
  worktreeCleanupCommand: 'cd "{projectGitCwd}" && WT="{worktreePath}" && ROOT="$(pwd)" && case "$WT" in "$ROOT"/.worktrees/*) if git worktree list --porcelain | grep -Fxq "worktree $WT"; then git worktree remove --force "$WT" || true; fi; [ -d "$WT" ] && rm -rf "$WT" || true ;; *) echo "skip unsafe worktree path: $WT" ;; esac && git worktree prune && CURRENT_BRANCH=$(git branch --show-current) && DEFAULT_BRANCH=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | cut -d/ -f2-) && TARGET_BRANCH="${DEFAULT_BRANCH:-$CURRENT_BRANCH}" && if [ -n "$TARGET_BRANCH" ]; then git checkout "$TARGET_BRANCH" >/dev/null 2>&1 || true; fi && if git rev-parse --abbrev-ref --symbolic-full-name "@{u}" >/dev/null 2>&1 && [ -z "$(git status --porcelain)" ]; then git pull --ff-only || true; fi',
599
605
  tasksAllDoneButNoChecklist: '\uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uB97C \uC791\uC131\uD558\uC138\uC694. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC5D0 \uAC80\uC99D \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0, \uC0AC\uC6A9\uC790\uC640 \uD655\uC778 \uD6C4 \uCDA9\uC871 \uD56D\uBAA9\uC744 [x]\uB85C \uCCB4\uD06C\uD558\uC138\uC694. \uBA85\uC2DC\uC801\uC778 \uCD5C\uC885 \uC2B9\uC778\uB3C4 \uBC18\uC601\uD558\uC138\uC694.',
600
606
  tasksAllDoneButChecklist: "\uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uC758 \uB0A8\uC740 \uD56D\uBAA9\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uD604\uC7AC \uC9C4\uD589: ({checked}/{total}) \uC0AC\uC6A9\uC790\uC640 \uD655\uC778 \uD6C4 \uCDA9\uC871 \uD56D\uBAA9\uC744 [x]\uB85C \uCCB4\uD06C\uD558\uACE0 \uBA85\uC2DC\uC801\uC778 \uCD5C\uC885 \uC2B9\uC778\uC744 \uBC18\uC601\uD558\uC138\uC694.",
@@ -609,9 +615,10 @@ var koMessages = {
609
615
  taskCommitGateReasonMismatchLastDone: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B\uC774 \uC9C1\uC804 \uC644\uB8CC \uD0DC\uC2A4\uD06C\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
610
616
  prLegacyAsk: "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD15C\uD50C\uB9BF\uC744 \uCD5C\uC2E0 \uD3EC\uB9F7\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694? (\uD655\uC778 \uD544\uC694)",
611
617
  prePrReviewFieldMissing: "tasks.md\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uC804 \uB9AC\uBDF0**: Pending | Done` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
612
- prePrReviewRun: "\uCF54\uB4DC \uB9AC\uBDF0 \uC5D0\uC774\uC804\uD2B8\uB97C \uC2E4\uD589\uD574 `review-trace.json`\uC744 \uC0DD\uC131\uD55C \uB4A4, `pre-pr-review --evidence review-trace.json`\uC73C\uB85C \uB9AC\uBDF0 \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
613
- prePrReviewEvidenceMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC2E4\uC81C \uD30C\uC77C \uACBD\uB85C\uC640 `Pre-PR Review Log`(\uB610\uB294 `PR \uC804 \uB9AC\uBDF0 \uB85C\uADF8`)\uC5D0 placeholder\uAC00 \uC544\uB2CC `Summary`/`Decision`/`Findings`(\uB610\uB294 \uBA85\uC2DC\uC801 `0 findings`)/`Residual Risks`/`Tests Run`\uC744 \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
618
+ prePrReviewRun: "\uCF54\uB4DC \uB9AC\uBDF0 \uC5D0\uC774\uC804\uD2B8\uB97C \uC2E4\uD589\uD574 \uBE44\uC5B4\uC788\uC9C0 \uC54A\uC740 `commandsExecuted`\uB97C \uD3EC\uD568\uD55C `review-trace.json`\uC744 \uC0DD\uC131\uD55C \uB4A4, `pre-pr-review --evidence review-trace.json`\uC73C\uB85C \uB9AC\uBDF0 \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
619
+ prePrReviewEvidenceMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC2E4\uC81C \uD30C\uC77C \uACBD\uB85C\uC640 `Pre-PR Review Log`(\uB610\uB294 `PR \uC804 \uB9AC\uBDF0 \uB85C\uADF8`)\uC5D0 placeholder\uAC00 \uC544\uB2CC `Summary`/`Decision`/`Findings`(\uB610\uB294 \uBA85\uC2DC\uC801 `0 findings`)/`Residual Risks`/`Tests Run`/\uC2E4\uD589 \uBA85\uB839(`commandsExecuted`)\uC744 \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
614
620
  prePrReviewDecisionMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 \uBE44\uC5B4\uC788\uAC70\uB098 \uACB0\uC815 \uD615\uC2DD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `\uACB0\uC815: ...`(\uB610\uB294 `decision: ...`) \uD615\uC2DD\uC73C\uB85C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
621
+ prePrReviewDecisionReconfirm: "\uD604\uC7AC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 `{decision}`\uC785\uB2C8\uB2E4. \uC9C0\uC801\uC0AC\uD56D\uC744 \uBC18\uC601\uD55C \uB4A4 \uC774\uC804 \uC0C1\uD0DC \uC7AC\uC0AC\uC6A9\uC744 \uB9C9\uAE30 \uC704\uD574 \uBA85\uC2DC\uC801\uC73C\uB85C Decision\uC744 \uC9C0\uC815\uD574 \uC7AC\uC2E4\uD589\uD558\uC138\uC694: `{command}` (\uD655\uC778 \uD544\uC694)",
615
622
  prReviewEvidenceFieldMissing: "tasks.md\uC5D0 `PR \uB9AC\uBDF0 Evidence` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uB9AC\uBDF0 Evidence**: -` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC \uC9C4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
616
623
  prReviewEvidenceMissing: "tasks.md\uC758 `PR \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. `\uC694\uC57D: ...`(\uB610\uB294 `summary: ...`) \uD615\uC2DD\uC73C\uB85C \uAE30\uB85D\uD558\uAC70\uB098 `PR Review Log`(\uB610\uB294 `PR \uB9AC\uBDF0 \uB85C\uADF8`)\uC758 `Summary`/`Decision`\uC774 \uC788\uB294 \uD30C\uC77C \uACBD\uB85C\uB97C \uC9C0\uC815\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
617
624
  prReviewDecisionFieldMissing: "tasks.md\uC5D0 `PR \uB9AC\uBDF0 Decision` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uB9AC\uBDF0 Decision**: -` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC \uC9C4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
@@ -636,6 +643,9 @@ var koMessages = {
636
643
  prReviewMerge: "\uBA38\uC9C0 \uC900\uBE44\uAC00 \uB418\uBA74 \uBA85\uC2DC\uC801\uC778 \uC2B9\uC778(\uB77C\uBCA8)\uC744 \uBC1B\uC740 \uB4A4 \uBA38\uC9C0 \uC635\uC158\uC744 \uC2E4\uD589\uD558\uC138\uC694. (\uC131\uACF5 \uC2DC PR \uC0C1\uD0DC\uAC00 Approved\uB85C \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4.)",
637
644
  prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
638
645
  prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC124\uC815/\uC720\uC9C0\uD558\uC138\uC694.",
646
+ featureScopeSplitKeep: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}; \uBD84\uD560 \uC81C\uC548 \uAE30\uC900: {taskThreshold}/{decisionsThreshold}) \uD604\uC7AC \uAD8C\uC7A5 \uBD84\uD560\uC740 {recommendedIssues}\uAC1C \uC774\uC288\uC785\uB2C8\uB2E4. \uBA3C\uC800 `{guideCommand}`\uB97C \uD655\uC778\uD558\uACE0 \uACB0\uD569\uB3C4, \uBCC0\uACBD \uD30C\uC77C \uACB9\uCE68, \uD14C\uC2A4\uD2B8 \uACBD\uACC4, \uBC30\uD3EC \uB3C5\uB9BD\uC131 \uAE30\uC900\uC73C\uB85C \uD310\uB2E8\uD558\uC138\uC694. \uC774\uC288\uB97C \uC720\uC9C0\uD560 \uACBD\uC6B0 tasks.md \uBC94\uC704\uB97C \uCD95\uC18C\uD558\uACE0 \uC800\uC6B0\uC120 TODO\uB294 \uD65C\uC131 \uBC30\uCE58\uC5D0\uC11C \uC81C\uC678\uD558\uC138\uC694.",
647
+ featureScopeSplitTwo: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}) \uAD8C\uC7A5 \uADDC\uCE59\uC0C1 40~79 \uD0DC\uC2A4\uD06C\uC774\uBA74\uC11C \uD558\uB4DC \uAE30\uC900 \uBBF8\uB9CC\uC774\uBA74 2\uAC1C \uC774\uC288 \uBD84\uD560\uC774 \uAE30\uBCF8\uC785\uB2C8\uB2E4. `{guideCommand}`\uB97C \uB530\uB77C \uACB0\uD569\uB3C4/\uD30C\uC77C\uACB9\uCE68/\uD14C\uC2A4\uD2B8/\uBC30\uD3EC \uAE30\uC900\uC73C\uB85C \uBD84\uD560\uD558\uC138\uC694. \uAC01 \uC774\uC288\uC5D0\uB294 \uB2E4\uC74C \uD15C\uD50C\uB9BF\uC744 \uAE30\uB85D\uD558\uC138\uC694: \uBAA9\uD45C, \uD3EC\uD568 \uBC94\uC704, \uC81C\uC678 \uBC94\uC704, \uC120\uD589 \uC758\uC874\uC131, PR \uC644\uB8CC \uAE30\uC900.",
648
+ featureScopeSplitFour: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}) tasks >= {recommendFourTaskThreshold} \uB610\uB294 decisions \uC904\uC218 >= {recommendFourDecisionsThreshold}\uC774\uBA74 4\uAC1C \uC774\uC288 \uBD84\uD560\uC744 \uAC15\uD558\uAC8C \uAD8C\uC7A5\uD569\uB2C8\uB2E4. `{guideCommand}`\uB97C \uB530\uB77C 4\uAC1C\uC758 \uC5F0\uAD00 \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uACE0 \uC758\uC874 \uC21C\uC11C\uB97C \uBA85\uC2DC\uD55C \uB4A4 PR\uC744 \uC21C\uCC28 \uBA38\uC9C0\uD558\uC138\uC694. \uAC01 \uC774\uC288 \uD15C\uD50C\uB9BF: \uBAA9\uD45C, \uD3EC\uD568 \uBC94\uC704, \uC81C\uC678 \uBC94\uC704, \uC120\uD589 \uC758\uC874\uC131, PR \uC644\uB8CC \uAE30\uC900.",
639
649
  userRequestReplan: "\uD604\uC7AC \uB2E8\uACC4\uC640 \uBCC4\uAC1C\uB85C \uC0AC\uC6A9\uC790\uAC00 \uC81C\uC548\uD55C \uC0C8 \uC694\uAD6C\uB97C \uBA3C\uC800 \uBC18\uC601\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC694\uAD6C\uC0AC\uD56D\uC744 \uC694\uC57D\uD574 tasks.md\uC5D0 \uCD94\uAC00\uD558\uAC70\uB098 \uBCC4\uB3C4 Feature\uB85C \uBD84\uB9AC\uD55C \uB4A4, \uBB38\uC11C \uC0C1\uD0DC\uB97C \uB9DE\uCD94\uACE0 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
640
650
  featureDone: "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC694\uAD6C\uC0AC\uD56D\uACFC \uBAA8\uB4E0 \uD0DC\uC2A4\uD06C/\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uB8CC \uC0C1\uD0DC\uC785\uB2C8\uB2E4.",
641
651
  fallbackRerunContext: "\uC0C1\uD0DC\uB97C \uD310\uBCC4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694."
@@ -644,10 +654,13 @@ var koMessages = {
644
654
  // src/utils/locales/ko/warnings.ts
645
655
  var koWarnings = {
646
656
  projectBranchUnavailable: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.)",
657
+ projectExpectedBranchOnMainWorkspace: "feature \uBE0C\uB79C\uCE58\uAC00 \uBA54\uC778 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uCCB4\uD06C\uC544\uC6C3\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uAC00\uB2A5\uD558\uBA74 `.worktrees/*` \uACBD\uB85C\uC5D0\uC11C \uC791\uC5C5\uD558\uC138\uC694.",
658
+ workflowWorktreeRequired: "`workflow.requireWorktree=true` \uC124\uC815\uC73C\uB85C \uC778\uD574 \uD0DC\uC2A4\uD06C \uC2E4\uD589\uC740 `.worktrees/*` \uACBD\uB85C\uC5D0\uC11C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.",
647
659
  docsGitUnavailable: "docs \uB808\uD3EC\uC758 git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uB808\uD3EC \uC704\uCE58 / git init \uD655\uC778)",
648
660
  docsPathIgnored: "\uD604\uC7AC Feature \uBB38\uC11C \uACBD\uB85C\uAC00 git ignore \uB300\uC0C1\uC785\uB2C8\uB2E4: {path} (docs \uCEE4\uBC0B \uAC10\uC9C0\uAC00 \uC81C\uD55C\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.)",
649
661
  docsUncommittedChanges: "\uBB38\uC11C \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uBB38\uC11C \uCEE4\uBC0B \uD544\uC694) \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59\uC740 git-workflow \uAC00\uC774\uB4DC\uB97C \uAE30\uC900\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.",
650
662
  projectUncommittedChanges: "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694)",
663
+ featureScopeSplitSuggested: "Feature \uBC94\uC704\uAC00 \uB2E8\uC77C \uC774\uC288\uB85C \uCC98\uB9AC\uD558\uAE30\uC5D0 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}; \uBD84\uD560 \uC81C\uC548 \uAE30\uC900: tasks {taskThreshold}\uAC1C \uB610\uB294 decisions {decisionsThreshold}\uC904) \uD604\uC7AC \uAD8C\uC7A5 \uBD84\uD560: {recommendedIssues}\uAC1C \uC774\uC288 (4\uBD84\uD560 \uD558\uB4DC \uAE30\uC900: tasks >= {recommendFourTaskThreshold} \uB610\uB294 decisions \uC904\uC218 >= {recommendFourDecisionsThreshold}).",
651
664
  legacyTasksDocStatusField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. `\uBB38\uC11C \uC0C1\uD0DC` \uD544\uB4DC(Draft/Review/Approved)\uB97C \uCD94\uAC00\uD574 \uD0DC\uC2A4\uD06C \uC2B9\uC778 \uB2E8\uACC4\uB97C \uD65C\uC131\uD654\uD558\uC138\uC694.",
652
665
  legacyTasksPrFields: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR` \uBC0F `PR \uC0C1\uD0DC` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
653
666
  legacyTasksPrePrReviewField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`)",
@@ -1041,6 +1054,10 @@ var enContext = {
1041
1054
  "context.actionDetail.codeReviewRemoteBlocked": "Resolve remote PR blockers before merge",
1042
1055
  "context.actionDetail.codeReviewMergeAfterOk": "Merge PR after explicit OK",
1043
1056
  "context.actionDetail.codeReviewRequestReview": "Request review and keep PR Status as Review",
1057
+ "context.actionDetail.featureScopeSplit": "Review whether this feature should be split into smaller issue units",
1058
+ "context.actionDetail.featureScopeSplitKeep": "Keep current issue scope and continue (after split-guide check)",
1059
+ "context.actionDetail.featureScopeSplitTwo": "Split into 2 linked issues using coupling/file-overlap/test/deploy criteria",
1060
+ "context.actionDetail.featureScopeSplitFour": "Split into 4 linked issues (criteria-based) and merge PRs in dependency order",
1044
1061
  "context.actionDetail.worktreeCleanup": "Clean up the completed feature worktree",
1045
1062
  "context.actionDetail.prMetadataMigrate": "Update tasks.md PR fields to the latest template format",
1046
1063
  "context.actionDetail.prMetadataMigratePrFields": "Update tasks.md with PR/PR Status fields",
@@ -1124,6 +1141,8 @@ var enMessages = {
1124
1141
  reviewFixCommitGuidance: "Commit PR review fixes. Stage only review-fix files, then commit with `fix(review): <review-fix-summary>`. `<review-fix-summary>` must describe review comments resolved in this commit (do not reuse task titles).",
1125
1142
  standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
1126
1143
  createBranch: 'cd "{projectGitCwd}" && mkdir -p .worktrees && (git worktree add ".worktrees/feat-{issueNumber}-{slug}" "feat/{issueNumber}-{slug}" || git worktree add -b "feat/{issueNumber}-{slug}" ".worktrees/feat-{issueNumber}-{slug}") && WT="{projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}" && for f in .env .env.local .env.development .env.development.local .env.test .env.test.local .env.production .env.production.local; do [ -f "{projectGitCwd}/$f" ] && [ ! -e "$WT/$f" ] && cp "{projectGitCwd}/$f" "$WT/$f" || true; done && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
1144
+ moveToExistingWorktree: 'A matching feature worktree already exists. Move first: `cd "{worktreePath}"`, then re-run context.',
1145
+ worktreeRequiredFromMainBranch: "`workflow.requireWorktree` is enabled, but this feature branch is checked out in the main workspace. Switch the main workspace to a base branch first, then create/reuse `.worktrees/feat-{issueNumber}-{slug}` and continue from that worktree.",
1127
1146
  worktreeCleanupCommand: 'cd "{projectGitCwd}" && WT="{worktreePath}" && ROOT="$(pwd)" && case "$WT" in "$ROOT"/.worktrees/*) if git worktree list --porcelain | grep -Fxq "worktree $WT"; then git worktree remove --force "$WT" || true; fi; [ -d "$WT" ] && rm -rf "$WT" || true ;; *) echo "skip unsafe worktree path: $WT" ;; esac && git worktree prune && CURRENT_BRANCH=$(git branch --show-current) && DEFAULT_BRANCH=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | cut -d/ -f2-) && TARGET_BRANCH="${DEFAULT_BRANCH:-$CURRENT_BRANCH}" && if [ -n "$TARGET_BRANCH" ]; then git checkout "$TARGET_BRANCH" >/dev/null 2>&1 || true; fi && if git rev-parse --abbrev-ref --symbolic-full-name "@{u}" >/dev/null 2>&1 && [ -z "$(git status --porcelain)" ]; then git pull --ff-only || true; fi',
1128
1147
  tasksAllDoneButNoChecklist: 'Create the completion checklist. Add verification items to the tasks.md "Completion Criteria" section, then mark satisfied items as [x] after user confirmation. Record explicit final approval as well.',
1129
1148
  tasksAllDoneButChecklist: "Proceed with remaining completion checklist items. Current progress: ({checked}/{total}) Mark items as [x] only after user confirmation and real verification. Record explicit final approval as well.",
@@ -1138,9 +1157,10 @@ var enMessages = {
1138
1157
  taskCommitGateReasonMismatchLastDone: "The latest project code commit does not match the last completed task",
1139
1158
  prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
1140
1159
  prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
1141
- prePrReviewRun: "Run the code review agent to generate `review-trace.json`, then execute `pre-pr-review --evidence review-trace.json` to record findings. (CHECK required)",
1142
- prePrReviewEvidenceMissing: "tasks.md `Pre-PR Evidence` is empty/invalid. Point to a real file and include a `Pre-PR Review Log` section with non-placeholder `Summary`, `Decision`, `Findings` (or explicit `0 findings`), `Residual Risks`, and `Tests Run`. (CHECK required)",
1160
+ prePrReviewRun: "Run the code review agent and generate `review-trace.json` with non-empty `commandsExecuted`, then execute `pre-pr-review --evidence review-trace.json` to record findings. (CHECK required)",
1161
+ prePrReviewEvidenceMissing: "tasks.md `Pre-PR Evidence` is empty/invalid. Point to a real file and include a `Pre-PR Review Log` section with non-placeholder `Summary`, `Decision`, `Findings` (or explicit `0 findings`), `Residual Risks`, `Tests Run`, and real executed commands (`commandsExecuted`). (CHECK required)",
1143
1162
  prePrReviewDecisionMissing: "tasks.md `Pre-PR Decision` is empty/placeholder or missing decision format. Record it as `decision: ...` (or `\uACB0\uC815: ...`). (CHECK required)",
1163
+ prePrReviewDecisionReconfirm: "Current `Pre-PR Decision` is `{decision}`. After fixing findings, rerun pre-PR review with an explicit decision to avoid replaying the prior state: `{command}` (CHECK required)",
1144
1164
  prReviewEvidenceFieldMissing: "tasks.md is missing the `PR Review Evidence` field. Add `- **PR Review Evidence**: -` and continue. (CHECK required)",
1145
1165
  prReviewEvidenceMissing: "tasks.md `PR Review Evidence` is empty/invalid. Use `summary: ...` (or `\uC694\uC57D: ...`), or point to a file containing `PR Review Log` with non-placeholder `Summary` and `Decision`. (CHECK required)",
1146
1166
  prReviewDecisionFieldMissing: "tasks.md is missing the `PR Review Decision` field. Add `- **PR Review Decision**: -` and continue. (CHECK required)",
@@ -1165,6 +1185,9 @@ var enMessages = {
1165
1185
  prReviewMerge: "When ready to merge, get explicit approval (label) and run the merge option. (On success, PR Status is synced to Approved.)",
1166
1186
  prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
1167
1187
  prRequestReview: "Request review and set/keep PR Status as Review.",
1188
+ featureScopeSplitKeep: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}; split suggestion threshold: {taskThreshold}/{decisionsThreshold}). Current recommendation is {recommendedIssues} issues. Before deciding, read `{guideCommand}` and evaluate coupling, changed-file overlap, test boundary, and deployment independence. If you keep one issue, tighten scope in tasks.md and move low-priority TODO items out of the active batch.",
1189
+ featureScopeSplitTwo: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}). Recommendation rule: 40~79 tasks and below the hard threshold usually maps to 2 issues. Follow `{guideCommand}` and split by coupling/file-overlap/test/deploy criteria. For each new issue, document this template: Goal, Included Scope, Excluded Scope, Depends On, PR Done Criteria.",
1190
+ featureScopeSplitFour: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}). Hard recommendation is 4 issues when tasks >= {recommendFourTaskThreshold} or decisions lines >= {recommendFourDecisionsThreshold}. Follow `{guideCommand}` and split into 4 linked issues with explicit dependency order and sequential PR merges. Use the per-issue template: Goal, Included Scope, Excluded Scope, Depends On, PR Done Criteria.",
1168
1191
  userRequestReplan: "You can pause this step and handle a newly requested user requirement first. Summarize it, add it to tasks.md or split it into a separate Feature, then align document statuses and rerun context.",
1169
1192
  featureDone: "Workflow requirements and all tasks/completion criteria are satisfied. This feature is done.",
1170
1193
  fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
@@ -1173,10 +1196,13 @@ var enMessages = {
1173
1196
  // src/utils/locales/en/warnings.ts
1174
1197
  var enWarnings = {
1175
1198
  projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
1199
+ projectExpectedBranchOnMainWorkspace: "Feature branch is checked out in the main workspace. Prefer working from a `.worktrees/*` path.",
1200
+ workflowWorktreeRequired: "With `workflow.requireWorktree=true`, task execution is allowed only from `.worktrees/*` paths.",
1176
1201
  docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
1177
1202
  docsPathIgnored: "Current feature docs path is ignored by git: {path} (docs commit detection may be limited).",
1178
1203
  docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Check commit message rules against the git-workflow guide.",
1179
1204
  projectUncommittedChanges: "Project code changes are not committed. (Additional code commit needed.)",
1205
+ featureScopeSplitSuggested: "Feature scope may be too large for one issue (tasks: {taskCount}, decisions lines: {decisionsLineCount}; suggest split at {taskThreshold} tasks or {decisionsThreshold} decision lines). Current recommendation: split into {recommendedIssues} issue units (hard 4-way rule: tasks >= {recommendFourTaskThreshold} or decisions lines >= {recommendFourDecisionsThreshold}).",
1180
1206
  legacyTasksDocStatusField: "Legacy tasks.md format detected. Add a `Doc Status` field (Draft/Review/Approved) to enable tasks approval.",
1181
1207
  legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
1182
1208
  legacyTasksPrePrReviewField: "Legacy tasks.md format detected. Add `Pre-PR Review` before PR steps. (`- **Pre-PR Review**: Pending | Done`)",
@@ -2444,6 +2470,7 @@ async function runInit(options) {
2444
2470
  docsRepo,
2445
2471
  workflow: {
2446
2472
  mode: workflowMode,
2473
+ requireWorktree: false,
2447
2474
  codeDirtyScope: "auto",
2448
2475
  taskCommitGate: "warn",
2449
2476
  auto: {
@@ -2453,7 +2480,9 @@ async function runInit(options) {
2453
2480
  skills: ["code-review-excellence"],
2454
2481
  fallback: "builtin-checklist",
2455
2482
  evidenceMode: "path_required",
2456
- decisionEnum: ["approve", "changes_requested", "blocked"]
2483
+ decisionEnum: ["approve", "changes_requested", "blocked"],
2484
+ enforceExecutionEvidence: true,
2485
+ executionCommandPrefixes: []
2457
2486
  }
2458
2487
  },
2459
2488
  pr: {
@@ -3163,6 +3192,7 @@ var ACTION_CATEGORIES = [
3163
3192
  "pre_pr_review",
3164
3193
  "pr_status_update",
3165
3194
  "code_review",
3195
+ "feature_scope_split",
3166
3196
  "worktree_cleanup",
3167
3197
  "user_request_replan",
3168
3198
  "feature_done",
@@ -3182,6 +3212,7 @@ function resolveWorkflowPolicy(workflow) {
3182
3212
  mode,
3183
3213
  requireIssue: false,
3184
3214
  requireBranch: false,
3215
+ requireWorktree: false,
3185
3216
  requirePr: false,
3186
3217
  requireReview: false,
3187
3218
  requireMerge: false
@@ -3189,6 +3220,7 @@ function resolveWorkflowPolicy(workflow) {
3189
3220
  mode,
3190
3221
  requireIssue: true,
3191
3222
  requireBranch: true,
3223
+ requireWorktree: false,
3192
3224
  requirePr: true,
3193
3225
  requireReview: true,
3194
3226
  requireMerge: true
@@ -3199,6 +3231,9 @@ function resolveWorkflowPolicy(workflow) {
3199
3231
  if (typeof workflow?.requireBranch === "boolean") {
3200
3232
  policy.requireBranch = workflow.requireBranch;
3201
3233
  }
3234
+ if (typeof workflow?.requireWorktree === "boolean") {
3235
+ policy.requireWorktree = workflow.requireWorktree;
3236
+ }
3202
3237
  if (typeof workflow?.requirePr === "boolean") {
3203
3238
  policy.requirePr = workflow.requirePr;
3204
3239
  }
@@ -3211,6 +3246,9 @@ function resolveWorkflowPolicy(workflow) {
3211
3246
  if (!policy.requireIssue) {
3212
3247
  policy.requireBranch = false;
3213
3248
  }
3249
+ if (!policy.requireBranch) {
3250
+ policy.requireWorktree = false;
3251
+ }
3214
3252
  if (!policy.requirePr) {
3215
3253
  policy.requireReview = false;
3216
3254
  policy.requireMerge = false;
@@ -3267,6 +3305,16 @@ function normalizeDecisionEnumList(input) {
3267
3305
  }
3268
3306
  return [...deduped];
3269
3307
  }
3308
+ function normalizeCommandPrefixList(input) {
3309
+ if (!Array.isArray(input)) return [];
3310
+ const deduped = /* @__PURE__ */ new Set();
3311
+ for (const raw of input) {
3312
+ const value = String(raw || "").trim();
3313
+ if (!value) continue;
3314
+ deduped.add(value);
3315
+ }
3316
+ return [...deduped];
3317
+ }
3270
3318
  function resolvePrePrReviewPolicy(workflow) {
3271
3319
  const workflowPolicy = resolveWorkflowPolicy(workflow);
3272
3320
  const configured = workflow?.prePrReview;
@@ -3274,17 +3322,45 @@ function resolvePrePrReviewPolicy(workflow) {
3274
3322
  const configuredDecisionEnum = normalizeDecisionEnumList(
3275
3323
  configured?.decisionEnum
3276
3324
  );
3325
+ const configuredCommandPrefixes = normalizeCommandPrefixList(
3326
+ configured?.executionCommandPrefixes
3327
+ );
3277
3328
  const configuredEnabled = typeof configured?.enabled === "boolean" ? configured.enabled : workflowPolicy.requirePr;
3329
+ const configuredExecutionEvidence = typeof configured?.enforceExecutionEvidence === "boolean" ? configured.enforceExecutionEvidence : true;
3278
3330
  return {
3279
3331
  enabled: workflowPolicy.requirePr ? configuredEnabled : false,
3280
3332
  skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
3281
3333
  fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
3282
3334
  evidenceMode: configured?.evidenceMode === "any" ? "any" : "path_required",
3283
- decisionEnum: configuredDecisionEnum.length > 0 ? configuredDecisionEnum : DEFAULT_PRE_PR_DECISION_ENUM
3335
+ decisionEnum: configuredDecisionEnum.length > 0 ? configuredDecisionEnum : DEFAULT_PRE_PR_DECISION_ENUM,
3336
+ enforceExecutionEvidence: configuredExecutionEvidence,
3337
+ executionCommandPrefixes: configuredCommandPrefixes.length > 0 ? configuredCommandPrefixes : []
3284
3338
  };
3285
3339
  }
3286
3340
 
3287
3341
  // src/utils/agent-orchestration.ts
3342
+ function getPrePrReviewPrompt(lang, skills, fallbackText) {
3343
+ if (lang === "ko") {
3344
+ return `PR \uC0DD\uC131 \uC804 \uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0\uB97C \uC9C4\uD589\uD558\uC138\uC694.
3345
+ 1. \uC790\uB3D9\uD654\uB41C \uC624\uB958 \uBD84\uC11D\uAE30(\uC608: vitest, biome check, pnpm audit)\uB97C \uC2E4\uD589\uD558\uC138\uC694.
3346
+ 2. \uB9AC\uBDF0 \uBC94\uC704\uB97C \uBD84\uB9AC\uD574 \uD655\uC778\uD558\uC138\uC694.
3347
+ - main \uAE30\uC900: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
3348
+ - worktree \uAE30\uC900: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
3349
+ 3. \uD655\uC778\uB41C \uAC01 \uD30C\uC77C\uC5D0 \uB300\uD574 risk, security, perf, maintainability \uD3C9\uAC00\uC640 \uAD6C\uCCB4\uC801\uC778 fileLine \uC704\uCE58\uAC00 \uD3EC\uD568\uB41C 'review-trace.json' \uC99D\uAC70 \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC138\uC694. \uC794\uC5EC \uC704\uD5D8(residualRisks)\uACFC \uC2E4\uD589\uD55C \uBA85\uB839\uC5B4(commandsExecuted)\uB3C4 \uD3EC\uD568\uD558\uC138\uC694.
3350
+ 4. \uAE30\uBCF8 \uBCA0\uC774\uC2A4\uB77C\uC778\uC740 '${fallbackText}'\uC774\uBA70, 'create-pr' \uBB38\uC11C\uC758 'Pre-PR \uAE30\uBCF8 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8' \uC139\uC158\uC744 \uC218\uD589\uD558\uC138\uC694.
3351
+ 5. \uC6B0\uC120\uC21C\uC704 \uC2A4\uD0AC: ${skills.length > 0 ? skills.join(", ") : "\uC5C6\uC74C"} \uB85C \uC2EC\uD654 \uAC80\uD1A0\uB97C \uC9C4\uD589\uD558\uC138\uC694.
3352
+ \uC644\uB8CC \uD6C4 'pnpm lee pre-pr-review <feature> --evidence review-trace.json --decision approve' \uB97C \uC2E4\uD589\uD558\uC138\uC694.`;
3353
+ }
3354
+ return `Conduct a pre-PR code review.
3355
+ 1. Run automated analyzers (e.g., vitest, biome check, pnpm audit).
3356
+ 2. Split and check the review scope.
3357
+ - Main scope: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
3358
+ - Worktree scope: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
3359
+ 3. Generate a 'review-trace.json' file for all changed files, including evaluations for risk, security, perf, maintainability, and specific fileLine locators. Also include residualRisks and commandsExecuted array.
3360
+ 4. The baseline is '${fallbackText}'. Always perform the 'Pre-PR Core Checklist' section of the 'create-pr' document.
3361
+ 5. Priority skills: ${skills.length > 0 ? skills.join(", ") : "None"} for deeper technical review.
3362
+ After completion, execute 'pnpm lee pre-pr-review <feature> --evidence review-trace.json --decision approve'.`;
3363
+ }
3288
3364
  function getCodeReviewPrompt(lang) {
3289
3365
  if (lang === "ko") {
3290
3366
  return `\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD655\uC778/\uBD84\uC11D\uD55C \uB4A4 \uD544\uC694\uD55C \uC218\uC815\uC744 \uC9C4\uD589\uD558\uC138\uC694. PR \uC0C1\uD0DC\uB294 Review\uB97C \uC720\uC9C0\uD558\uACE0 'PR \uB9AC\uBDF0 Evidence/Decision'\uC744 \uCD5C\uC2E0\uC73C\uB85C \uAE30\uB85D\uD558\uC138\uC694. \uC6D0\uACA9 \uBC18\uC601(push)\uC740 \uBA85\uC2DC\uC801\uC778 \uBA38\uC9C0 \uC2B9\uC778(\uB77C\uBCA8) \uD6C4, \uB85C\uCEEC \uBE0C\uB79C\uCE58\uAC00 upstream\uBCF4\uB2E4 \uC55E\uC120 \uACBD\uC6B0\uC5D0\uB9CC \uC9C4\uD589\uD558\uC138\uC694.`;
@@ -3371,6 +3447,17 @@ function buildSelfCliCommand(args) {
3371
3447
  const base = [process.execPath, entry, "--no-banner", ...args];
3372
3448
  return base.map((arg) => toShellArg(arg)).join(" ");
3373
3449
  }
3450
+ function buildPrePrReviewCommandArgs(feature, evidencePath, decision) {
3451
+ const commandArgs = ["pre-pr-review", feature.folderName];
3452
+ if (feature.type && feature.type !== "single") {
3453
+ commandArgs.push("--component", feature.type);
3454
+ }
3455
+ commandArgs.push("--evidence", evidencePath);
3456
+ if (decision) {
3457
+ commandArgs.push("--decision", decision);
3458
+ }
3459
+ return commandArgs;
3460
+ }
3374
3461
  function resolvePrePrReviewEvidencePath(feature) {
3375
3462
  const docsRoot = feature.git.docsGitCwd;
3376
3463
  const candidates = [];
@@ -3392,7 +3479,7 @@ function resolvePrePrReviewEvidencePath(feature) {
3392
3479
  const abs = path12.resolve(candidate);
3393
3480
  if (seen.has(abs)) continue;
3394
3481
  seen.add(abs);
3395
- if (!fs8.existsSync(abs)) continue;
3482
+ if (!fs9.existsSync(abs)) continue;
3396
3483
  if (!abs.toLowerCase().endsWith(".json")) continue;
3397
3484
  const rel = path12.relative(docsRoot, abs).replace(/\\/g, "/");
3398
3485
  if (rel && !rel.startsWith("../")) {
@@ -3849,6 +3936,60 @@ function getStepDefinitions(ctx) {
3849
3936
  current: {
3850
3937
  when: (f) => f.docs.tasksExists && f.tasks.total > 0 && (f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)) && isTasksDocApproved(f) && (!workflowPolicy.requireBranch || f.git.onExpectedBranch || f.tasks.done === f.tasks.total),
3851
3938
  actions: (f) => {
3939
+ if (workflowPolicy.requireWorktree && f.tasks.done < f.tasks.total && f.issueNumber && !f.git.projectInManagedWorktree) {
3940
+ if (f.git.expectedWorktreePath) {
3941
+ return [
3942
+ {
3943
+ type: "instruction",
3944
+ category: "branch_create",
3945
+ requiresUserCheck: true,
3946
+ uiDetailKey: "context.actionDetail.branchCreate",
3947
+ message: tr(lang, "messages", "moveToExistingWorktree", {
3948
+ worktreePath: f.git.expectedWorktreePath
3949
+ })
3950
+ }
3951
+ ];
3952
+ }
3953
+ if (!f.git.projectGitCwd) {
3954
+ return [
3955
+ {
3956
+ type: "instruction",
3957
+ category: "branch_create",
3958
+ requiresUserCheck: true,
3959
+ message: tr(lang, "messages", "standaloneNeedsProjectRoot")
3960
+ }
3961
+ ];
3962
+ }
3963
+ if (f.git.onExpectedBranch) {
3964
+ return [
3965
+ {
3966
+ type: "instruction",
3967
+ category: "branch_create",
3968
+ requiresUserCheck: true,
3969
+ uiDetailKey: "context.actionDetail.branchCreate",
3970
+ message: tr(lang, "messages", "worktreeRequiredFromMainBranch", {
3971
+ projectGitCwd: f.git.projectGitCwd,
3972
+ issueNumber: f.issueNumber,
3973
+ slug: f.slug
3974
+ })
3975
+ }
3976
+ ];
3977
+ }
3978
+ return [
3979
+ {
3980
+ type: "command",
3981
+ category: "branch_create",
3982
+ requiresUserCheck: true,
3983
+ scope: "project",
3984
+ cwd: f.git.projectGitCwd,
3985
+ cmd: tr(lang, "messages", "createBranch", {
3986
+ projectGitCwd: f.git.projectGitCwd,
3987
+ issueNumber: f.issueNumber,
3988
+ slug: f.slug
3989
+ })
3990
+ }
3991
+ ];
3992
+ }
3852
3993
  if (f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f)) {
3853
3994
  if (f.git.docsHasUncommittedChanges) {
3854
3995
  return [
@@ -4204,15 +4345,30 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4204
4345
  type: "instruction",
4205
4346
  category: "pre_pr_review",
4206
4347
  requiresUserCheck: true,
4207
- message: tr(lang, "messages", "prePrReviewRun")
4348
+ message: getPrePrReviewPrompt(
4349
+ lang,
4350
+ prePrReviewPolicy.skills,
4351
+ prePrReviewPolicy.fallback
4352
+ )
4208
4353
  }
4209
4354
  ];
4210
4355
  }
4211
- const commandArgs = ["pre-pr-review", f.folderName];
4212
- if (f.type && f.type !== "single") {
4213
- commandArgs.push("--component", f.type);
4356
+ if (f.prePrReview.decisionOutcome && f.prePrReview.decisionOutcome !== "approve") {
4357
+ return [
4358
+ {
4359
+ type: "instruction",
4360
+ category: "pre_pr_review",
4361
+ requiresUserCheck: true,
4362
+ message: tr(lang, "messages", "prePrReviewDecisionReconfirm", {
4363
+ decision: f.prePrReview.decisionOutcome,
4364
+ command: buildSelfCliCommand(
4365
+ buildPrePrReviewCommandArgs(f, evidencePath, "approve")
4366
+ )
4367
+ })
4368
+ }
4369
+ ];
4214
4370
  }
4215
- commandArgs.push("--evidence", evidencePath);
4371
+ const commandArgs = buildPrePrReviewCommandArgs(f, evidencePath);
4216
4372
  return [
4217
4373
  {
4218
4374
  type: "command",
@@ -4575,6 +4731,77 @@ function applyTaskExecutePhaseCheck(action, requiresUserCheck, policy, explicitl
4575
4731
  if (explicitlyRequired) return requiresUserCheck;
4576
4732
  return false;
4577
4733
  }
4734
+ function withFeatureScopeSplitOptions(actions, feature, lang) {
4735
+ if (!feature.scopeSplit.suggested) return actions;
4736
+ if (feature.tasks.total === 0 || feature.tasks.done >= feature.tasks.total) {
4737
+ return actions;
4738
+ }
4739
+ if (actions.some((action) => action.category === "feature_scope_split")) {
4740
+ return actions;
4741
+ }
4742
+ const recommendedIssues = feature.scopeSplit.recommendation === "split_4" ? 4 : 2;
4743
+ const recommendationLabel = feature.scopeSplit.recommendation === "split_4" ? "split_4" : feature.scopeSplit.recommendation === "split_2" ? "split_2" : "none";
4744
+ const vars = {
4745
+ taskCount: feature.scopeSplit.taskCount,
4746
+ decisionsLineCount: feature.scopeSplit.decisionsLineCount,
4747
+ taskThreshold: feature.scopeSplit.suggestTaskCountThreshold,
4748
+ decisionsThreshold: feature.scopeSplit.suggestDecisionsLineCountThreshold,
4749
+ recommendFourTaskThreshold: feature.scopeSplit.recommendSplitFourTaskCountThreshold,
4750
+ recommendFourDecisionsThreshold: feature.scopeSplit.recommendSplitFourDecisionsLineCountThreshold,
4751
+ recommendedIssues,
4752
+ recommendationLabel,
4753
+ guideCommand: "npx lee-spec-kit docs get split-feature --json"
4754
+ };
4755
+ return [
4756
+ ...actions,
4757
+ {
4758
+ type: "instruction",
4759
+ category: "feature_scope_split",
4760
+ requiresUserCheck: true,
4761
+ uiDetailKey: "context.actionDetail.featureScopeSplitKeep",
4762
+ message: tr(lang, "messages", "featureScopeSplitKeep", vars)
4763
+ },
4764
+ {
4765
+ type: "instruction",
4766
+ category: "feature_scope_split",
4767
+ requiresUserCheck: true,
4768
+ uiDetailKey: "context.actionDetail.featureScopeSplitTwo",
4769
+ message: tr(lang, "messages", "featureScopeSplitTwo", vars)
4770
+ },
4771
+ {
4772
+ type: "instruction",
4773
+ category: "feature_scope_split",
4774
+ requiresUserCheck: true,
4775
+ uiDetailKey: "context.actionDetail.featureScopeSplitFour",
4776
+ message: tr(lang, "messages", "featureScopeSplitFour", vars)
4777
+ }
4778
+ ];
4779
+ }
4780
+ function withExistingWorktreeMoveOption(actions, feature, lang) {
4781
+ if (feature.tasks.total === 0 || feature.tasks.done >= feature.tasks.total) {
4782
+ return actions;
4783
+ }
4784
+ if (feature.git.projectInManagedWorktree) return actions;
4785
+ if (!feature.git.onExpectedBranch) return actions;
4786
+ if (!feature.git.expectedWorktreePath) return actions;
4787
+ if (actions.some(
4788
+ (action) => action.category === "branch_create" && action.type === "instruction" && action.message.includes(feature.git.expectedWorktreePath || "")
4789
+ )) {
4790
+ return actions;
4791
+ }
4792
+ return [
4793
+ ...actions,
4794
+ {
4795
+ type: "instruction",
4796
+ category: "branch_create",
4797
+ requiresUserCheck: true,
4798
+ uiDetailKey: "context.actionDetail.branchCreate",
4799
+ message: tr(lang, "messages", "moveToExistingWorktree", {
4800
+ worktreePath: feature.git.expectedWorktreePath
4801
+ })
4802
+ }
4803
+ ];
4804
+ }
4578
4805
  function withUserRequestReplanOption(actions, lang) {
4579
4806
  if (actions.some((action) => action.category === "user_request_replan")) {
4580
4807
  return actions;
@@ -4594,8 +4821,18 @@ function resolveFeatureProgress(feature, stepDefinitions, lang, approval) {
4594
4821
  for (const definition of ordered) {
4595
4822
  if (!definition.current) continue;
4596
4823
  if (definition.current.when(feature)) {
4597
- const currentActions = withUserRequestReplanOption(
4824
+ const actionsWithScopeSplit = withFeatureScopeSplitOptions(
4598
4825
  definition.current.actions(feature),
4826
+ feature,
4827
+ lang
4828
+ );
4829
+ const actionsWithWorktreeMove = withExistingWorktreeMoveOption(
4830
+ actionsWithScopeSplit,
4831
+ feature,
4832
+ lang
4833
+ );
4834
+ const currentActions = withUserRequestReplanOption(
4835
+ actionsWithWorktreeMove,
4599
4836
  lang
4600
4837
  );
4601
4838
  const actions = applyApprovalPolicy(
@@ -4737,9 +4974,22 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
4737
4974
  }
4738
4975
  }
4739
4976
  var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
4977
+ var WORKTREE_MARKER = `${path12.sep}.worktrees${path12.sep}`;
4740
4978
  function resetContextGitCaches() {
4741
4979
  GIT_WORKTREE_CACHE.clear();
4742
4980
  }
4981
+ function isManagedWorktreePath(cwd) {
4982
+ if (!cwd) return false;
4983
+ const normalized = path12.resolve(cwd);
4984
+ return normalized.includes(WORKTREE_MARKER);
4985
+ }
4986
+ function resolveProjectRootFromGitCwd(cwd) {
4987
+ const normalized = path12.resolve(cwd);
4988
+ const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
4989
+ if (markerIndex <= 0) return normalized;
4990
+ const projectRoot = normalized.slice(0, markerIndex);
4991
+ return projectRoot || normalized;
4992
+ }
4743
4993
  function getGitTopLevel(ctx, cwd) {
4744
4994
  try {
4745
4995
  return ctx.cmd.execFileSync("git", ["rev-parse", "--show-toplevel"], {
@@ -4843,6 +5093,17 @@ function isExpectedFeatureBranch(branchName, issueNumber, slug, folderName) {
4843
5093
  const rest = match[1];
4844
5094
  return rest === slug || rest === folderName;
4845
5095
  }
5096
+ var FEATURE_SCOPE_SPLIT_TASK_THRESHOLD = 40;
5097
+ var FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD = 1200;
5098
+ var FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD = 80;
5099
+ var FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD = 2500;
5100
+ function countDocumentLines(content) {
5101
+ if (!content) return 0;
5102
+ const lines = content.split(/\r?\n/);
5103
+ if (lines.length === 0) return 0;
5104
+ if (lines[lines.length - 1] === "") return lines.length - 1;
5105
+ return lines.length;
5106
+ }
4846
5107
  function escapeRegExp(value) {
4847
5108
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4848
5109
  }
@@ -5033,7 +5294,13 @@ function isExplicitZeroFindingsEntry(value) {
5033
5294
  if (!trimmed) return false;
5034
5295
  return /^0+\s*findings?\b/.test(trimmed) || /^no\s+findings?\b/.test(trimmed) || /^findings?\s*[::]\s*0+\b/.test(trimmed) || /^지적\s*사항?\s*[::]?\s*0+\b/.test(trimmed) || /^지적\s*없음\b/.test(trimmed);
5035
5296
  }
5036
- function hasPrePrReviewLogQuality(content) {
5297
+ function isNoCommandsPlaceholder(value) {
5298
+ const trimmed = value.trim().toLowerCase();
5299
+ if (!trimmed) return true;
5300
+ return /^none(?:\s+specified)?\b/.test(trimmed) || /^no\s+commands?\b/.test(trimmed) || /^n\/a\b/.test(trimmed) || /^없음\b/.test(trimmed) || /^명령(?:어)?\s*없음\b/.test(trimmed);
5301
+ }
5302
+ function hasPrePrReviewLogQuality(content, options) {
5303
+ const requireCommandsExecuted = !!options?.requireCommandsExecuted;
5037
5304
  const sections = splitReviewLogSections(content, PRE_PR_REVIEW_LOG_HEADER);
5038
5305
  for (const section of sections) {
5039
5306
  const summaryEntries = collectStructuredReviewEntries(section, [
@@ -5074,16 +5341,28 @@ function hasPrePrReviewLogQuality(content) {
5074
5341
  "\uD14C\uC2A4\uD2B8 \uC2E4\uD589"
5075
5342
  ]);
5076
5343
  if (!hasValidReviewLogEntries(testsRunEntries)) continue;
5344
+ if (requireCommandsExecuted) {
5345
+ const commandsEntries = collectStructuredReviewEntries(section, [
5346
+ "Commands Executed",
5347
+ "Executed Commands",
5348
+ "\uC2E4\uD589 \uBA85\uB839\uC5B4",
5349
+ "\uC2E4\uD589 \uBA85\uB839"
5350
+ ]);
5351
+ const hasCommandsExecuted = commandsEntries.map((entry) => entry.trim()).some(
5352
+ (entry) => entry.length > 0 && !isReviewDraftPlaceholder(entry) && !isPlaceholderReviewEvidence(entry) && !isNoCommandsPlaceholder(entry)
5353
+ );
5354
+ if (!hasCommandsExecuted) continue;
5355
+ }
5077
5356
  return true;
5078
5357
  }
5079
5358
  return false;
5080
5359
  }
5081
5360
  var PRE_PR_REVIEW_LOG_HEADER = /^##\s+(?:Pre-PR Review Log|PR 전 리뷰 로그)\b.*$/gim;
5082
5361
  var PR_REVIEW_LOG_HEADER = /^##\s+(?:PR Review Log|PR 리뷰 로그)\b.*$/gim;
5083
- async function hasPrePrReviewLogEvidence(ctx, candidatePath) {
5362
+ async function hasPrePrReviewLogEvidence(ctx, candidatePath, options) {
5084
5363
  try {
5085
5364
  const content = await ctx.fs.readFile(candidatePath, "utf-8");
5086
- return hasPrePrReviewLogQuality(content);
5365
+ return hasPrePrReviewLogQuality(content, options);
5087
5366
  } catch {
5088
5367
  return false;
5089
5368
  }
@@ -5130,12 +5409,19 @@ async function isPrePrEvidenceProvided(ctx, rawValue, policy, context) {
5130
5409
  rawValue,
5131
5410
  context
5132
5411
  );
5412
+ if (policy.enforceExecutionEvidence && !existingEvidencePath) {
5413
+ return false;
5414
+ }
5133
5415
  if (policy.evidenceMode !== "path_required") {
5134
5416
  if (!existingEvidencePath) return true;
5135
- return hasPrePrReviewLogEvidence(ctx, existingEvidencePath);
5417
+ return hasPrePrReviewLogEvidence(ctx, existingEvidencePath, {
5418
+ requireCommandsExecuted: policy.enforceExecutionEvidence
5419
+ });
5136
5420
  }
5137
5421
  if (!existingEvidencePath) return false;
5138
- return hasPrePrReviewLogEvidence(ctx, existingEvidencePath);
5422
+ return hasPrePrReviewLogEvidence(ctx, existingEvidencePath, {
5423
+ requireCommandsExecuted: policy.enforceExecutionEvidence
5424
+ });
5139
5425
  }
5140
5426
  async function isPrReviewEvidenceProvided(ctx, rawValue, context) {
5141
5427
  if (isPlaceholderReviewEvidence(rawValue)) return false;
@@ -5203,6 +5489,31 @@ function resetContextParseCaches() {
5203
5489
  PR_REMOTE_STATUS_CACHE.clear();
5204
5490
  FEATURE_WORKTREE_CACHE.clear();
5205
5491
  }
5492
+ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderName) {
5493
+ const projectRoot = resolveProjectRootFromGitCwd(projectGitCwd);
5494
+ const names = [`feat-${issueNumber}-${slug}`, `feat-${issueNumber}-${folderName}`];
5495
+ const seen = /* @__PURE__ */ new Set();
5496
+ const out = [];
5497
+ for (const name of names) {
5498
+ const candidate = path12.resolve(projectRoot, ".worktrees", name);
5499
+ if (seen.has(candidate)) continue;
5500
+ seen.add(candidate);
5501
+ out.push(candidate);
5502
+ }
5503
+ return out;
5504
+ }
5505
+ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, folderName) {
5506
+ for (const candidate of getExpectedWorktreeCandidates(
5507
+ projectGitCwd,
5508
+ issueNumber,
5509
+ slug,
5510
+ folderName
5511
+ )) {
5512
+ if (!fs9.existsSync(candidate)) continue;
5513
+ return candidate;
5514
+ }
5515
+ return void 0;
5516
+ }
5206
5517
  function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folderName) {
5207
5518
  const expectedBranches = [
5208
5519
  `feat/${issueNumber}-${slug}`,
@@ -5221,6 +5532,21 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
5221
5532
  branch: getCurrentBranch(ctx, foundPath) || branchName
5222
5533
  };
5223
5534
  }
5535
+ const expectedBranchesSet = new Set(expectedBranches);
5536
+ for (const candidate of getExpectedWorktreeCandidates(
5537
+ projectGitCwd,
5538
+ issueNumber,
5539
+ slug,
5540
+ folderName
5541
+ )) {
5542
+ if (!fs9.existsSync(candidate)) continue;
5543
+ const branchName = getCurrentBranch(ctx, candidate);
5544
+ if (!expectedBranchesSet.has(branchName)) continue;
5545
+ return {
5546
+ cwd: candidate,
5547
+ branch: branchName
5548
+ };
5549
+ }
5224
5550
  return void 0;
5225
5551
  }
5226
5552
  function toUpperToken(value) {
@@ -5466,6 +5792,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5466
5792
  const specPath = path12.join(featurePath, "spec.md");
5467
5793
  const planPath = path12.join(featurePath, "plan.md");
5468
5794
  const tasksPath = path12.join(featurePath, "tasks.md");
5795
+ const decisionsPath = path12.join(featurePath, "decisions.md");
5469
5796
  const issueDocPath = path12.join(featurePath, "issue.md");
5470
5797
  const prDocPath = path12.join(featurePath, "pr.md");
5471
5798
  let specStatus;
@@ -5486,25 +5813,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5486
5813
  let effectiveProjectBranch = context.projectBranch;
5487
5814
  let effectiveProjectBranchAvailable = context.projectBranchAvailable;
5488
5815
  if (effectiveProjectGitCwd && issueNumber) {
5489
- const alreadyExpected = isExpectedFeatureBranch(
5490
- effectiveProjectBranch,
5816
+ const worktree = resolveFeatureWorktreePath(
5817
+ ctx,
5818
+ effectiveProjectGitCwd,
5491
5819
  issueNumber,
5492
5820
  slug,
5493
5821
  folderName
5494
5822
  );
5495
- if (!alreadyExpected) {
5496
- const worktree = resolveFeatureWorktreePath(
5497
- ctx,
5498
- effectiveProjectGitCwd,
5499
- issueNumber,
5500
- slug,
5501
- folderName
5502
- );
5503
- if (worktree) {
5504
- effectiveProjectGitCwd = worktree.cwd;
5505
- effectiveProjectBranch = worktree.branch;
5506
- effectiveProjectBranchAvailable = true;
5507
- }
5823
+ if (worktree) {
5824
+ effectiveProjectGitCwd = worktree.cwd;
5825
+ effectiveProjectBranch = worktree.branch;
5826
+ effectiveProjectBranchAvailable = true;
5508
5827
  }
5509
5828
  }
5510
5829
  let planStatus;
@@ -5514,6 +5833,12 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5514
5833
  const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
5515
5834
  planStatus = parseDocStatus(statusValue);
5516
5835
  }
5836
+ const decisionsExists = await ctx.fs.pathExists(decisionsPath);
5837
+ let decisionsLineCount = 0;
5838
+ if (decisionsExists) {
5839
+ const content = await ctx.fs.readFile(decisionsPath, "utf-8");
5840
+ decisionsLineCount = countDocumentLines(content);
5841
+ }
5517
5842
  const tasksExists = await ctx.fs.pathExists(tasksPath);
5518
5843
  const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
5519
5844
  let activeTask;
@@ -5650,25 +5975,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5650
5975
  prReviewDecisionProvided = !isPlaceholderReviewEvidence(prReviewDecisionValue) && hasStructuredReviewDecision(prReviewDecisionValue);
5651
5976
  }
5652
5977
  if (effectiveProjectGitCwd && issueNumber) {
5653
- const alreadyExpected = isExpectedFeatureBranch(
5654
- effectiveProjectBranch,
5978
+ const worktree = resolveFeatureWorktreePath(
5979
+ ctx,
5980
+ effectiveProjectGitCwd,
5655
5981
  issueNumber,
5656
5982
  slug,
5657
5983
  folderName
5658
5984
  );
5659
- if (!alreadyExpected) {
5660
- const worktree = resolveFeatureWorktreePath(
5661
- ctx,
5662
- effectiveProjectGitCwd,
5663
- issueNumber,
5664
- slug,
5665
- folderName
5666
- );
5667
- if (worktree) {
5668
- effectiveProjectGitCwd = worktree.cwd;
5669
- effectiveProjectBranch = worktree.branch;
5670
- effectiveProjectBranchAvailable = true;
5671
- }
5985
+ if (worktree) {
5986
+ effectiveProjectGitCwd = worktree.cwd;
5987
+ effectiveProjectBranch = worktree.branch;
5988
+ effectiveProjectBranchAvailable = true;
5672
5989
  }
5673
5990
  }
5674
5991
  const issueDocExists = await ctx.fs.pathExists(issueDocPath);
@@ -5701,6 +6018,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5701
6018
  if (workflowPolicy.requireMerge && prStatus === "Review" && prLink && effectiveProjectGitCwd) {
5702
6019
  prRemote = resolvePrRemoteStatus(ctx, prLink, effectiveProjectGitCwd) || void 0;
5703
6020
  }
6021
+ const scopeSplitReasons = [];
6022
+ if (tasksSummary.total >= FEATURE_SCOPE_SPLIT_TASK_THRESHOLD) {
6023
+ scopeSplitReasons.push("task_count");
6024
+ }
6025
+ if (decisionsLineCount >= FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD) {
6026
+ scopeSplitReasons.push("decisions_lines");
6027
+ }
6028
+ const scopeSplitSuggested = scopeSplitReasons.length > 0;
6029
+ const scopeSplitRecommendFour = tasksSummary.total >= FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD || decisionsLineCount >= FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD;
6030
+ const scopeSplitRecommendation = !scopeSplitSuggested ? "none" : scopeSplitRecommendFour ? "split_4" : "split_2";
5704
6031
  const warnings = [];
5705
6032
  if (effectiveProjectBranchAvailable === false) {
5706
6033
  warnings.push(tr(lang, "warnings", "projectBranchUnavailable"));
@@ -5711,6 +6038,18 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5711
6038
  slug,
5712
6039
  folderName
5713
6040
  );
6041
+ const projectInManagedWorktree = isManagedWorktreePath(effectiveProjectGitCwd);
6042
+ const expectedWorktreePath = effectiveProjectGitCwd && issueNumber ? resolveExistingExpectedWorktreePath(
6043
+ effectiveProjectGitCwd,
6044
+ issueNumber,
6045
+ slug,
6046
+ folderName
6047
+ ) : void 0;
6048
+ if (workflowPolicy.requireBranch && tasksSummary.total > tasksSummary.done && onExpectedBranch && !projectInManagedWorktree) {
6049
+ warnings.push(tr(lang, "warnings", "projectExpectedBranchOnMainWorkspace"));
6050
+ } else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
6051
+ warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
6052
+ }
5714
6053
  const relativeFeaturePathFromDocs = path12.relative(
5715
6054
  context.docsDir,
5716
6055
  featurePath
@@ -5835,6 +6174,19 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5835
6174
  if (tasksExists && !tasksDocStatusFieldExists) {
5836
6175
  warnings.push(tr(lang, "warnings", "legacyTasksDocStatusField"));
5837
6176
  }
6177
+ if (scopeSplitSuggested && tasksSummary.total > tasksSummary.done) {
6178
+ warnings.push(
6179
+ tr(lang, "warnings", "featureScopeSplitSuggested", {
6180
+ taskCount: tasksSummary.total,
6181
+ decisionsLineCount,
6182
+ taskThreshold: FEATURE_SCOPE_SPLIT_TASK_THRESHOLD,
6183
+ decisionsThreshold: FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD,
6184
+ recommendFourTaskThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD,
6185
+ recommendFourDecisionsThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD,
6186
+ recommendedIssues: scopeSplitRecommendation === "split_4" ? 4 : 2
6187
+ })
6188
+ );
6189
+ }
5838
6190
  if (docsEverCommitted && docsHasUncommittedChanges) {
5839
6191
  warnings.push(tr(lang, "warnings", "docsUncommittedChanges"));
5840
6192
  }
@@ -5946,6 +6298,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5946
6298
  planStatus,
5947
6299
  tasksDocStatus,
5948
6300
  tasks: tasksSummary,
6301
+ scopeSplit: {
6302
+ suggested: scopeSplitSuggested,
6303
+ reasons: scopeSplitReasons,
6304
+ recommendation: scopeSplitRecommendation,
6305
+ taskCount: tasksSummary.total,
6306
+ decisionsLineCount,
6307
+ suggestTaskCountThreshold: FEATURE_SCOPE_SPLIT_TASK_THRESHOLD,
6308
+ suggestDecisionsLineCountThreshold: FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD,
6309
+ recommendSplitFourTaskCountThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD,
6310
+ recommendSplitFourDecisionsLineCountThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD
6311
+ },
5949
6312
  activeTask,
5950
6313
  lastDoneTask,
5951
6314
  nextTodoTask,
@@ -5972,6 +6335,8 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5972
6335
  docsGitCwd: context.docsGitCwd,
5973
6336
  projectGitCwd: effectiveProjectGitCwd,
5974
6337
  onExpectedBranch,
6338
+ projectInManagedWorktree,
6339
+ expectedWorktreePath,
5975
6340
  docsEverCommitted,
5976
6341
  docsHasUncommittedChanges,
5977
6342
  projectHasUncommittedChanges,
@@ -6545,6 +6910,16 @@ function normalizeSkillList2(raw) {
6545
6910
  }
6546
6911
  return [...deduped];
6547
6912
  }
6913
+ function normalizeStringList(raw) {
6914
+ if (!Array.isArray(raw)) return [];
6915
+ const deduped = /* @__PURE__ */ new Set();
6916
+ for (const item of raw) {
6917
+ const value = String(item || "").trim();
6918
+ if (!value) continue;
6919
+ deduped.add(value);
6920
+ }
6921
+ return [...deduped];
6922
+ }
6548
6923
  function normalizeDecisionEnumList2(raw) {
6549
6924
  if (!Array.isArray(raw)) return [];
6550
6925
  const deduped = /* @__PURE__ */ new Set();
@@ -6578,6 +6953,7 @@ async function backfillMissingConfigDefaults(docsDir) {
6578
6953
  }
6579
6954
  const workflow = raw.workflow;
6580
6955
  setIfMissing(workflow, "mode", "github", "workflow.mode");
6956
+ setIfMissing(workflow, "requireWorktree", false, "workflow.requireWorktree");
6581
6957
  setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
6582
6958
  setIfMissing(workflow, "taskCommitGate", "warn", "workflow.taskCommitGate");
6583
6959
  if (!isPlainObject(workflow.auto)) {
@@ -6632,6 +7008,24 @@ async function backfillMissingConfigDefaults(docsDir) {
6632
7008
  changedPaths.push("workflow.prePrReview.decisionEnum");
6633
7009
  }
6634
7010
  }
7011
+ setIfMissing(
7012
+ prePrReview,
7013
+ "enforceExecutionEvidence",
7014
+ true,
7015
+ "workflow.prePrReview.enforceExecutionEvidence"
7016
+ );
7017
+ if (prePrReview.executionCommandPrefixes === void 0) {
7018
+ prePrReview.executionCommandPrefixes = [];
7019
+ changedPaths.push("workflow.prePrReview.executionCommandPrefixes");
7020
+ } else {
7021
+ const normalizedExecutionCommandPrefixes = normalizeStringList(
7022
+ prePrReview.executionCommandPrefixes
7023
+ );
7024
+ if (JSON.stringify(normalizedExecutionCommandPrefixes) !== JSON.stringify(prePrReview.executionCommandPrefixes)) {
7025
+ prePrReview.executionCommandPrefixes = normalizedExecutionCommandPrefixes;
7026
+ changedPaths.push("workflow.prePrReview.executionCommandPrefixes");
7027
+ }
7028
+ }
6635
7029
  if (!isPlainObject(raw.pr)) {
6636
7030
  raw.pr = {};
6637
7031
  changedPaths.push("pr");
@@ -6966,12 +7360,18 @@ var BUILTIN_DOC_DEFINITIONS = [
6966
7360
  id: "create-pr",
6967
7361
  title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
6968
7362
  relativePath: (_, lang) => path12.join(lang, "common", "agents", "skills", "create-pr.md")
7363
+ },
7364
+ {
7365
+ id: "split-feature",
7366
+ title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
7367
+ relativePath: (_, lang) => path12.join(lang, "common", "agents", "skills", "split-feature.md")
6969
7368
  }
6970
7369
  ];
6971
7370
  var DOC_FOLLOWUPS = {
6972
7371
  agents: [
6973
7372
  "create-feature",
6974
7373
  "execute-task",
7374
+ "split-feature",
6975
7375
  "git-workflow",
6976
7376
  "create-issue",
6977
7377
  "issue-doc",
@@ -6982,9 +7382,10 @@ var DOC_FOLLOWUPS = {
6982
7382
  "issue-doc": [],
6983
7383
  "pr-doc": [],
6984
7384
  "create-feature": ["execute-task"],
6985
- "execute-task": ["git-workflow"],
7385
+ "execute-task": ["git-workflow", "split-feature"],
6986
7386
  "create-issue": ["issue-doc"],
6987
- "create-pr": ["pr-doc"]
7387
+ "create-pr": ["pr-doc"],
7388
+ "split-feature": []
6988
7389
  };
6989
7390
  var CATEGORY_DOC_MAP = {
6990
7391
  spec_write: ["agents"],
@@ -7002,6 +7403,7 @@ var CATEGORY_DOC_MAP = {
7002
7403
  pr_create: ["create-pr", "pr-doc", "git-workflow"],
7003
7404
  pr_status_update: ["create-pr"],
7004
7405
  code_review: ["create-pr"],
7406
+ feature_scope_split: ["split-feature", "execute-task"],
7005
7407
  worktree_cleanup: ["git-workflow"],
7006
7408
  user_request_replan: ["agents", "execute-task"]
7007
7409
  };
@@ -7019,6 +7421,9 @@ function normalizeBuiltinDocId(input) {
7019
7421
  if (normalized === "execute-task") return "execute-task";
7020
7422
  if (normalized === "create-issue") return "create-issue";
7021
7423
  if (normalized === "create-pr") return "create-pr";
7424
+ if (normalized === "split-feature" || normalized === "feature-split") {
7425
+ return "split-feature";
7426
+ }
7022
7427
  if (normalized === "agents") return "agents";
7023
7428
  return null;
7024
7429
  }
@@ -7107,6 +7512,7 @@ var ACTION_DETAIL_KEY_BY_CATEGORY = {
7107
7512
  pr_create: "context.actionDetail.prCreate",
7108
7513
  pr_status_update: "context.actionDetail.prStatusUpdate",
7109
7514
  code_review: "context.actionDetail.codeReview",
7515
+ feature_scope_split: "context.actionDetail.featureScopeSplit",
7110
7516
  worktree_cleanup: "context.actionDetail.worktreeCleanup",
7111
7517
  pr_metadata_migrate: "context.actionDetail.prMetadataMigrate",
7112
7518
  user_request_replan: "context.actionDetail.userRequestReplan",
@@ -13877,6 +14283,22 @@ function normalizeCommandsExecuted(value) {
13877
14283
  if (!Array.isArray(value)) return [];
13878
14284
  return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
13879
14285
  }
14286
+ function normalizeGitPath3(value) {
14287
+ return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
14288
+ }
14289
+ function parseGitPathList(stdout) {
14290
+ return stdout.split("\n").map((entry) => normalizeGitPath3(entry)).filter(Boolean);
14291
+ }
14292
+ function uniquePaths(values) {
14293
+ const seen = /* @__PURE__ */ new Set();
14294
+ const out = [];
14295
+ for (const value of values) {
14296
+ if (!value || seen.has(value)) continue;
14297
+ seen.add(value);
14298
+ out.push(value);
14299
+ }
14300
+ return out;
14301
+ }
13880
14302
  function normalizeEvidenceFiles(value) {
13881
14303
  if (!Array.isArray(value)) {
13882
14304
  throw createCliError(
@@ -13920,6 +14342,10 @@ var PrePrReviewValidator = class {
13920
14342
  this.ctx = ctx;
13921
14343
  }
13922
14344
  async validateEvidence(evidencePath, projectRoot) {
14345
+ const result = await this.validateEvidenceWithScope(evidencePath, projectRoot);
14346
+ return result.evidence;
14347
+ }
14348
+ async validateEvidenceWithScope(evidencePath, projectRoot) {
13923
14349
  const fullPath = path12.resolve(evidencePath);
13924
14350
  if (!await fs.pathExists(fullPath)) {
13925
14351
  throw createCliError(
@@ -13958,14 +14384,18 @@ var PrePrReviewValidator = class {
13958
14384
  );
13959
14385
  }
13960
14386
  }
13961
- const changedFiles = await this.getChangedFiles(projectRoot);
14387
+ const scope = await this.collectReviewScope(projectRoot);
14388
+ const changedFiles = uniquePaths([
14389
+ ...scope.mainChangedFiles,
14390
+ ...scope.worktreeChangedFiles
14391
+ ]);
13962
14392
  const reviewedFiles = new Set(
13963
14393
  normalizedEvidence.files.map(
13964
14394
  (f) => path12.relative(
13965
14395
  projectRoot,
13966
14396
  path12.resolve(projectRoot, f.path)
13967
14397
  )
13968
- )
14398
+ ).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
13969
14399
  );
13970
14400
  const missingFiles = changedFiles.filter((f) => !reviewedFiles.has(f));
13971
14401
  if (missingFiles.length > 0) {
@@ -13975,40 +14405,120 @@ var PrePrReviewValidator = class {
13975
14405
  ${missingFiles.map((f) => `- ${f}`).join("\n")}`
13976
14406
  );
13977
14407
  }
13978
- return normalizedEvidence;
14408
+ return {
14409
+ evidence: normalizedEvidence,
14410
+ scope
14411
+ };
13979
14412
  }
13980
- async getChangedFiles(cwd) {
13981
- const branchResult = await this.ctx.cmd.runAsync("git", ["rev-parse", "--abbrev-ref", "origin/HEAD"], { cwd }).catch(() => null);
13982
- let baseBranch = "origin/main";
13983
- if (branchResult && branchResult.code === 0 && branchResult.stdout.trim()) {
13984
- baseBranch = branchResult.stdout.trim().replace(/^origin\//, "");
14413
+ async collectReviewScope(cwd) {
14414
+ const baseRef = await this.resolveBaseRef(cwd);
14415
+ const mergeBase = await this.resolveMergeBase(cwd, baseRef);
14416
+ const mainDiff = await this.getMainChangedFiles(cwd, mergeBase);
14417
+ const worktreeDiff = await this.getWorktreeChangedFiles(cwd);
14418
+ if (!mainDiff.ok && !worktreeDiff.ok) {
14419
+ throw createCliError(
14420
+ "VALIDATION_FAILED",
14421
+ "Unable to determine changed files from git diff. Ensure this is a git repository with accessible history."
14422
+ );
13985
14423
  }
13986
- let diffTarget = "HEAD~1";
14424
+ return {
14425
+ baseRef,
14426
+ mergeBase,
14427
+ mainRange: mainDiff.rangeLabel,
14428
+ mainChangedFiles: mainDiff.files,
14429
+ worktreeChangedFiles: worktreeDiff.files
14430
+ };
14431
+ }
14432
+ async resolveBaseRef(cwd) {
13987
14433
  try {
13988
- const mergeBaseRes = await this.ctx.cmd.runAsync(
14434
+ const result = await this.ctx.cmd.runAsync(
13989
14435
  "git",
13990
- ["merge-base", "HEAD", baseBranch],
14436
+ ["rev-parse", "--abbrev-ref", "origin/HEAD"],
13991
14437
  { cwd }
13992
14438
  );
13993
- if (mergeBaseRes.code === 0 && mergeBaseRes.stdout.trim()) {
13994
- diffTarget = mergeBaseRes.stdout.trim();
14439
+ if (result.code === 0) {
14440
+ const value = result.stdout.trim();
14441
+ if (value && value !== "origin/HEAD") {
14442
+ return value;
14443
+ }
13995
14444
  }
13996
14445
  } catch {
13997
14446
  }
13998
- const targets = [diffTarget, "HEAD~1", ""];
13999
- const seen = /* @__PURE__ */ new Set();
14000
- for (const target of targets) {
14001
- if (seen.has(target)) continue;
14002
- seen.add(target);
14003
- const args = target ? ["diff", "--name-only", target] : ["diff", "--name-only"];
14004
- const diffResult = await this.ctx.cmd.runAsync("git", args, { cwd });
14005
- if (diffResult.code !== 0) continue;
14006
- return diffResult.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
14447
+ return "origin/main";
14448
+ }
14449
+ async resolveMergeBase(cwd, baseRef) {
14450
+ const candidates = uniquePaths([
14451
+ baseRef,
14452
+ baseRef.replace(/^origin\//, ""),
14453
+ "origin/main",
14454
+ "main"
14455
+ ]);
14456
+ for (const candidate of candidates) {
14457
+ if (!candidate) continue;
14458
+ try {
14459
+ const result = await this.ctx.cmd.runAsync(
14460
+ "git",
14461
+ ["merge-base", "HEAD", candidate],
14462
+ { cwd }
14463
+ );
14464
+ if (result.code !== 0) continue;
14465
+ const mergeBase = normalizeGitPath3(result.stdout);
14466
+ if (mergeBase) return mergeBase;
14467
+ } catch {
14468
+ }
14007
14469
  }
14008
- throw createCliError(
14009
- "VALIDATION_FAILED",
14010
- "Unable to determine changed files from git diff. Ensure this is a git repository with accessible history."
14011
- );
14470
+ return null;
14471
+ }
14472
+ async getMainChangedFiles(cwd, mergeBase) {
14473
+ const ranges = uniquePaths([
14474
+ mergeBase ? `${mergeBase}..HEAD` : "",
14475
+ "HEAD~1..HEAD",
14476
+ "HEAD^..HEAD"
14477
+ ]);
14478
+ for (const range of ranges) {
14479
+ if (!range) continue;
14480
+ try {
14481
+ const result = await this.ctx.cmd.runAsync(
14482
+ "git",
14483
+ ["diff", "--name-only", range],
14484
+ { cwd }
14485
+ );
14486
+ if (result.code !== 0) continue;
14487
+ return {
14488
+ ok: true,
14489
+ rangeLabel: range,
14490
+ files: uniquePaths(parseGitPathList(result.stdout))
14491
+ };
14492
+ } catch {
14493
+ }
14494
+ }
14495
+ return {
14496
+ ok: false,
14497
+ rangeLabel: mergeBase ? `${mergeBase}..HEAD` : "HEAD~1..HEAD",
14498
+ files: []
14499
+ };
14500
+ }
14501
+ async getWorktreeChangedFiles(cwd) {
14502
+ let hasSuccessfulCommand = false;
14503
+ const files = [];
14504
+ const commands = [
14505
+ ["diff", "--name-only"],
14506
+ ["diff", "--name-only", "--cached"],
14507
+ ["ls-files", "--others", "--exclude-standard"]
14508
+ ];
14509
+ for (const args of commands) {
14510
+ try {
14511
+ const result = await this.ctx.cmd.runAsync("git", args, { cwd });
14512
+ if (result.code !== 0) continue;
14513
+ hasSuccessfulCommand = true;
14514
+ files.push(...parseGitPathList(result.stdout));
14515
+ } catch {
14516
+ }
14517
+ }
14518
+ return {
14519
+ ok: hasSuccessfulCommand,
14520
+ files: uniquePaths(files)
14521
+ };
14012
14522
  }
14013
14523
  };
14014
14524
 
@@ -14075,6 +14585,9 @@ function upsertSpecLine(content, keys, preferredKey, value, anchorKeys) {
14075
14585
  function normalizePathForDoc(value) {
14076
14586
  return value.replace(/\\/g, "/");
14077
14587
  }
14588
+ function normalizeShellLikeCommand(value) {
14589
+ return value.trim().replace(/\s+/g, " ");
14590
+ }
14078
14591
  function getPreferredKeys(lang) {
14079
14592
  if (lang === "ko") {
14080
14593
  return {
@@ -14106,6 +14619,8 @@ function buildReportContent(input) {
14106
14619
  - Maintainability: ${f.review.maintainability}`;
14107
14620
  }).join("\n");
14108
14621
  }
14622
+ const mainScopeFiles = input.scope.mainChangedFiles.length > 0 ? input.scope.mainChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
14623
+ const worktreeScopeFiles = input.scope.worktreeChangedFiles.length > 0 ? input.scope.worktreeChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
14109
14624
  return `## Pre-PR Review Log (${input.date})
14110
14625
 
14111
14626
  - **Feature**: ${input.folderName}
@@ -14119,12 +14634,30 @@ ${commandsRun}
14119
14634
  - **Residual Risks**:
14120
14635
  - ${input.evidence.residualRisks}
14121
14636
 
14637
+ - **Review Scope**:
14638
+ - **Main Base Ref**: ${input.scope.baseRef}
14639
+ - **Main Merge Base**: ${input.scope.mergeBase ?? "unresolved"}
14640
+ - **Main Range**: ${input.scope.mainRange}
14641
+ - **Main Changed Files**:
14642
+ ${mainScopeFiles}
14643
+ - **Worktree Changed Files**:
14644
+ ${worktreeScopeFiles}
14645
+
14122
14646
  - **Findings (Changed Files)**:
14123
14647
  ${filesSection}
14124
14648
 
14125
14649
  - **Trace**: pre-pr-review command executed and synced with tasks.md
14126
14650
  `;
14127
14651
  }
14652
+ function createFallbackReviewScope() {
14653
+ return {
14654
+ baseRef: "origin/main",
14655
+ mergeBase: null,
14656
+ mainRange: "HEAD~1..HEAD",
14657
+ mainChangedFiles: [],
14658
+ worktreeChangedFiles: []
14659
+ };
14660
+ }
14128
14661
  function appendDecisionLog(content, entry) {
14129
14662
  const normalized = content.trimEnd();
14130
14663
  if (!normalized) return `${entry.trim()}
@@ -14207,6 +14740,12 @@ async function runPrePrReview(featureName, options) {
14207
14740
  "`--decision` must be one of: approve, changes_requested, blocked."
14208
14741
  );
14209
14742
  }
14743
+ if (!explicitDecision && feature.prePrReview.decisionOutcome && feature.prePrReview.decisionOutcome !== "approve") {
14744
+ throw createCliError(
14745
+ "INVALID_ARGUMENT",
14746
+ `Existing Pre-PR decision is "${feature.prePrReview.decisionOutcome}". Re-run with explicit --decision to avoid replaying the previous non-approve decision.`
14747
+ );
14748
+ }
14210
14749
  const decision = explicitDecision || feature.prePrReview.decisionOutcome || "approve";
14211
14750
  if (!policy.decisionEnum.includes(decision)) {
14212
14751
  throw createCliError(
@@ -14216,12 +14755,21 @@ async function runPrePrReview(featureName, options) {
14216
14755
  }
14217
14756
  const note = options.note?.trim() || (decision === "approve" ? "baseline review completed" : decision === "changes_requested" ? "follow-up changes are required before PR creation" : "blocked until prerequisite risk is resolved");
14218
14757
  let evidenceObj;
14758
+ let reviewScope = createFallbackReviewScope();
14759
+ if (policy.enforceExecutionEvidence && !options.evidence) {
14760
+ throw createCliError(
14761
+ "INVALID_ARGUMENT",
14762
+ "`--evidence <path>` is required when workflow.prePrReview.enforceExecutionEvidence=true."
14763
+ );
14764
+ }
14219
14765
  if (options.evidence) {
14220
14766
  const validator = new PrePrReviewValidator(ctx);
14221
- evidenceObj = await validator.validateEvidence(
14767
+ const validationResult = await validator.validateEvidenceWithScope(
14222
14768
  options.evidence,
14223
14769
  process.cwd()
14224
14770
  );
14771
+ evidenceObj = validationResult.evidence;
14772
+ reviewScope = validationResult.scope;
14225
14773
  } else if (policy.evidenceMode === "path_required") {
14226
14774
  throw createCliError(
14227
14775
  "INVALID_ARGUMENT",
@@ -14229,6 +14777,34 @@ async function runPrePrReview(featureName, options) {
14229
14777
  );
14230
14778
  } else {
14231
14779
  evidenceObj = DEFAULT_EVIDENCE_FOR_ANY_MODE;
14780
+ const validator = new PrePrReviewValidator(ctx);
14781
+ try {
14782
+ reviewScope = await validator.collectReviewScope(process.cwd());
14783
+ } catch {
14784
+ reviewScope = createFallbackReviewScope();
14785
+ }
14786
+ }
14787
+ if (policy.enforceExecutionEvidence) {
14788
+ const normalizedCommands = evidenceObj.commandsExecuted.map((entry) => normalizeShellLikeCommand(entry)).filter(Boolean);
14789
+ if (normalizedCommands.length === 0) {
14790
+ throw createCliError(
14791
+ "VALIDATION_FAILED",
14792
+ "Evidence must include non-empty commandsExecuted entries when workflow.prePrReview.enforceExecutionEvidence=true."
14793
+ );
14794
+ }
14795
+ if (policy.executionCommandPrefixes.length > 0) {
14796
+ const hasMatchedPrefix = normalizedCommands.some(
14797
+ (cmd) => policy.executionCommandPrefixes.some(
14798
+ (prefix) => cmd.toLowerCase().startsWith(normalizeShellLikeCommand(prefix).toLowerCase())
14799
+ )
14800
+ );
14801
+ if (!hasMatchedPrefix) {
14802
+ throw createCliError(
14803
+ "VALIDATION_FAILED",
14804
+ `Evidence commandsExecuted must include at least one command starting with workflow.prePrReview.executionCommandPrefixes: ${policy.executionCommandPrefixes.join(", ")}`
14805
+ );
14806
+ }
14807
+ }
14232
14808
  }
14233
14809
  const decisionsPath = path12.join(feature.path, "decisions.md");
14234
14810
  const decisionLogEntry = buildReportContent({
@@ -14238,7 +14814,8 @@ async function runPrePrReview(featureName, options) {
14238
14814
  note,
14239
14815
  fallback: policy.fallback,
14240
14816
  skills: policy.skills,
14241
- evidence: evidenceObj
14817
+ evidence: evidenceObj,
14818
+ scope: reviewScope
14242
14819
  });
14243
14820
  const decisionsContent = await fs.pathExists(decisionsPath) ? await fs.readFile(decisionsPath, "utf-8") : "";
14244
14821
  const nextDecisions = appendDecisionLog(decisionsContent, decisionLogEntry);
@@ -14285,6 +14862,7 @@ async function runPrePrReview(featureName, options) {
14285
14862
  decisionsPath: normalizePathForDoc(decisionsPath),
14286
14863
  evidencePath,
14287
14864
  decision,
14865
+ reviewScope,
14288
14866
  tasksUpdated: nextTasks !== tasksContent
14289
14867
  },
14290
14868
  null,