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 +85 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/agents/agents.md +1 -1
- package/templates/en/common/agents/pr-template.md +3 -1
- package/templates/en/common/agents/skills/execute-task.md +1 -1
- package/templates/ko/common/agents/agents.md +1 -1
- package/templates/ko/common/agents/pr-template.md +3 -1
- package/templates/ko/common/agents/skills/execute-task.md +1 -1
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 "
|
|
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 "
|
|
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.
|
|
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.
|
|
3993
|
+
done: (f) => !workflowPolicy.requireMerge || isPrMetadataConfigured(f) && f.pr.status === "Approved"
|
|
3985
3994
|
},
|
|
3986
3995
|
current: {
|
|
3987
|
-
when: (f) => workflowPolicy.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
7267
|
-
const
|
|
7268
|
-
const
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
10812
|
-
|
|
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
|
-
|
|
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)));
|