lee-spec-kit 0.6.29 → 0.6.31

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
  }
@@ -7243,17 +7264,38 @@ var LONG_RUNNING_DELEGATION_CATEGORIES = [
7243
7264
  "review_fix_commit",
7244
7265
  "pre_pr_review"
7245
7266
  ];
7267
+ function isTaskExecuteProjectCommitCommand(option) {
7268
+ if (!option || option.action.type !== "command") return false;
7269
+ if (option.action.category !== "task_execute") return false;
7270
+ if (option.action.scope !== "project") return false;
7271
+ return /\bgit\s+commit\b/i.test(option.action.cmd);
7272
+ }
7246
7273
  function shouldDelegateCurrentAction(actionOptions) {
7247
- const primaryCategory = actionOptions[0]?.action?.category || null;
7274
+ const primaryOption = actionOptions[0];
7275
+ const primaryCategory = primaryOption?.action?.category || null;
7248
7276
  const longRunningSet = new Set(LONG_RUNNING_DELEGATION_CATEGORIES);
7249
- const shouldDelegate = !!primaryCategory && longRunningSet.has(primaryCategory);
7277
+ const shouldDelegate = !!primaryCategory && longRunningSet.has(primaryCategory) && !isTaskExecuteProjectCommitCommand(primaryOption);
7250
7278
  return {
7251
7279
  shouldDelegate,
7252
7280
  category: primaryCategory
7253
7281
  };
7254
7282
  }
7255
- function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable) {
7283
+ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunCommand, featureRef) {
7256
7284
  const delegation = shouldDelegateCurrentAction(actionOptions);
7285
+ const primaryOption = actionOptions[0];
7286
+ const delegatedCommandOption = primaryOption && primaryOption.action.type === "command" && delegation.shouldDelegate ? primaryOption : null;
7287
+ const handoffMode = delegatedCommandOption ? "command" : autoRunAvailable ? "auto_run" : null;
7288
+ const handoffCwd = delegatedCommandOption?.action.cwd || (autoRunAvailable ? process.cwd() : null);
7289
+ const handoffCmd = delegatedCommandOption?.action.cmd || (autoRunAvailable ? autoRunCommand : null);
7290
+ const handoffRequired = !!handoffMode && !!handoffCwd && !!handoffCmd;
7291
+ const verifyCacheKey = handoffRequired ? createHash("sha1").update(
7292
+ [
7293
+ handoffMode,
7294
+ featureRef || "",
7295
+ handoffCwd || "",
7296
+ handoffCmd || ""
7297
+ ].join("|")
7298
+ ).digest("hex").slice(0, 12) : "";
7257
7299
  return {
7258
7300
  mode: "main_orchestrates_subagent_execution",
7259
7301
  delegationPolicy: "prefer_main_delegate_long_running_fallback_main",
@@ -7285,7 +7327,23 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable) {
7285
7327
  "flow --resume <RUN_ID>",
7286
7328
  "autoRun.resume.flowCommand",
7287
7329
  "context --json-compact"
7288
- ]
7330
+ ],
7331
+ subAgentHandoff: {
7332
+ required: handoffRequired,
7333
+ mode: handoffMode,
7334
+ featureRef,
7335
+ category: delegation.category,
7336
+ cwd: handoffCwd,
7337
+ cmd: handoffCmd,
7338
+ verify: handoffRequired ? {
7339
+ runOncePerSession: true,
7340
+ cacheKey: verifyCacheKey,
7341
+ expectedCwd: handoffCwd,
7342
+ commands: ["pwd", "git rev-parse --show-toplevel"],
7343
+ onMismatch: "stop_and_report",
7344
+ collectDetailedLogsOnMismatchOnly: true
7345
+ } : null
7346
+ }
7289
7347
  };
7290
7348
  }
