lee-spec-kit 0.6.30 → 0.6.32

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
@@ -514,8 +514,8 @@ var ko = {
514
514
  reviewFixCommitIssueGuidance: "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(#{issueNumber}): <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)",
515
515
  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)",
516
516
  standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
517
- 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}") && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
518
- worktreeCleanupCommand: 'cd "{projectGitCwd}" && git worktree remove "{worktreePath}" && 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',
517
+ 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}"',
518
+ 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',
519
519
  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. \uCD5C\uC885 \uC2B9\uC778(OK)\uB3C4 \uBC18\uC601\uD558\uC138\uC694.',
520
520
  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 \uCD5C\uC885 \uC2B9\uC778(OK)\uC744 \uBC18\uC601\uD558\uC138\uC694.",
521
521
  finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uD0DC\uC2A4\uD06C\uB97C \uC218\uD589\uD558\uC138\uC694: "{title}" ({done}/{total}) \uC644\uB8CC \uC2DC \uACB0\uACFC/\uAC80\uC99D\uC744 \uACF5\uC720\uD558\uACE0 DONE \uCC98\uB9AC',
@@ -1025,8 +1025,8 @@ var en = {
1025
1025
  reviewFixCommitIssueGuidance: "Commit PR review fixes. Stage only review-fix files, then commit with `fix(#{issueNumber}): <review-fix-summary>`. `<review-fix-summary>` must describe review comments resolved in this commit (do not reuse task titles).",
1026
1026
  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).",
1027
1027
  standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
1028
- 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}") && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
1029
- worktreeCleanupCommand: 'cd "{projectGitCwd}" && git worktree remove "{worktreePath}" && 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',
1028
+ 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}"',
1029
+ 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',
1030
1030
  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 final approval (OK) as well.',
1031
1031
  tasksAllDoneButChecklist: "Proceed with remaining completion checklist items. Current progress: ({checked}/{total}) Mark items as [x] only after user confirmation and real verification. Record final approval (OK) as well.",
1032
1032
  finishDoingTask: 'Continue working on the current DOING/REVIEW task: "{title}" ({done}/{total}) After it is complete, share outcome/verification and mark it DONE',
@@ -2951,13 +2951,15 @@ function resolveWorkflowPolicy(workflow) {
2951
2951
  requireIssue: false,
2952
2952
  requireBranch: false,
2953
2953
  requirePr: false,
2954
- requireReview: false
2954
+ requireReview: false,
2955
+ requireMerge: false
2955
2956
  } : {
2956
2957
  mode,
2957
2958
  requireIssue: true,
2958
2959
  requireBranch: true,
2959
2960
  requirePr: true,
2960
- requireReview: true
2961
+ requireReview: true,
2962
+ requireMerge: true
2961
2963
  };
