lee-spec-kit 0.6.34 → 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);
@@ -599,6 +599,8 @@ var koMessages = {
599
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)",
600
600
  standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
601
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.",
602
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',
603
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.',
604
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.",
@@ -613,9 +615,10 @@ var koMessages = {
613
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",
614
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)",
615
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)",
616
- 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)",
617
- 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)",
618
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)",
619
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)",
620
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)",
621
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)",
@@ -651,6 +654,8 @@ var koMessages = {
651
654
  // src/utils/locales/ko/warnings.ts
652
655
  var koWarnings = {
653
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.",
654
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)",
655
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.)",
656
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.",
@@ -1136,6 +1141,8 @@ var enMessages = {
1136
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).",
1137
1142
  standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
1138
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.",
1139
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',
1140
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.',
1141
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.",
@@ -1150,9 +1157,10 @@ var enMessages = {
1150
1157
  taskCommitGateReasonMismatchLastDone: "The latest project code commit does not match the last completed task",
1151
1158
  prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
1152
1159
  prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
1153
- 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)",
1154
- 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)",
1155
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)",
1156
1164
  prReviewEvidenceFieldMissing: "tasks.md is missing the `PR Review Evidence` field. Add `- **PR Review Evidence**: -` and continue. (CHECK required)",
1157
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)",
1158
1166
  prReviewDecisionFieldMissing: "tasks.md is missing the `PR Review Decision` field. Add `- **PR Review Decision**: -` and continue. (CHECK required)",