7291
7349
  async function resolveContextState(config, featureName, options) {
@@ -7837,7 +7895,9 @@ async function runContext(featureName, options) {
7837
7895
  );
7838
7896
  const agentOrchestration = buildAgentOrchestrationPolicy(
7839
7897
  state.actionOptions,
7840
- autoRunPlan.available
7898
+ autoRunPlan.available,
7899
+ autoRunPlan.command,
7900
+ state.matchedFeature?.folderName || null
7841
7901
  );
7842
7902
  if (options.approve || options.execute) {
7843
7903
  await runApprovedOption(
@@ -9657,8 +9717,12 @@ function toFlowRunStatus(status) {
9657
9717
  return "failed";
9658
9718
  }
9659
9719
  }
9660
- function buildAgentOrchestrationPolicy2(autoRun) {
9720
+ function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
9661
9721
  const preferredResumeCommand = autoRun?.run?.resumeCommand || autoRun?.resume?.flowCommand || null;
9722
+ const handoffRequired = !!autoRun && !!preferredResumeCommand;
9723
+ const verifyCacheKey = handoffRequired ? `${(featureRef || "unknown").toLowerCase()}|${Buffer.from(
9724
+ preferredResumeCommand
9725
+ ).toString("base64").slice(0, 12)}` : "";
9662
9726
  return {
9663
9727
  mode: "main_orchestrates_subagent_execution",
9664
9728
  delegationPolicy: "prefer_main_delegate_long_running_fallback_main",
@@ -9683,7 +9747,23 @@ function buildAgentOrchestrationPolicy2(autoRun) {
9683
9747
  "AUTO_MANUAL_REQUIRED",
9684
9748
  "command execution error"
9685
9749
  ],
9686
- preferredResumeCommand
9750
+ preferredResumeCommand,
9751
+ subAgentHandoff: {
9752
+ required: handoffRequired,
9753
+ mode: handoffRequired ? "auto_run" : null,
9754
+ featureRef,
9755
+ category: null,
9756
+ cwd: handoffRequired ? process.cwd() : null,
9757
+ cmd: handoffRequired ? preferredResumeCommand : null,
9758
+ verify: handoffRequired ? {
9759
+ runOncePerSession: true,
9760
+ cacheKey: verifyCacheKey,
9761
+ expectedCwd: process.cwd(),
9762
+ commands: ["pwd", "git rev-parse --show-toplevel"],
9763
+ onMismatch: "stop_and_report",
9764
+ collectDetailedLogsOnMismatchOnly: true
9765
+ } : null
9766
+ }
9687
9767
  };
9688
9768
  }
9689
9769
  function getFeatureRef2(feature) {
@@ -10334,7 +10414,10 @@ async function runFlow(featureName, options) {
10334
10414
  const jsonMode = !!options.json || !!options.jsonCompact;
10335
10415
  if (jsonMode) {
10336
10416
  const autoRunFailed = !!(autoRun && isAutoRunFailureStatus(autoRun.status));
10337
- const agentOrchestration2 = buildAgentOrchestrationPolicy2(autoRun);
10417
+ const agentOrchestration2 = buildAgentOrchestrationPolicy2(
10418
+ autoRun,
10419
+ resolvedFeatureName || null
10420
+ );
10338
10421
  const status = autoRunFailed ? "error" : "ok";
10339
10422
  const reasonCode = autoRunFailed ? autoRun?.reasonCode || "AUTO_EXECUTION_FAILED" : "FLOW_SUMMARY";
10340
10423
  if (options.jsonCompact) {
@@ -10429,7 +10512,10 @@ async function runFlow(featureName, options) {
10429
10512
  console.log(chalk6.gray(`- Resume with: ${autoRun.run.resumeCommand}`));
10430
10513
  }
10431
10514
  }
10432
- const agentOrchestration = buildAgentOrchestrationPolicy2(autoRun);
10515
+ const agentOrchestration = buildAgentOrchestrationPolicy2(
10516
+ autoRun,
10517
+ resolvedFeatureName || null
10518
+ );
10433
10519
  console.log(
10434
10520
  chalk6.gray(
10435
10521
  `- Orchestration: ${agentOrchestration.mode}, delegate long-running loops to sub-agent`
@@ -10743,8 +10829,14 @@ function runGhJson2(args, cwd, lang) {
10743
10829
  }
10744
10830
  function ensureSections(body, sections, kind, lang) {
10745
10831
  const hasHeading = (sectionHeading) => {
10746
- const re = new RegExp(`^##\\s+${escapeRegExp3(sectionHeading)}\\s*$`, "m");
10747
- return re.test(body);
10832
+ const target = normalizeHeading(sectionHeading);
10833
+ const lines = body.split("\n");
10834
+ for (const line of lines) {
10835
+ const match = line.match(/^\s*##\s+(.+?)\s*$/);
10836
+ if (!match) continue;
10837
+ if (normalizeHeading(match[1]) === target) return true;
10838
+ }
10839
+ return false;
10748
10840
  };
10749
10841
  const hasMetadataField = (field) => {
10750
10842
  const re = new RegExp(`^\\s*-\\s*\\*\\*${escapeRegExp3(field)}\\*\\*\\s*:`, "m");
@@ -10905,7 +10997,13 @@ function getFeatureDocPaths(feature) {
10905
10997
  };
10906
10998
  }
10907
10999
  function normalizeHeading(value) {
10908
- return value.trim().replace(/\s+/g, " ").toLowerCase();
11000
+ let normalized = value.trim();
11001
+ for (; ; ) {
11002
+ const next = normalized.replace(/\s*\([^)]*\)\s*$/, "").replace(/\s*([^)]*)\s*$/, "").trim();
11003
+ if (next === normalized) break;
11004
+ normalized = next;
11005
+ }
11006
+ return normalized.replace(/\s+/g, " ").toLowerCase();
10909
11007
  }
10910
11008
  function extractMarkdownByHeadings(content, headings, levels) {
10911
11009
  const targets = new Set(headings.map((heading) => normalizeHeading(heading)));