2962
2964
  if (typeof workflow?.requireIssue === "boolean") {
2963
2965
  policy.requireIssue = workflow.requireIssue;
@@ -2971,13 +2973,20 @@ function resolveWorkflowPolicy(workflow) {
2971
2973
  if (typeof workflow?.requireReview === "boolean") {
2972
2974
  policy.requireReview = workflow.requireReview;
2973
2975
  }
2976
+ if (typeof workflow?.requireMerge === "boolean") {
2977
+ policy.requireMerge = workflow.requireMerge;
2978
+ }
2974
2979
  if (!policy.requireIssue) {
2975
2980
  policy.requireBranch = false;
2976
2981
  }
2977
2982
  if (!policy.requirePr) {
2978
2983
  policy.requireReview = false;
2984
+ policy.requireMerge = false;
2979
2985
  } else if (policy.requireReview) {
2980
2986
  policy.requirePr = true;
2987
+ policy.requireMerge = true;
2988
+ } else if (policy.requireMerge) {
2989
+ policy.requirePr = true;
2981
2990
  }
2982
2991
  return policy;
2983
2992
  }
@@ -3076,7 +3085,7 @@ function isPrePrReviewSatisfied(feature, prePrReviewPolicy) {
3076
3085
  return true;
3077
3086
  }
3078
3087
  function isFeatureDone(feature, workflowPolicy, prePrReviewPolicy) {
3079
- return feature.specStatus === "Approved" && feature.planStatus === "Approved" && !feature.git.docsHasUncommittedChanges && !feature.git.projectHasUncommittedChanges && feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isTasksDocApproved(feature) && (!workflowPolicy.requireIssue || !!feature.issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured(feature) && !!feature.pr.link) && (!workflowPolicy.requireReview || feature.pr.status === "Approved") && isPrePrReviewSatisfied(feature, prePrReviewPolicy);
3088
+ return feature.specStatus === "Approved" && feature.planStatus === "Approved" && !feature.git.docsHasUncommittedChanges && !feature.git.projectHasUncommittedChanges && feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isTasksDocApproved(feature) && (!workflowPolicy.requireIssue || !!feature.issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured(feature) && !!feature.pr.link) && (!workflowPolicy.requireMerge || feature.pr.status === "Approved") && isPrePrReviewSatisfied(feature, prePrReviewPolicy);
3080
3089
  }
3081
3090
  function getPrReviewRemoteBlockReasons(feature, lang) {
3082
3091
  const remote = feature.pr.remote;
@@ -3981,10 +3990,10 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
3981
3990
  step: 14,
3982
3991
  name: tr(lang, "steps", "codeReview"),
3983
3992
  checklist: {
3984
- done: (f) => !workflowPolicy.requireReview || isPrMetadataConfigured(f) && f.pr.status === "Approved"
3993
+ done: (f) => !workflowPolicy.requireMerge || isPrMetadataConfigured(f) && f.pr.status === "Approved"
3985
3994
  },
3986
3995
  current: {
3987
- when: (f) => workflowPolicy.requireReview && isPrMetadataConfigured(f) && !!f.pr.link && f.pr.status !== "Approved",
3996
+ when: (f) => workflowPolicy.requireMerge && isPrMetadataConfigured(f) && !!f.pr.link && f.pr.status !== "Approved",
3988
3997
  actions: (f) => {
3989
3998
  if (!f.pr.status) {
3990
3999
  return [
@@ -3998,7 +4007,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
3998
4007
  ];
3999
4008
  }
4000
4009
  if (f.pr.status === "Review") {
4001
- if (f.pr.remote?.available && f.pr.remote.isMerged) {
4010
+ if (workflowPolicy.requireReview && f.pr.remote?.available && f.pr.remote.isMerged) {
4002
4011
  return [
4003
4012
  {
4004
4013
  type: "instruction",
@@ -4009,7 +4018,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4009
4018
  }
4010
4019
  ];
4011
4020
  }
4012
- if (!f.docs.prReviewEvidenceFieldExists) {
4021
+ if (workflowPolicy.requireReview && !f.docs.prReviewEvidenceFieldExists) {
4013
4022
  return [
4014
4023
  {
4015
4024
  type: "instruction",
@@ -4020,7 +4029,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4020
4029
  }
4021
4030
  ];
4022
4031
  }
4023
- if (!f.prReview.evidenceProvided) {
4032
+ if (workflowPolicy.requireReview && !f.prReview.evidenceProvided) {
4024
4033
  return [
4025
4034
  {
4026
4035
  type: "instruction",
@@ -4031,7 +4040,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4031
4040
  }
4032
4041
  ];
4033
4042
  }
4034
- if (!f.docs.prReviewDecisionFieldExists) {
4043
+ if (workflowPolicy.requireReview && !f.docs.prReviewDecisionFieldExists) {
4035
4044
  return [
4036
4045
  {
4037
4046
  type: "instruction",
@@ -4042,7 +4051,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4042
4051
  }
4043
4052
  ];
4044
4053
  }
4045
- if (!f.prReview.decisionProvided) {
4054
+ if (workflowPolicy.requireReview && !f.prReview.decisionProvided) {
4046
4055
  return [
4047
4056
  {
4048
4057
  type: "instruction",
@@ -4055,15 +4064,16 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4055
4064
  }
4056
4065
  const remoteBlockReasons = getPrReviewRemoteBlockReasons(f, lang);
4057
4066
  const remoteUnavailable = workflowPolicy.mode === "github" && !!f.pr.link && (!f.pr.remote || !f.pr.remote.available);
4058
- const actions = [
4059
- {
4067
+ const actions = [];
4068
+ if (workflowPolicy.requireReview) {
4069
+ actions.push({
4060
4070
  type: "instruction",
4061
4071
  category: "code_review",
4062
4072
  requiresUserCheck: true,
4063
4073
  uiDetailKey: "context.actionDetail.codeReviewResolve",
4064
4074
  message: tr(lang, "messages", "prReviewResolve")
4065
- }
4066
- ];
4075
+ });
4076
+ }
4067
4077
  if (!f.git.projectGitCwd) {
4068
4078
  actions.push({
4069
4079
  type: "instruction",
@@ -4121,7 +4131,18 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
4121
4131
  })
4122
4132
  });
4123
4133
  }
4124
- return actions;
4134
+ if (actions.length > 0) return actions;
4135
+ return [
4136
+ {
4137
+ type: "instruction",
4138
+ category: "code_review",
4139
+ requiresUserCheck: true,
4140
+ uiDetailKey: "context.actionDetail.codeReviewMergeAfterOk",
4141
+ message: tr(lang, "messages", "prReviewMerge", {
4142
+ featureRef: f.id || f.folderName
4143
+ })
4144
+ }
4145
+ ];
4125
4146
  }
4126
4147
  return [
4127
4148
  {
@@ -5331,7 +5352,7 @@ async function parseFeature(featurePath, type, context, options) {
5331
5352
  prDocPrFieldExists = hasAnySpecKey(content, ["PR", "Pull Request"]);
5332
5353
  prDocReviewStatusFieldExists = hasAnySpecKey(content, ["PR \uC0C1\uD0DC", "PR Status"]);
5333
5354
  }
5334
- if (workflowPolicy.requireReview && prStatus === "Review" && prLink && effectiveProjectGitCwd) {
5355
+ if (workflowPolicy.requireMerge && prStatus === "Review" && prLink && effectiveProjectGitCwd) {
5335
5356
  prRemote = resolvePrRemoteStatus(prLink, effectiveProjectGitCwd) || void 0;
5336
5357
  }
5337
5358
  const warnings = [];
@@ -5473,7 +5494,7 @@ async function parseFeature(featurePath, type, context, options) {
5473
5494
  }
5474
5495
  const tasksDocApproved = !tasksDocStatusFieldExists || tasksDocStatus === "Approved";
5475
5496
  const implementationDone = tasksExists && tasksSummary.total > 0 && tasksSummary.total === tasksSummary.done && isCompletionChecklistDone2({ completionChecklist }) && tasksDocApproved;
5476
- const workflowDone = implementationDone && !docsHasUncommittedChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink) && (!workflowPolicy.requireReview || prStatus === "Approved") && isPrePrReviewSatisfied2(
5497
+ const workflowDone = implementationDone && !docsHasUncommittedChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink) && (!workflowPolicy.requireMerge || prStatus === "Approved") && isPrePrReviewSatisfied2(
5477
5498
  {
5478
5499
  docs: {
5479
5500
  prePrReviewFieldExists,
@@ -5504,11 +5525,11 @@ async function parseFeature(featurePath, type, context, options) {
5504
5525
  }
5505
5526
  if (workflowPolicy.requirePr && prFieldExists && prStatusFieldExists) {
5506
5527
  if (!prLink) warnings.push(tr(lang, "warnings", "workflowPrLinkMissing"));
5507
- if (workflowPolicy.requireReview) {
5528
+ if (workflowPolicy.requireMerge) {
5508
5529
  if (!prStatus) warnings.push(tr(lang, "warnings", "workflowPrStatusMissing"));
5509
5530
  if (prStatus && prStatus !== "Approved") {
5510
5531
  warnings.push(tr(lang, "warnings", "workflowPrStatusNotApproved"));
5511
- if (prStatus === "Review") {
5532
+ if (workflowPolicy.requireReview && prStatus === "Review") {
5512
5533
  if (!prReviewEvidenceFieldExists || !prReviewEvidenceProvided) {
5513
5534
  warnings.push(tr(lang, "warnings", "workflowPrReviewEvidenceMissing"));
5514
5535
  }
@@ -6756,6 +6777,9 @@ function detectFromBranch(branchName, features) {
6756
6777
  (f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
6757
6778
  );
6758
6779
  }
6780
+ function detectFromExpectedFeatureBranch(features) {
6781
+ return features.filter((feature) => feature.git.onExpectedBranch);
6782
+ }
6759
6783
  function toSelectionStatus(features, selectionMode, openFeatures, targetFeatures) {
6760
6784
  const isNoOpen = selectionMode === "open" && features.length > 0 && openFeatures.length === 0;
6761
6785
  if (features.length === 0) return "no_features";
@@ -6792,7 +6816,12 @@ async function resolveContextSelection(config, featureName, options) {
6792
6816
  );
6793
6817
  selectionMode = "explicit";
6794
6818
  } else {
6795
- if (config.projectType === "single") {
6819
+ const expectedBranchMatches = detectFromExpectedFeatureBranch(scopedFeatures);
6820
+ if (expectedBranchMatches.length === 1) {
6821
+ targetFeatures = expectedBranchMatches;
6822
+ selectionMode = "branch";
6823
+ selectionFallback = "none";
6824
+ } else if (config.projectType === "single") {
6796
6825
  const branchName = branches.project.single || "";
6797
6826
  targetFeatures = detectFromBranch(branchName, scopedFeatures);
6798
6827
  } else if (selectedComponent) {
@@ -6863,16 +6892,6 @@ async function resolveContextSelection(config, featureName, options) {
6863
6892
  }
6864
6893
 
6865
6894
  // src/utils/context/approval-reply.ts
6866
- function isAffirmativeApprovalReply(input) {
6867
- const raw = input.trim();
6868
- if (!raw) return false;
6869
- if (/\b(?:no|don'?t|do not|stop|cancel|hold|wait|아니|취소|중지|보류)\b/i.test(raw)) {
6870
- return false;
6871
- }
6872
- return /(?:^|[\s`"'([{<])(?:ok|okay|yes|y|go|proceed|continue|run|execute|approve(?:d)?|진행(?:해|하세요)?|수행(?:해|하세요)?|실행(?:해|하세요)?|승인(?:해|하세요)?|해줘|해주세요|오케이)(?:$|[\s`"')\]}>.!?,])/i.test(
6873
- raw
6874
- );
6875
- }
6876
6895
  function normalizeRequestText(raw) {
6877
6896
  return raw.replace(/^[\s,;:]+/, "").trim();
6878
6897
  }
@@ -6895,9 +6914,6 @@ function parseApprovalReply(input, validLabels) {
6895
6914
  for (const token of tokens) {
6896
6915
  if (validSet.has(token)) return { label: token };
6897
6916
  }
6898
- if (normalizedLabels.length === 1 && isAffirmativeApprovalReply(input)) {
6899
- return { label: normalizedLabels[0] };
6900
- }
6901
6917
  return null;
6902
6918
  }
6903
6919
  var LEGACY_APPROVAL_TICKET_FILENAME = ".lee-spec-kit.approval-tickets.json";
@@ -7263,9 +7279,10 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunC
7263
7279
  const delegation = shouldDelegateCurrentAction(actionOptions);
7264
7280
  const primaryOption = actionOptions[0];
7265
7281
  const delegatedCommandOption = primaryOption && primaryOption.action.type === "command" && delegation.shouldDelegate ? primaryOption : null;
7266
- const handoffMode = delegatedCommandOption ? "command" : autoRunAvailable ? "auto_run" : null;
7267
- const handoffCwd = delegatedCommandOption?.action.cwd || (autoRunAvailable ? process.cwd() : null);
7268
- const handoffCmd = delegatedCommandOption?.action.cmd || (autoRunAvailable ? autoRunCommand : null);
7282
+ const shouldDelegateAutoRunNow = autoRunAvailable && actionOptions.length === 0;
7283
+ const handoffMode = delegatedCommandOption ? "command" : shouldDelegateAutoRunNow ? "auto_run" : null;
7284
+ const handoffCwd = delegatedCommandOption?.action.cwd || (shouldDelegateAutoRunNow ? process.cwd() : null);
7285
+ const handoffCmd = delegatedCommandOption?.action.cmd || (shouldDelegateAutoRunNow ? autoRunCommand : null);
7269
7286
  const handoffRequired = !!handoffMode && !!handoffCwd && !!handoffCmd;
7270
7287
  const verifyCacheKey = handoffRequired ? createHash("sha1").update(
7271
7288
  [
@@ -7283,7 +7300,8 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunC
7283
7300
  fallbackToMainAgentWhenSubAgentUnavailable: true,
7284
7301
  longRunningCategories: [...LONG_RUNNING_DELEGATION_CATEGORIES],
7285
7302
  currentActionShouldDelegate: delegation.shouldDelegate,
7286
- autoRunShouldDelegate: autoRunAvailable,
7303
+ autoRunDelegationAvailable: autoRunAvailable,
7304
+ autoRunShouldDelegate: shouldDelegateAutoRunNow,
7287
7305
  currentActionCategory: delegation.category,
7288
7306
  mainAgentResponsibilities: [
7289
7307
  "Keep user conversation state and approval boundaries",
@@ -7311,7 +7329,7 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunC
7311
7329
  required: handoffRequired,
7312
7330
  mode: handoffMode,
7313
7331
  featureRef,
7314
- category: delegation.category,
7332
+ category: handoffMode === "command" ? delegation.category : null,
7315
7333
  cwd: handoffCwd,
7316
7334
  cmd: handoffCmd,
7317
7335
  verify: handoffRequired ? {
@@ -7661,16 +7679,16 @@ function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
7661
7679
  if (workflowPolicy.requirePr && !f.pr.link) {
7662
7680
  return tr(lang, "cli", "context.list.recordPrLink");
7663
7681
  }
7664
- if (workflowPolicy.requireReview && !f.pr.status) {
7682
+ if (workflowPolicy.requireMerge && !f.pr.status) {
7665
7683
  return tr(lang, "cli", "context.list.setPrStatus");
7666
7684
  }
7667
- if (workflowPolicy.requireReview && f.pr.status === "Review" && (!f.docs.prReviewEvidenceFieldExists || !f.prReview.evidenceProvided)) {
7685
+ if (workflowPolicy.requireMerge && workflowPolicy.requireReview && f.pr.status === "Review" && (!f.docs.prReviewEvidenceFieldExists || !f.prReview.evidenceProvided)) {
7668
7686
  return tr(lang, "cli", "context.list.addPrReviewEvidence");
7669
7687
  }
7670
- if (workflowPolicy.requireReview && f.pr.status === "Review" && (!f.docs.prReviewDecisionFieldExists || !f.prReview.decisionProvided)) {
7688
+ if (workflowPolicy.requireMerge && workflowPolicy.requireReview && f.pr.status === "Review" && (!f.docs.prReviewDecisionFieldExists || !f.prReview.decisionProvided)) {
7671
7689
  return tr(lang, "cli", "context.list.addPrReviewDecision");
7672
7690
  }
7673
- if (workflowPolicy.requireReview && f.pr.status !== "Approved") {
7691
+ if (workflowPolicy.requireMerge && f.pr.status !== "Approved") {
7674
7692
  return tr(lang, "cli", "context.list.prStatusToApproved", {
7675
7693
  status: f.pr.status
7676
7694
  });
@@ -8035,7 +8053,7 @@ async function runContext(featureName, options) {
8035
8053
  "actionOptions[].detail",
8036
8054
  "actionOptions[].approvalPrompt"
8037
8055
  ] : [],
8038
- recommendation: "Before asking for approval, show only `actionOptions[].approvalPrompt` lines and `approvalRequest.finalPrompt` to the user. Keep `requiredDocs`, `checkPolicy`, and raw execution commands as internal guidance. For commit actions, include scope (`docs`/`project`) and commit message in the visible prompt. User replies should include the label token (e.g. `A`, `A OK`, `A proceed`, `A \uC9C4\uD589\uD574`). For command execution, prefer one-shot `npx lee-spec-kit flow <featureRef> --approve <LABEL> --execute` to avoid session mismatch after context compression/reset. Use ticket-based `context --execute --ticket` only when explicitly needed. Use main-agent orchestration: keep short steps in main agent, and delegate only long-running command/auto loops to sub-agents.",
8056
+ recommendation: 'Before asking for approval, show only `actionOptions[].approvalPrompt` lines and `approvalRequest.finalPrompt` to the user. Keep `requiredDocs`, `checkPolicy`, and raw execution commands as internal guidance. For commit actions, include scope (`docs`/`project`) and commit message in the visible prompt. User replies should include the label token (e.g. `A`, `A OK`, `A proceed`, `A \uC9C4\uD589\uD574`). For command execution, prefer one-shot `npx lee-spec-kit flow <featureRef> --approve <LABEL> --execute` to avoid session mismatch after context compression/reset. Use ticket-based `context --execute --ticket` only when explicitly needed. Use main-agent orchestration: keep short steps in main agent. Delegate command runs only when `agentOrchestration.currentActionShouldDelegate=true`, and delegate auto-run only when `agentOrchestration.subAgentHandoff.required=true` with `mode="auto_run"`.',
8039
8057
  oneApprovalPerAction: approvalRequired,
8040
8058
  requireFreshContext: true,
8041
8059
  contextVersion: state.contextVersion,
@@ -8049,10 +8067,10 @@ async function runContext(featureName, options) {
8049
8067
  command: autoRunPlan.command,
8050
8068
  untilCategories: autoRunPlan.untilCategories,
8051
8069
  unknownCategories: autoRunPlan.unknownCategories,
8052
- guidance: "Use auto-run only when `autoRun.available=true`. Stop and request approval when `approvalRequest.required=true` or when auto mode reaches configured gate categories."
8070
+ guidance: 'Use auto-run only when `autoRun.available=true`. Do not treat `autoRun.available` alone as a delegation trigger; use `agentOrchestration.subAgentHandoff.required` + `mode="auto_run"` for actual delegation. Stop and request approval when `approvalRequest.required=true` or when auto mode reaches configured gate categories.'
8053
8071
  },
8054
8072
  approvalRequest: {
8055
- guidance: "User-facing output must include only approval prompts (`A: ...`) and `finalPrompt`. Do not expose `requiredDocs`, `checkPolicy`, or raw `cmd` unless explicitly requested. For approved command actions, prefer one-shot `flow --approve <LABEL> --execute`. Keep short steps in main agent and delegate only long-running command/auto loops to sub-agents.",
8073
+ guidance: 'User-facing output must include only approval prompts (`A: ...`) and `finalPrompt`. Do not expose `requiredDocs`, `checkPolicy`, or raw `cmd` unless explicitly requested. For approved command actions, prefer one-shot `flow --approve <LABEL> --execute`. Keep short steps in main agent. Delegate command runs only when `agentOrchestration.currentActionShouldDelegate=true`, and delegate auto-run only when `agentOrchestration.subAgentHandoff.required=true` with `mode="auto_run"`.',
8056
8074
  required: approvalRequired,
8057
8075
  finalPrompt: finalApprovalPrompt,
8058
8076
  userFacingLines: approvalUserFacingLines,
@@ -8310,12 +8328,14 @@ async function runContext(featureName, options) {
8310
8328
  const actionOptions = state.actionOptions;
8311
8329
  const hasCommandOption = actionOptions.some((option) => option.action.type === "command");
8312
8330
  const longRunningDelegation = shouldDelegateCurrentAction(actionOptions);
8331
+ const showOptionLabels = hasCheckAction;
8313
8332
  console.log(chalk6.green(chalk6.bold("\u{1F449} Next Options (Atomic):")));
8314
8333
  let hasDocsCommand = false;
8315
8334
  actionOptions.forEach((option) => {
8316
8335
  const requiresCheck = option.action.requiresUserCheck;
8317
8336
  const detail = option.detail;
8318
- console.log(` ${option.label}. ${checkTag(requiresCheck)}${detail}`);
8337
+ const prefix = showOptionLabels ? `${option.label}. ` : "- ";
8338
+ console.log(` ${prefix}${checkTag(requiresCheck)}${detail}`);
8319
8339
  if (option.action.type === "command" && option.action.scope === "docs") {
8320
8340
  hasDocsCommand = true;
8321
8341
  }
@@ -10808,8 +10828,14 @@ function runGhJson2(args, cwd, lang) {
10808
10828
  }
10809
10829
  function ensureSections(body, sections, kind, lang) {
10810
10830
  const hasHeading = (sectionHeading) => {
10811
- const re = new RegExp(`^##\\s+${escapeRegExp3(sectionHeading)}\\s*$`, "m");
10812
- return re.test(body);
10831
+ const target = normalizeHeading(sectionHeading);
10832
+ const lines = body.split("\n");
10833
+ for (const line of lines) {
10834
+ const match = line.match(/^\s*##\s+(.+?)\s*$/);
10835
+ if (!match) continue;
10836
+ if (normalizeHeading(match[1]) === target) return true;
10837
+ }
10838
+ return false;
10813
10839
  };
10814
10840
  const hasMetadataField = (field) => {
10815
10841
  const re = new RegExp(`^\\s*-\\s*\\*\\*${escapeRegExp3(field)}\\*\\*\\s*:`, "m");
@@ -10970,7 +10996,13 @@ function getFeatureDocPaths(feature) {
10970
10996
  };
10971
10997
  }
10972
10998
  function normalizeHeading(value) {
10973
- return value.trim().replace(/\s+/g, " ").toLowerCase();
10999
+ let normalized = value.trim();
11000
+ for (; ; ) {
11001
+ const next = normalized.replace(/\s*\([^)]*\)\s*$/, "").replace(/\s*([^)]*)\s*$/, "").trim();
11002
+ if (next === normalized) break;
11003
+ normalized = next;
11004
+ }
11005
+ return normalized.replace(/\s+/g, " ").toLowerCase();
10974
11006
  }
10975
11007
  function extractMarkdownByHeadings(content, headings, levels) {
10976
11008
  const targets = new Set(headings.map((heading) => normalizeHeading(heading)));