@@ -1188,6 +1196,8 @@ var enMessages = {
1188
1196
  // src/utils/locales/en/warnings.ts
1189
1197
  var enWarnings = {
1190
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.",
1191
1201
  docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
1192
1202
  docsPathIgnored: "Current feature docs path is ignored by git: {path} (docs commit detection may be limited).",
1193
1203
  docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Check commit message rules against the git-workflow guide.",
@@ -2460,6 +2470,7 @@ async function runInit(options) {
2460
2470
  docsRepo,
2461
2471
  workflow: {
2462
2472
  mode: workflowMode,
2473
+ requireWorktree: false,
2463
2474
  codeDirtyScope: "auto",
2464
2475
  taskCommitGate: "warn",
2465
2476
  auto: {
@@ -2469,7 +2480,9 @@ async function runInit(options) {
2469
2480
  skills: ["code-review-excellence"],
2470
2481
  fallback: "builtin-checklist",
2471
2482
  evidenceMode: "path_required",
2472
- decisionEnum: ["approve", "changes_requested", "blocked"]
2483
+ decisionEnum: ["approve", "changes_requested", "blocked"],
2484
+ enforceExecutionEvidence: true,
2485
+ executionCommandPrefixes: []
2473
2486
  }
2474
2487
  },
2475
2488
  pr: {
@@ -3199,6 +3212,7 @@ function resolveWorkflowPolicy(workflow) {
3199
3212
  mode,
3200
3213
  requireIssue: false,
3201
3214
  requireBranch: false,
3215
+ requireWorktree: false,
3202
3216
  requirePr: false,
3203
3217
  requireReview: false,
3204
3218
  requireMerge: false
@@ -3206,6 +3220,7 @@ function resolveWorkflowPolicy(workflow) {
3206
3220
  mode,
3207
3221
  requireIssue: true,
3208
3222
  requireBranch: true,
3223
+ requireWorktree: false,
3209
3224
  requirePr: true,
3210
3225
  requireReview: true,
3211
3226
  requireMerge: true
@@ -3216,6 +3231,9 @@ function resolveWorkflowPolicy(workflow) {
3216
3231
  if (typeof workflow?.requireBranch === "boolean") {
3217
3232
  policy.requireBranch = workflow.requireBranch;
3218
3233
  }
3234
+ if (typeof workflow?.requireWorktree === "boolean") {
3235
+ policy.requireWorktree = workflow.requireWorktree;
3236
+ }
3219
3237
  if (typeof workflow?.requirePr === "boolean") {
3220
3238
  policy.requirePr = workflow.requirePr;
3221
3239
  }
@@ -3228,6 +3246,9 @@ function resolveWorkflowPolicy(workflow) {
3228
3246
  if (!policy.requireIssue) {
3229
3247
  policy.requireBranch = false;
3230
3248
  }
3249
+ if (!policy.requireBranch) {
3250
+ policy.requireWorktree = false;
3251
+ }
3231
3252
  if (!policy.requirePr) {
3232
3253
  policy.requireReview = false;
3233
3254
  policy.requireMerge = false;
@@ -3284,6 +3305,16 @@ function normalizeDecisionEnumList(input) {
3284
3305
  }
3285
3306
  return [...deduped];
3286
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
+ }
3287
3318
  function resolvePrePrReviewPolicy(workflow) {
3288
3319
  const workflowPolicy = resolveWorkflowPolicy(workflow);
3289
3320
  const configured = workflow?.prePrReview;
@@ -3291,17 +3322,45 @@ function resolvePrePrReviewPolicy(workflow) {
3291
3322
  const configuredDecisionEnum = normalizeDecisionEnumList(
3292
3323
  configured?.decisionEnum
3293
3324
  );
3325
+ const configuredCommandPrefixes = normalizeCommandPrefixList(
3326
+ configured?.executionCommandPrefixes
3327
+ );
3294
3328
  const configuredEnabled = typeof configured?.enabled === "boolean" ? configured.enabled : workflowPolicy.requirePr;
3329
+ const configuredExecutionEvidence = typeof configured?.enforceExecutionEvidence === "boolean" ? configured.enforceExecutionEvidence : true;
3295
3330
  return {
3296
3331
  enabled: workflowPolicy.requirePr ? configuredEnabled : false,
3297
3332
  skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
3298
3333
  fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
3299
3334
  evidenceMode: configured?.evidenceMode === "any" ? "any" : "path_required",
3300
- 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 : []
3301
3338
  };
3302
3339
  }
3303
3340
 
3304
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
+ }
3305
3364
  function getCodeReviewPrompt(lang) {
3306
3365
  if (lang === "ko") {
3307
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.`;
@@ -3388,6 +3447,17 @@ function buildSelfCliCommand(args) {
3388
3447
  const base = [process.execPath, entry, "--no-banner", ...args];
3389
3448
  return base.map((arg) => toShellArg(arg)).join(" ");
3390
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
+ }
3391
3461
  function resolvePrePrReviewEvidencePath(feature) {
3392
3462
  const docsRoot = feature.git.docsGitCwd;
3393
3463
  const candidates = [];
@@ -3409,7 +3479,7 @@ function resolvePrePrReviewEvidencePath(feature) {
3409
3479
  const abs = path12.resolve(candidate);
3410
3480
  if (seen.has(abs)) continue;
3411
3481
  seen.add(abs);
3412
- if (!fs8.existsSync(abs)) continue;
3482
+ if (!fs9.existsSync(abs)) continue;
3413
3483
  if (!abs.toLowerCase().endsWith(".json")) continue;
3414
3484
  const rel = path12.relative(docsRoot, abs).replace(/\\/g, "/");
3415
3485
  if (rel && !rel.startsWith("../")) {
@@ -3866,6 +3936,60 @@ function getStepDefinitions(ctx) {
3866
3936
  current: {
3867
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),
3868
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
+ }
3869
3993
  if (f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f)) {
3870
3994
  if (f.git.docsHasUncommittedChanges) {
3871
3995
  return [
@@ -4221,15 +4345,30 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4221
4345
  type: "instruction",
4222
4346
  category: "pre_pr_review",
4223
4347
  requiresUserCheck: true,
4224
- message: tr(lang, "messages", "prePrReviewRun")
4348
+ message: getPrePrReviewPrompt(
4349
+ lang,
4350
+ prePrReviewPolicy.skills,
4351
+ prePrReviewPolicy.fallback
4352
+ )
4225
4353
  }
4226
4354
  ];
4227
4355
  }
4228
- const commandArgs = ["pre-pr-review", f.folderName];
4229
- if (f.type && f.type !== "single") {
4230
- 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
+ ];
4231
4370
  }
4232
- commandArgs.push("--evidence", evidencePath);
4371
+ const commandArgs = buildPrePrReviewCommandArgs(f, evidencePath);
4233
4372
  return [
4234
4373
  {
4235
4374
  type: "command",
@@ -4638,6 +4777,31 @@ function withFeatureScopeSplitOptions(actions, feature, lang) {
4638
4777
  }
4639
4778
  ];
4640
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
+ }
4641
4805
  function withUserRequestReplanOption(actions, lang) {
4642
4806
  if (actions.some((action) => action.category === "user_request_replan")) {
4643
4807
  return actions;
@@ -4662,8 +4826,13 @@ function resolveFeatureProgress(feature, stepDefinitions, lang, approval) {
4662
4826
  feature,
4663
4827
  lang
4664
4828
  );
4665
- const currentActions = withUserRequestReplanOption(
4829
+ const actionsWithWorktreeMove = withExistingWorktreeMoveOption(
4666
4830
  actionsWithScopeSplit,
4831
+ feature,
4832
+ lang
4833
+ );
4834
+ const currentActions = withUserRequestReplanOption(
4835
+ actionsWithWorktreeMove,
4667
4836
  lang
4668
4837
  );
4669
4838
  const actions = applyApprovalPolicy(
@@ -4805,9 +4974,22 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
4805
4974
  }
4806
4975
  }
4807
4976
  var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
4977
+ var WORKTREE_MARKER = `${path12.sep}.worktrees${path12.sep}`;
4808
4978
  function resetContextGitCaches() {
4809
4979
  GIT_WORKTREE_CACHE.clear();
4810
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
+ }
4811
4993
  function getGitTopLevel(ctx, cwd) {
4812
4994
  try {
4813
4995
  return ctx.cmd.execFileSync("git", ["rev-parse", "--show-toplevel"], {
@@ -5112,7 +5294,13 @@ function isExplicitZeroFindingsEntry(value) {
5112
5294
  if (!trimmed) return false;
5113
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);
5114
5296
  }
5115
- 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;
5116
5304
  const sections = splitReviewLogSections(content, PRE_PR_REVIEW_LOG_HEADER);
5117
5305
  for (const section of sections) {
5118
5306
  const summaryEntries = collectStructuredReviewEntries(section, [
@@ -5153,16 +5341,28 @@ function hasPrePrReviewLogQuality(content) {
5153
5341
  "\uD14C\uC2A4\uD2B8 \uC2E4\uD589"
5154
5342
  ]);
5155
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
+ }
5156
5356
  return true;
5157
5357
  }
5158
5358
  return false;
5159
5359
  }
5160
5360
  var PRE_PR_REVIEW_LOG_HEADER = /^##\s+(?:Pre-PR Review Log|PR 전 리뷰 로그)\b.*$/gim;
5161
5361
  var PR_REVIEW_LOG_HEADER = /^##\s+(?:PR Review Log|PR 리뷰 로그)\b.*$/gim;
5162
- async function hasPrePrReviewLogEvidence(ctx, candidatePath) {
5362
+ async function hasPrePrReviewLogEvidence(ctx, candidatePath, options) {
5163
5363
  try {
5164
5364
  const content = await ctx.fs.readFile(candidatePath, "utf-8");
5165
- return hasPrePrReviewLogQuality(content);
5365
+ return hasPrePrReviewLogQuality(content, options);
5166
5366
  } catch {
5167
5367
  return false;
5168
5368
  }
@@ -5209,12 +5409,19 @@ async function isPrePrEvidenceProvided(ctx, rawValue, policy, context) {
5209
5409
  rawValue,
5210
5410
  context
5211
5411
  );
5412
+ if (policy.enforceExecutionEvidence && !existingEvidencePath) {
5413
+ return false;
5414
+ }
5212
5415
  if (policy.evidenceMode !== "path_required") {
5213
5416
  if (!existingEvidencePath) return true;
5214
- return hasPrePrReviewLogEvidence(ctx, existingEvidencePath);
5417
+ return hasPrePrReviewLogEvidence(ctx, existingEvidencePath, {
5418
+ requireCommandsExecuted: policy.enforceExecutionEvidence
5419
+ });
5215
5420
  }
5216
5421
  if (!existingEvidencePath) return false;
5217
- return hasPrePrReviewLogEvidence(ctx, existingEvidencePath);
5422
+ return hasPrePrReviewLogEvidence(ctx, existingEvidencePath, {
5423
+ requireCommandsExecuted: policy.enforceExecutionEvidence
5424
+ });
5218
5425
  }
5219
5426
  async function isPrReviewEvidenceProvided(ctx, rawValue, context) {
5220
5427
  if (isPlaceholderReviewEvidence(rawValue)) return false;
@@ -5282,6 +5489,31 @@ function resetContextParseCaches() {
5282
5489
  PR_REMOTE_STATUS_CACHE.clear();
5283
5490
  FEATURE_WORKTREE_CACHE.clear();
5284
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
+ }
5285
5517
  function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folderName) {
5286
5518
  const expectedBranches = [
5287
5519
  `feat/${issueNumber}-${slug}`,
@@ -5300,6 +5532,21 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
5300
5532
  branch: getCurrentBranch(ctx, foundPath) || branchName
5301
5533
  };
5302
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
+ }
5303
5550
  return void 0;
5304
5551
  }
5305
5552
  function toUpperToken(value) {
@@ -5566,25 +5813,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5566
5813
  let effectiveProjectBranch = context.projectBranch;
5567
5814
  let effectiveProjectBranchAvailable = context.projectBranchAvailable;
5568
5815
  if (effectiveProjectGitCwd && issueNumber) {
5569
- const alreadyExpected = isExpectedFeatureBranch(
5570
- effectiveProjectBranch,
5816
+ const worktree = resolveFeatureWorktreePath(
5817
+ ctx,
5818
+ effectiveProjectGitCwd,
5571
5819
  issueNumber,
5572
5820
  slug,
5573
5821
  folderName
5574
5822
  );
5575
- if (!alreadyExpected) {
5576
- const worktree = resolveFeatureWorktreePath(
5577
- ctx,
5578
- effectiveProjectGitCwd,
5579
- issueNumber,
5580
- slug,
5581
- folderName
5582
- );
5583
- if (worktree) {
5584
- effectiveProjectGitCwd = worktree.cwd;
5585
- effectiveProjectBranch = worktree.branch;
5586
- effectiveProjectBranchAvailable = true;
5587
- }
5823
+ if (worktree) {
5824
+ effectiveProjectGitCwd = worktree.cwd;
5825
+ effectiveProjectBranch = worktree.branch;
5826
+ effectiveProjectBranchAvailable = true;
5588
5827
  }
5589
5828
  }
5590
5829
  let planStatus;
@@ -5736,25 +5975,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5736
5975
  prReviewDecisionProvided = !isPlaceholderReviewEvidence(prReviewDecisionValue) && hasStructuredReviewDecision(prReviewDecisionValue);
5737
5976
  }
5738
5977
  if (effectiveProjectGitCwd && issueNumber) {
5739
- const alreadyExpected = isExpectedFeatureBranch(
5740
- effectiveProjectBranch,
5978
+ const worktree = resolveFeatureWorktreePath(
5979
+ ctx,
5980
+ effectiveProjectGitCwd,
5741
5981
  issueNumber,
5742
5982
  slug,
5743
5983
  folderName
5744
5984
  );
5745
- if (!alreadyExpected) {
5746
- const worktree = resolveFeatureWorktreePath(
5747
- ctx,
5748
- effectiveProjectGitCwd,
5749
- issueNumber,
5750
- slug,
5751
- folderName
5752
- );
5753
- if (worktree) {
5754
- effectiveProjectGitCwd = worktree.cwd;
5755
- effectiveProjectBranch = worktree.branch;
5756
- effectiveProjectBranchAvailable = true;
5757
- }
5985
+ if (worktree) {
5986
+ effectiveProjectGitCwd = worktree.cwd;
5987
+ effectiveProjectBranch = worktree.branch;
5988
+ effectiveProjectBranchAvailable = true;
5758
5989
  }
5759
5990
  }
5760
5991
  const issueDocExists = await ctx.fs.pathExists(issueDocPath);
@@ -5807,6 +6038,18 @@ async function parseFeature(ctx, featurePath, type, context, options) {
5807
6038
  slug,
5808
6039
  folderName
5809
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
+ }
5810
6053
  const relativeFeaturePathFromDocs = path12.relative(
5811
6054
  context.docsDir,
5812
6055
  featurePath
@@ -6092,6 +6335,8 @@ async function parseFeature(ctx, featurePath, type, context, options) {
6092
6335
  docsGitCwd: context.docsGitCwd,
6093
6336
  projectGitCwd: effectiveProjectGitCwd,
6094
6337
  onExpectedBranch,
6338
+ projectInManagedWorktree,
6339
+ expectedWorktreePath,
6095
6340
  docsEverCommitted,
6096
6341
  docsHasUncommittedChanges,
6097
6342
  projectHasUncommittedChanges,
@@ -6665,6 +6910,16 @@ function normalizeSkillList2(raw) {
6665
6910
  }
6666
6911
  return [...deduped];
6667
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
+ }
6668
6923
  function normalizeDecisionEnumList2(raw) {
6669
6924
  if (!Array.isArray(raw)) return [];
6670
6925
  const deduped = /* @__PURE__ */ new Set();
@@ -6698,6 +6953,7 @@ async function backfillMissingConfigDefaults(docsDir) {
6698
6953
  }
6699
6954
  const workflow = raw.workflow;
6700
6955
  setIfMissing(workflow, "mode", "github", "workflow.mode");
6956
+ setIfMissing(workflow, "requireWorktree", false, "workflow.requireWorktree");
6701
6957
  setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
6702
6958
  setIfMissing(workflow, "taskCommitGate", "warn", "workflow.taskCommitGate");
6703
6959
  if (!isPlainObject(workflow.auto)) {
@@ -6752,6 +7008,24 @@ async function backfillMissingConfigDefaults(docsDir) {
6752
7008
  changedPaths.push("workflow.prePrReview.decisionEnum");
6753
7009
  }
6754
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
+ }
6755
7029
  if (!isPlainObject(raw.pr)) {
6756
7030
  raw.pr = {};
6757
7031
  changedPaths.push("pr");
@@ -14311,6 +14585,9 @@ function upsertSpecLine(content, keys, preferredKey, value, anchorKeys) {
14311
14585
  function normalizePathForDoc(value) {
14312
14586
  return value.replace(/\\/g, "/");
14313
14587
  }
14588
+ function normalizeShellLikeCommand(value) {
14589
+ return value.trim().replace(/\s+/g, " ");
14590
+ }
14314
14591
  function getPreferredKeys(lang) {
14315
14592
  if (lang === "ko") {
14316
14593
  return {
@@ -14463,6 +14740,12 @@ async function runPrePrReview(featureName, options) {
14463
14740
  "`--decision` must be one of: approve, changes_requested, blocked."
14464
14741
  );
14465
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
+ }
14466
14749
  const decision = explicitDecision || feature.prePrReview.decisionOutcome || "approve";
14467
14750
  if (!policy.decisionEnum.includes(decision)) {
14468
14751
  throw createCliError(
@@ -14473,6 +14756,12 @@ async function runPrePrReview(featureName, options) {
14473
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");
14474
14757
  let evidenceObj;
14475
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
+ }
14476
14765
  if (options.evidence) {
14477
14766
  const validator = new PrePrReviewValidator(ctx);
14478
14767
  const validationResult = await validator.validateEvidenceWithScope(
@@ -14495,6 +14784,28 @@ async function runPrePrReview(featureName, options) {
14495
14784
  reviewScope = createFallbackReviewScope();
14496
14785
  }
14497
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
+ }
14808
+ }
14498
14809
  const decisionsPath = path12.join(feature.path, "decisions.md");
14499
14810
  const decisionLogEntry = buildReportContent({
14500
14811
  folderName: feature.folderName,