lee-spec-kit 0.6.22 → 0.6.24
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/README.en.md +9 -1
- package/README.md +9 -1
- package/dist/index.js +519 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/agents/skills/create-pr.md +4 -3
- package/templates/en/common/features/README.md +2 -1
- package/templates/en/common/features/feature-base/tasks.md +5 -2
- package/templates/ko/common/agents/skills/create-pr.md +4 -3
- package/templates/ko/common/features/README.md +2 -1
- package/templates/ko/common/features/feature-base/tasks.md +5 -2
package/dist/index.js
CHANGED
|
@@ -206,8 +206,10 @@ var ko = {
|
|
|
206
206
|
"context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
|
|
207
207
|
"context.list.addPrePrReviewField": "Pre-PR Review \uD544\uB4DC \uCD94\uAC00",
|
|
208
208
|
"context.list.completePrePrReview": "Pre-PR \uB9AC\uBDF0 \uC644\uB8CC \uCC98\uB9AC",
|
|
209
|
+
"context.list.addPrePrFindings": "Pre-PR Findings \uAE30\uB85D",
|
|
209
210
|
"context.list.addPrePrEvidence": "Pre-PR Evidence \uADFC\uAC70 \uCD94\uAC00",
|
|
210
211
|
"context.list.addPrePrDecision": "Pre-PR Decision \uAE30\uB85D",
|
|
212
|
+
"context.list.resolvePrePrDecision": "Pre-PR Decision\uC744 approve\uB85C \uC815\uB9AC",
|
|
211
213
|
"context.list.addPrReviewEvidence": "PR \uB9AC\uBDF0 Evidence \uC694\uC57D \uCD94\uAC00",
|
|
212
214
|
"context.list.addPrReviewDecision": "PR \uB9AC\uBDF0 Decision \uAE30\uB85D",
|
|
213
215
|
"context.list.setPrStatus": "PR \uC0C1\uD0DC \uC124\uC815",
|
|
@@ -487,7 +489,7 @@ var ko = {
|
|
|
487
489
|
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)",
|
|
488
490
|
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
489
491
|
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}"',
|
|
490
|
-
worktreeCleanupCommand: 'cd "{projectGitCwd}" && git worktree remove "{worktreePath}" && git worktree prune',
|
|
492
|
+
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',
|
|
491
493
|
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.',
|
|
492
494
|
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.",
|
|
493
495
|
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 \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD)\uC744 \uBC1B\uC740 \uB4A4 DONE \uCC98\uB9AC',
|
|
@@ -542,6 +544,7 @@ var ko = {
|
|
|
542
544
|
legacyTasksDocStatusField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. `\uBB38\uC11C \uC0C1\uD0DC` \uD544\uB4DC(Draft/Review/Approved)\uB97C \uCD94\uAC00\uD574 \uD0DC\uC2A4\uD06C \uC2B9\uC778 \uB2E8\uACC4\uB97C \uD65C\uC131\uD654\uD558\uC138\uC694.",
|
|
543
545
|
legacyTasksPrFields: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR` \uBC0F `PR \uC0C1\uD0DC` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
544
546
|
legacyTasksPrePrReviewField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`)",
|
|
547
|
+
legacyTasksPrePrFindingsField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0 Findings` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0 Findings**: major=0, minor=0`)",
|
|
545
548
|
legacyTasksPrePrEvidenceField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0 Evidence` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
546
549
|
legacyTasksPrePrDecisionField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0 Decision` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0 Decision**: \uACB0\uC815: ...`)",
|
|
547
550
|
legacyTasksPrReviewEvidenceField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uB9AC\uBDF0 \uB2E8\uACC4 \uC804\uC5D0 `PR \uB9AC\uBDF0 Evidence` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
@@ -560,8 +563,10 @@ var ko = {
|
|
|
560
563
|
workflowPrRemoteChecksPending: "\uC6D0\uACA9 PR \uCCB4\uD06C \uB300\uAE30\uAC00 {count}\uAC74 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCCB4\uD06C \uC644\uB8CC \uD6C4 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.",
|
|
561
564
|
workflowPrePrReviewMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. (tasks.md\uC5D0 `- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`\uC744 \uCD94\uAC00\uD558\uC138\uC694.)",
|
|
562
565
|
workflowPrePrReviewNotDone: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0`\uAC00 Done\uC774 \uC544\uB2D9\uB2C8\uB2E4. (\uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0 \uD6C4 Done\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
|
|
563
|
-
|
|
564
|
-
|
|
566
|
+
workflowPrePrFindingsMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Findings`\uAC00 \uC5C6\uAC70\uB098 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (`major=<n>, minor=<n>` \uD615\uC2DD\uC73C\uB85C \uAE30\uB85D)",
|
|
567
|
+
workflowPrePrEvidenceMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (path_required \uC815\uCC45\uC774\uBA74 \uC2E4\uC81C \uC874\uC7AC\uD558\uB294 \uACBD\uB85C\uB97C \uAE30\uB85D\uD558\uC138\uC694.)",
|
|
568
|
+
workflowPrePrDecisionMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 \uBE44\uC5B4\uC788\uAC70\uB098 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (`decision: approve|changes_requested|blocked ...` \uD615\uC2DD)",
|
|
569
|
+
workflowPrePrDecisionNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 `{outcome}`\uC785\uB2C8\uB2E4. Findings\uB97C \uD574\uACB0\uD558\uACE0 pre-pr-review\uB97C \uC7AC\uC2E4\uD589\uD574 `approve`\uB85C \uB9DE\uCD94\uC138\uC694."
|
|
565
570
|
}
|
|
566
571
|
};
|
|
567
572
|
var ko_default = ko;
|
|
@@ -688,8 +693,10 @@ var en = {
|
|
|
688
693
|
"context.list.recordPrLink": "Record PR link",
|
|
689
694
|
"context.list.addPrePrReviewField": "Add Pre-PR Review field",
|
|
690
695
|
"context.list.completePrePrReview": "Complete Pre-PR review",
|
|
696
|
+
"context.list.addPrePrFindings": "Record Pre-PR Findings",
|
|
691
697
|
"context.list.addPrePrEvidence": "Add Pre-PR Evidence",
|
|
692
698
|
"context.list.addPrePrDecision": "Add Pre-PR Decision",
|
|
699
|
+
"context.list.resolvePrePrDecision": "Resolve Pre-PR decision to approve",
|
|
693
700
|
"context.list.addPrReviewEvidence": "Add PR Review Evidence summary",
|
|
694
701
|
"context.list.addPrReviewDecision": "Add PR Review Decision",
|
|
695
702
|
"context.list.setPrStatus": "Set PR Status",
|
|
@@ -969,7 +976,7 @@ var en = {
|
|
|
969
976
|
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).",
|
|
970
977
|
standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
|
|
971
978
|
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}"',
|
|
972
|
-
worktreeCleanupCommand: 'cd "{projectGitCwd}" && git worktree remove "{worktreePath}" && git worktree prune',
|
|
979
|
+
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',
|
|
973
980
|
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.',
|
|
974
981
|
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.",
|
|
975
982
|
finishDoingTask: 'Continue working on the current DOING/REVIEW task: "{title}" ({done}/{total}) After it is complete, share outcome/verification and get approval before marking DONE',
|
|
@@ -1024,6 +1031,7 @@ var en = {
|
|
|
1024
1031
|
legacyTasksDocStatusField: "Legacy tasks.md format detected. Add a `Doc Status` field (Draft/Review/Approved) to enable tasks approval.",
|
|
1025
1032
|
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
|
|
1026
1033
|
legacyTasksPrePrReviewField: "Legacy tasks.md format detected. Add `Pre-PR Review` before PR steps. (`- **Pre-PR Review**: Pending | Done`)",
|
|
1034
|
+
legacyTasksPrePrFindingsField: "Legacy tasks.md format detected. Add `Pre-PR Findings` before PR steps. (`- **Pre-PR Findings**: major=0, minor=0`)",
|
|
1027
1035
|
legacyTasksPrePrEvidenceField: "Legacy tasks.md format detected. Add `Pre-PR Evidence` before PR steps.",
|
|
1028
1036
|
legacyTasksPrePrDecisionField: "Legacy tasks.md format detected. Add `Pre-PR Decision` before PR steps. (`- **Pre-PR Decision**: decision: ...`)",
|
|
1029
1037
|
legacyTasksPrReviewEvidenceField: "Legacy tasks.md format detected. Add `PR Review Evidence` before review iteration.",
|
|
@@ -1042,8 +1050,10 @@ var en = {
|
|
|
1042
1050
|
workflowPrRemoteChecksPending: "Remote PR has {count} pending check(s). Wait for checks to complete, then re-check.",
|
|
1043
1051
|
workflowPrePrReviewMissing: "Implementation is done but `Pre-PR Review` is missing. (Add `- **Pre-PR Review**: Pending | Done` in tasks.md.)",
|
|
1044
1052
|
workflowPrePrReviewNotDone: "Implementation is done but `Pre-PR Review` is not Done. (Run pre-PR review, then update it to Done.)",
|
|
1045
|
-
|
|
1046
|
-
|
|
1053
|
+
workflowPrePrFindingsMissing: "Implementation is done but `Pre-PR Findings` is missing/invalid. (Use `major=<n>, minor=<n>`.)",
|
|
1054
|
+
workflowPrePrEvidenceMissing: "Implementation is done but `Pre-PR Evidence` is empty/invalid. (Record a real existing path when path_required policy is enabled.)",
|
|
1055
|
+
workflowPrePrDecisionMissing: "Implementation is done but `Pre-PR Decision` is empty/invalid. (Use `decision: approve|changes_requested|blocked ...`.)",
|
|
1056
|
+
workflowPrePrDecisionNotApproved: "Implementation is done but `Pre-PR Decision` is `{outcome}`. Resolve findings and re-run pre-PR review until decision becomes `approve`."
|
|
1047
1057
|
}
|
|
1048
1058
|
};
|
|
1049
1059
|
var en_default = en;
|
|
@@ -1066,7 +1076,10 @@ var I18N = {
|
|
|
1066
1076
|
};
|
|
1067
1077
|
function tr(lang, category, key, vars = {}) {
|
|
1068
1078
|
const safeLang = normalizeLang(lang);
|
|
1069
|
-
const
|
|
1079
|
+
const safeCategory = I18N[safeLang]?.[category];
|
|
1080
|
+
const defaultCategory = I18N[DEFAULT_LANG]?.[category];
|
|
1081
|
+
const koCategory = I18N.ko?.[category];
|
|
1082
|
+
const template = safeCategory?.[key] ?? defaultCategory?.[key] ?? koCategory?.[key] ?? `${category}.${key}`;
|
|
1070
1083
|
return formatTemplate(template, vars);
|
|
1071
1084
|
}
|
|
1072
1085
|
|
|
@@ -2259,7 +2272,10 @@ async function runInit(options) {
|
|
|
2259
2272
|
},
|
|
2260
2273
|
prePrReview: {
|
|
2261
2274
|
skills: ["code-review-excellence"],
|
|
2262
|
-
|
|
2275
|
+
fallback: "builtin-checklist",
|
|
2276
|
+
evidenceMode: "path_required",
|
|
2277
|
+
findings: "required",
|
|
2278
|
+
decisionEnum: ["approve", "changes_requested", "blocked"]
|
|
2263
2279
|
}
|
|
2264
2280
|
},
|
|
2265
2281
|
pr: {
|
|
@@ -2878,6 +2894,11 @@ var ACTION_CATEGORIES = [
|
|
|
2878
2894
|
|
|
2879
2895
|
// src/utils/workflow.ts
|
|
2880
2896
|
var DEFAULT_PRE_PR_REVIEW_SKILLS = ["code-review-excellence"];
|
|
2897
|
+
var DEFAULT_PRE_PR_DECISION_ENUM = [
|
|
2898
|
+
"approve",
|
|
2899
|
+
"changes_requested",
|
|
2900
|
+
"blocked"
|
|
2901
|
+
];
|
|
2881
2902
|
function resolveWorkflowPolicy(workflow) {
|
|
2882
2903
|
const mode = workflow?.mode === "local" ? "local" : "github";
|
|
2883
2904
|
const policy = mode === "local" ? {
|
|
@@ -2939,17 +2960,42 @@ function normalizeSkillList(input) {
|
|
|
2939
2960
|
}
|
|
2940
2961
|
return [...deduped];
|
|
2941
2962
|
}
|
|
2963
|
+
function normalizeDecisionEnumList(input) {
|
|
2964
|
+
if (!Array.isArray(input)) return [];
|
|
2965
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
2966
|
+
for (const raw of input) {
|
|
2967
|
+
const value = String(raw || "").trim().toLowerCase();
|
|
2968
|
+
if (!value) continue;
|
|
2969
|
+
if (value === "approve") {
|
|
2970
|
+
deduped.add("approve");
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
if (value === "changes_requested") {
|
|
2974
|
+
deduped.add("changes_requested");
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
if (value === "blocked") {
|
|
2978
|
+
deduped.add("blocked");
|
|
2979
|
+
continue;
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
return [...deduped];
|
|
2983
|
+
}
|
|
2942
2984
|
function resolvePrePrReviewPolicy(workflow) {
|
|
2943
2985
|
const workflowPolicy = resolveWorkflowPolicy(workflow);
|
|
2944
2986
|
const configured = workflow?.prePrReview;
|
|
2945
2987
|
const configuredSkills = normalizeSkillList(configured?.skills);
|
|
2988
|
+
const configuredDecisionEnum = normalizeDecisionEnumList(
|
|
2989
|
+
configured?.decisionEnum
|
|
2990
|
+
);
|
|
2946
2991
|
const configuredEnabled = typeof configured?.enabled === "boolean" ? configured.enabled : workflowPolicy.requirePr;
|
|
2947
2992
|
return {
|
|
2948
2993
|
enabled: workflowPolicy.requirePr ? configuredEnabled : false,
|
|
2949
2994
|
skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
|
|
2950
2995
|
fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
|
|
2951
|
-
|
|
2952
|
-
|
|
2996
|
+
evidenceMode: configured?.evidenceMode === "any" ? "any" : "path_required",
|
|
2997
|
+
findings: configured?.findings === "optional" ? "optional" : "required",
|
|
2998
|
+
decisionEnum: configuredDecisionEnum.length > 0 ? configuredDecisionEnum : DEFAULT_PRE_PR_DECISION_ENUM
|
|
2953
2999
|
};
|
|
2954
3000
|
}
|
|
2955
3001
|
|
|
@@ -2977,17 +3023,20 @@ function isPrePrReviewSatisfied(feature, prePrReviewPolicy) {
|
|
|
2977
3023
|
if (!feature.docs.prePrEvidenceFieldExists || !feature.prePrReview.evidenceProvided) {
|
|
2978
3024
|
return false;
|
|
2979
3025
|
}
|
|
3026
|
+
if (prePrReviewPolicy.findings === "required" && (!feature.docs.prePrFindingsFieldExists || !feature.prePrReview.findingsProvided)) {
|
|
3027
|
+
return false;
|
|
3028
|
+
}
|
|
2980
3029
|
if (!feature.docs.prePrDecisionFieldExists || !feature.prePrReview.decisionProvided) {
|
|
2981
3030
|
return false;
|
|
2982
3031
|
}
|
|
3032
|
+
if (feature.prePrReview.decisionOutcome !== "approve") {
|
|
3033
|
+
return false;
|
|
3034
|
+
}
|
|
2983
3035
|
return true;
|
|
2984
3036
|
}
|
|
2985
3037
|
function isFeatureDone(feature, workflowPolicy, prePrReviewPolicy) {
|
|
2986
3038
|
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);
|
|
2987
3039
|
}
|
|
2988
|
-
function formatSkillList(skills) {
|
|
2989
|
-
return skills.join(", ");
|
|
2990
|
-
}
|
|
2991
3040
|
function getPrReviewRemoteBlockReasons(feature, lang) {
|
|
2992
3041
|
const remote = feature.pr.remote;
|
|
2993
3042
|
if (!remote || !remote.available) return [];
|
|
@@ -3024,6 +3073,14 @@ function getPrReviewRemoteBlockReasons(feature, lang) {
|
|
|
3024
3073
|
function normalizeCommitTopicText(value) {
|
|
3025
3074
|
return value.replace(/\s+/g, " ").trim();
|
|
3026
3075
|
}
|
|
3076
|
+
function toShellArg(value) {
|
|
3077
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`")}"`;
|
|
3078
|
+
}
|
|
3079
|
+
function buildSelfCliCommand(args) {
|
|
3080
|
+
const entry = process.argv[1] || "dist/index.js";
|
|
3081
|
+
const base = [process.execPath, entry, "--no-banner", ...args];
|
|
3082
|
+
return base.map((arg) => toShellArg(arg)).join(" ");
|
|
3083
|
+
}
|
|
3027
3084
|
function toShellSafeCommitTopic(value) {
|
|
3028
3085
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
|
|
3029
3086
|
}
|
|
@@ -3792,74 +3849,19 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3792
3849
|
}
|
|
3793
3850
|
];
|
|
3794
3851
|
}
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
{
|
|
3799
|
-
type: "instruction",
|
|
3800
|
-
category: "pre_pr_review",
|
|
3801
|
-
requiresUserCheck: true,
|
|
3802
|
-
message: tr(lang, "messages", "prePrReviewRun", {
|
|
3803
|
-
skills: "code-review-excellence",
|
|
3804
|
-
fallback: prePrReviewPolicy.fallback
|
|
3805
|
-
})
|
|
3806
|
-
}
|
|
3807
|
-
];
|
|
3808
|
-
}
|
|
3809
|
-
return [
|
|
3810
|
-
{
|
|
3811
|
-
type: "instruction",
|
|
3812
|
-
category: "pre_pr_review",
|
|
3813
|
-
requiresUserCheck: true,
|
|
3814
|
-
message: tr(lang, "messages", "prePrReviewRun", {
|
|
3815
|
-
skills: formatSkillList(prePrReviewPolicy.skills),
|
|
3816
|
-
fallback: prePrReviewPolicy.fallback
|
|
3817
|
-
})
|
|
3818
|
-
}
|
|
3819
|
-
];
|
|
3820
|
-
}
|
|
3821
|
-
if (!f.docs.prePrEvidenceFieldExists || !f.prePrReview.evidenceProvided) {
|
|
3822
|
-
return [
|
|
3823
|
-
{
|
|
3824
|
-
type: "instruction",
|
|
3825
|
-
category: "pre_pr_review",
|
|
3826
|
-
requiresUserCheck: true,
|
|
3827
|
-
message: tr(lang, "messages", "prePrReviewEvidenceMissing")
|
|
3828
|
-
}
|
|
3829
|
-
];
|
|
3830
|
-
}
|
|
3831
|
-
if (!f.docs.prePrDecisionFieldExists || !f.prePrReview.decisionProvided) {
|
|
3832
|
-
return [
|
|
3833
|
-
{
|
|
3834
|
-
type: "instruction",
|
|
3835
|
-
category: "pre_pr_review",
|
|
3836
|
-
requiresUserCheck: true,
|
|
3837
|
-
message: tr(lang, "messages", "prePrReviewDecisionMissing")
|
|
3838
|
-
}
|
|
3839
|
-
];
|
|
3840
|
-
}
|
|
3841
|
-
if (!prePrReviewPolicy.skills.length) {
|
|
3842
|
-
return [
|
|
3843
|
-
{
|
|
3844
|
-
type: "instruction",
|
|
3845
|
-
category: "pre_pr_review",
|
|
3846
|
-
requiresUserCheck: true,
|
|
3847
|
-
message: tr(lang, "messages", "prePrReviewRun", {
|
|
3848
|
-
skills: "code-review-excellence",
|
|
3849
|
-
fallback: prePrReviewPolicy.fallback
|
|
3850
|
-
})
|
|
3851
|
-
}
|
|
3852
|
-
];
|
|
3852
|
+
const commandArgs = ["pre-pr-review", f.folderName];
|
|
3853
|
+
if (f.type && f.type !== "single") {
|
|
3854
|
+
commandArgs.push("--component", f.type);
|
|
3853
3855
|
}
|
|
3854
3856
|
return [
|
|
3855
3857
|
{
|
|
3856
|
-
type: "
|
|
3858
|
+
type: "command",
|
|
3857
3859
|
category: "pre_pr_review",
|
|
3860
|
+
operationType: "local",
|
|
3858
3861
|
requiresUserCheck: true,
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
})
|
|
3862
|
+
scope: "docs",
|
|
3863
|
+
cwd: f.git.docsGitCwd,
|
|
3864
|
+
cmd: buildSelfCliCommand(commandArgs)
|
|
3863
3865
|
}
|
|
3864
3866
|
];
|
|
3865
3867
|
}
|
|
@@ -4556,6 +4558,52 @@ function hasStructuredReviewDecision(value) {
|
|
|
4556
4558
|
if (!trimmed) return false;
|
|
4557
4559
|
return /^(?:decision|결정)\s*[::]\s*\S.+$/i.test(trimmed);
|
|
4558
4560
|
}
|
|
4561
|
+
function parsePrePrDecisionOutcome(value) {
|
|
4562
|
+
if (!value) return void 0;
|
|
4563
|
+
const trimmed = value.trim();
|
|
4564
|
+
if (!trimmed) return void 0;
|
|
4565
|
+
const structured = trimmed.match(/^(?:decision|결정)\s*[::]\s*(.+)$/i);
|
|
4566
|
+
if (!structured) return void 0;
|
|
4567
|
+
const payload = structured[1].trim().toLowerCase();
|
|
4568
|
+
const normalized = payload.split(/[,\s-]+/)[0]?.replace(/[^a-z_]/g, "");
|
|
4569
|
+
if (!normalized) return void 0;
|
|
4570
|
+
if (normalized === "approve" || normalized === "approved") {
|
|
4571
|
+
return "approve";
|
|
4572
|
+
}
|
|
4573
|
+
if (normalized === "changes_requested" || normalized === "changes" || normalized === "change") {
|
|
4574
|
+
return "changes_requested";
|
|
4575
|
+
}
|
|
4576
|
+
if (normalized === "blocked" || normalized === "block") {
|
|
4577
|
+
return "blocked";
|
|
4578
|
+
}
|
|
4579
|
+
return void 0;
|
|
4580
|
+
}
|
|
4581
|
+
function resolveEvidencePathValue(value) {
|
|
4582
|
+
const trimmed = value.trim();
|
|
4583
|
+
const mdLink = trimmed.match(/\(([^)]+)\)/);
|
|
4584
|
+
if (mdLink && mdLink[1]) return mdLink[1].trim();
|
|
4585
|
+
return trimmed.split(/\s+/)[0] || "";
|
|
4586
|
+
}
|
|
4587
|
+
async function isPrePrEvidenceProvided(rawValue, policy, context) {
|
|
4588
|
+
if (isPlaceholderReviewEvidence(rawValue)) return false;
|
|
4589
|
+
if (policy.evidenceMode !== "path_required") return true;
|
|
4590
|
+
if (!rawValue) return false;
|
|
4591
|
+
const evidencePath = resolveEvidencePathValue(rawValue);
|
|
4592
|
+
if (!evidencePath) return false;
|
|
4593
|
+
if (/^https?:\/\//i.test(evidencePath)) return false;
|
|
4594
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4595
|
+
if (path22.isAbsolute(evidencePath)) {
|
|
4596
|
+
candidates.add(path22.resolve(evidencePath));
|
|
4597
|
+
} else {
|
|
4598
|
+
candidates.add(path22.resolve(context.featurePath, evidencePath));
|
|
4599
|
+
candidates.add(path22.resolve(context.docsDir, evidencePath));
|
|
4600
|
+
candidates.add(path22.resolve(path22.dirname(context.docsDir), evidencePath));
|
|
4601
|
+
}
|
|
4602
|
+
for (const candidate of candidates) {
|
|
4603
|
+
if (await fs16.pathExists(candidate)) return true;
|
|
4604
|
+
}
|
|
4605
|
+
return false;
|
|
4606
|
+
}
|
|
4559
4607
|
function parseIssueNumber(value) {
|
|
4560
4608
|
if (!value) return void 0;
|
|
4561
4609
|
const match = value.match(/#?(\d+)/);
|
|
@@ -4848,9 +4896,15 @@ function isPrePrReviewSatisfied2(feature, policy) {
|
|
|
4848
4896
|
if (!feature.docs.prePrEvidenceFieldExists || !feature.prePrReview.evidenceProvided) {
|
|
4849
4897
|
return false;
|
|
4850
4898
|
}
|
|
4899
|
+
if (policy.findings === "required" && (!feature.docs.prePrFindingsFieldExists || !feature.prePrReview.findingsProvided)) {
|
|
4900
|
+
return false;
|
|
4901
|
+
}
|
|
4851
4902
|
if (!feature.docs.prePrDecisionFieldExists || !feature.prePrReview.decisionProvided) {
|
|
4852
4903
|
return false;
|
|
4853
4904
|
}
|
|
4905
|
+
if (feature.prePrReview.decisionOutcome !== "approve") {
|
|
4906
|
+
return false;
|
|
4907
|
+
}
|
|
4854
4908
|
return true;
|
|
4855
4909
|
}
|
|
4856
4910
|
async function parseFeature(featurePath, type, context, options) {
|
|
@@ -4917,9 +4971,11 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4917
4971
|
let completionChecklist;
|
|
4918
4972
|
let prePrReviewStatus;
|
|
4919
4973
|
let prePrFindings;
|
|
4974
|
+
let prePrFindingsProvided = false;
|
|
4920
4975
|
let prePrEvidence;
|
|
4921
4976
|
let prePrEvidenceProvided = false;
|
|
4922
4977
|
let prePrDecision;
|
|
4978
|
+
let prePrDecisionOutcome;
|
|
4923
4979
|
let prePrDecisionProvided = false;
|
|
4924
4980
|
let prReviewFindings;
|
|
4925
4981
|
let prReviewEvidence;
|
|
@@ -4994,6 +5050,7 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4994
5050
|
"Pre-PR Findings"
|
|
4995
5051
|
]);
|
|
4996
5052
|
prePrFindings = parseReviewFindings(prePrFindingsValue);
|
|
5053
|
+
prePrFindingsProvided = !!prePrFindings;
|
|
4997
5054
|
const prePrEvidenceValue = extractFirstSpecValue(content, [
|
|
4998
5055
|
"PR \uC804 \uB9AC\uBDF0 Evidence",
|
|
4999
5056
|
"Pre-PR Evidence"
|
|
@@ -5003,7 +5060,11 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5003
5060
|
"Pre-PR Evidence"
|
|
5004
5061
|
]);
|
|
5005
5062
|
prePrEvidence = prePrEvidenceValue?.trim();
|
|
5006
|
-
prePrEvidenceProvided =
|
|
5063
|
+
prePrEvidenceProvided = await isPrePrEvidenceProvided(
|
|
5064
|
+
prePrEvidenceValue,
|
|
5065
|
+
prePrReviewPolicy,
|
|
5066
|
+
{ featurePath, docsDir: context.docsDir }
|
|
5067
|
+
);
|
|
5007
5068
|
const prePrDecisionValue = extractFirstSpecValue(content, [
|
|
5008
5069
|
"PR \uC804 \uB9AC\uBDF0 Decision",
|
|
5009
5070
|
"Pre-PR Decision"
|
|
@@ -5013,7 +5074,8 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5013
5074
|
"Pre-PR Decision"
|
|
5014
5075
|
]);
|
|
5015
5076
|
prePrDecision = prePrDecisionValue?.trim();
|
|
5016
|
-
|
|
5077
|
+
prePrDecisionOutcome = parsePrePrDecisionOutcome(prePrDecisionValue);
|
|
5078
|
+
prePrDecisionProvided = !isPlaceholderReviewEvidence(prePrDecisionValue) && hasStructuredReviewDecision(prePrDecisionValue) && !!prePrDecisionOutcome && prePrReviewPolicy.decisionEnum.includes(prePrDecisionOutcome);
|
|
5017
5079
|
const prReviewFindingsValue = extractFirstSpecValue(content, [
|
|
5018
5080
|
"PR \uB9AC\uBDF0 Findings",
|
|
5019
5081
|
"PR Review Findings"
|
|
@@ -5188,6 +5250,9 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5188
5250
|
if (tasksExists && prePrReviewPolicy.enabled && !prePrReviewFieldExists) {
|
|
5189
5251
|
warnings.push(tr(lang, "warnings", "legacyTasksPrePrReviewField"));
|
|
5190
5252
|
}
|
|
5253
|
+
if (tasksExists && prePrReviewPolicy.enabled && prePrReviewPolicy.findings === "required" && !prePrFindingsFieldExists) {
|
|
5254
|
+
warnings.push(tr(lang, "warnings", "legacyTasksPrePrFindingsField"));
|
|
5255
|
+
}
|
|
5191
5256
|
if (tasksExists && prePrReviewPolicy.enabled && !prePrEvidenceFieldExists) {
|
|
5192
5257
|
warnings.push(tr(lang, "warnings", "legacyTasksPrePrEvidenceField"));
|
|
5193
5258
|
}
|
|
@@ -5232,12 +5297,15 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5232
5297
|
{
|
|
5233
5298
|
docs: {
|
|
5234
5299
|
prePrReviewFieldExists,
|
|
5300
|
+
prePrFindingsFieldExists,
|
|
5235
5301
|
prePrEvidenceFieldExists,
|
|
5236
5302
|
prePrDecisionFieldExists
|
|
5237
5303
|
},
|
|
5238
5304
|
prePrReview: {
|
|
5239
5305
|
status: prePrReviewStatus,
|
|
5306
|
+
findingsProvided: prePrFindingsProvided,
|
|
5240
5307
|
evidenceProvided: prePrEvidenceProvided,
|
|
5308
|
+
decisionOutcome: prePrDecisionOutcome,
|
|
5241
5309
|
decisionProvided: prePrDecisionProvided
|
|
5242
5310
|
}
|
|
5243
5311
|
},
|
|
@@ -5278,10 +5346,18 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5278
5346
|
warnings.push(tr(lang, "warnings", "workflowPrePrReviewMissing"));
|
|
5279
5347
|
} else if (prePrReviewStatus !== "Done") {
|
|
5280
5348
|
warnings.push(tr(lang, "warnings", "workflowPrePrReviewNotDone"));
|
|
5349
|
+
} else if (prePrReviewPolicy.findings === "required" && (!prePrFindingsFieldExists || !prePrFindingsProvided)) {
|
|
5350
|
+
warnings.push(tr(lang, "warnings", "workflowPrePrFindingsMissing"));
|
|
5281
5351
|
} else if (!prePrEvidenceFieldExists || !prePrEvidenceProvided) {
|
|
5282
5352
|
warnings.push(tr(lang, "warnings", "workflowPrePrEvidenceMissing"));
|
|
5283
5353
|
} else if (!prePrDecisionFieldExists || !prePrDecisionProvided) {
|
|
5284
5354
|
warnings.push(tr(lang, "warnings", "workflowPrePrDecisionMissing"));
|
|
5355
|
+
} else if (prePrDecisionOutcome !== "approve") {
|
|
5356
|
+
warnings.push(
|
|
5357
|
+
tr(lang, "warnings", "workflowPrePrDecisionNotApproved", {
|
|
5358
|
+
outcome: prePrDecisionOutcome || "-"
|
|
5359
|
+
})
|
|
5360
|
+
);
|
|
5285
5361
|
}
|
|
5286
5362
|
}
|
|
5287
5363
|
}
|
|
@@ -5307,9 +5383,11 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5307
5383
|
prePrReview: {
|
|
5308
5384
|
status: prePrReviewStatus,
|
|
5309
5385
|
findings: prePrFindings,
|
|
5386
|
+
findingsProvided: prePrFindingsProvided,
|
|
5310
5387
|
evidence: prePrEvidence,
|
|
5311
5388
|
evidenceProvided: prePrEvidenceProvided,
|
|
5312
5389
|
decision: prePrDecision,
|
|
5390
|
+
decisionOutcome: prePrDecisionOutcome,
|
|
5313
5391
|
decisionProvided: prePrDecisionProvided
|
|
5314
5392
|
},
|
|
5315
5393
|
prReview: {
|
|
@@ -5867,6 +5945,18 @@ function normalizeSkillList2(raw) {
|
|
|
5867
5945
|
}
|
|
5868
5946
|
return [...deduped];
|
|
5869
5947
|
}
|
|
5948
|
+
function normalizeDecisionEnumList2(raw) {
|
|
5949
|
+
if (!Array.isArray(raw)) return [];
|
|
5950
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
5951
|
+
for (const item of raw) {
|
|
5952
|
+
const value = String(item || "").trim().toLowerCase();
|
|
5953
|
+
if (!value) continue;
|
|
5954
|
+
if (value === "approve" || value === "changes_requested" || value === "blocked") {
|
|
5955
|
+
deduped.add(value);
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
return [...deduped];
|
|
5959
|
+
}
|
|
5870
5960
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
5871
5961
|
const configPath = path22.join(docsDir, ".lee-spec-kit.json");
|
|
5872
5962
|
if (!await fs16.pathExists(configPath)) {
|
|
@@ -5915,8 +6005,39 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
5915
6005
|
}
|
|
5916
6006
|
}
|
|
5917
6007
|
setIfMissing(prePrReview, "fallback", "builtin-checklist", "workflow.prePrReview.fallback");
|
|
5918
|
-
setIfMissing(
|
|
5919
|
-
|
|
6008
|
+
setIfMissing(
|
|
6009
|
+
prePrReview,
|
|
6010
|
+
"evidenceMode",
|
|
6011
|
+
"path_required",
|
|
6012
|
+
"workflow.prePrReview.evidenceMode"
|
|
6013
|
+
);
|
|
6014
|
+
if (prePrReview.evidenceMode !== void 0 && prePrReview.evidenceMode !== "path_required" && prePrReview.evidenceMode !== "any") {
|
|
6015
|
+
prePrReview.evidenceMode = "path_required";
|
|
6016
|
+
changedPaths.push("workflow.prePrReview.evidenceMode");
|
|
6017
|
+
}
|
|
6018
|
+
setIfMissing(
|
|
6019
|
+
prePrReview,
|
|
6020
|
+
"findings",
|
|
6021
|
+
"required",
|
|
6022
|
+
"workflow.prePrReview.findings"
|
|
6023
|
+
);
|
|
6024
|
+
if (prePrReview.findings !== void 0 && prePrReview.findings !== "required" && prePrReview.findings !== "optional") {
|
|
6025
|
+
prePrReview.findings = "required";
|
|
6026
|
+
changedPaths.push("workflow.prePrReview.findings");
|
|
6027
|
+
}
|
|
6028
|
+
if (prePrReview.decisionEnum === void 0) {
|
|
6029
|
+
prePrReview.decisionEnum = ["approve", "changes_requested", "blocked"];
|
|
6030
|
+
changedPaths.push("workflow.prePrReview.decisionEnum");
|
|
6031
|
+
} else {
|
|
6032
|
+
const normalizedDecisionEnum = normalizeDecisionEnumList2(prePrReview.decisionEnum);
|
|
6033
|
+
if (normalizedDecisionEnum.length === 0) {
|
|
6034
|
+
prePrReview.decisionEnum = ["approve", "changes_requested", "blocked"];
|
|
6035
|
+
changedPaths.push("workflow.prePrReview.decisionEnum");
|
|
6036
|
+
} else if (JSON.stringify(normalizedDecisionEnum) !== JSON.stringify(prePrReview.decisionEnum)) {
|
|
6037
|
+
prePrReview.decisionEnum = normalizedDecisionEnum;
|
|
6038
|
+
changedPaths.push("workflow.prePrReview.decisionEnum");
|
|
6039
|
+
}
|
|
6040
|
+
}
|
|
5920
6041
|
if (!isPlainObject(raw.pr)) {
|
|
5921
6042
|
raw.pr = {};
|
|
5922
6043
|
changedPaths.push("pr");
|
|
@@ -6312,21 +6433,27 @@ function buildActionDetail(action, lang) {
|
|
|
6312
6433
|
const branchMatch = command.match(/\bfeat\/([A-Za-z0-9._-]+)/);
|
|
6313
6434
|
const worktree = worktreeMatch ? `.worktrees/${worktreeMatch[1]}` : null;
|
|
6314
6435
|
const branch = branchMatch ? `feat/${branchMatch[1]}` : null;
|
|
6436
|
+
if (action.type !== "command") {
|
|
6437
|
+
return tr(lang, "cli", "context.commandDetail.branchCreateGeneric", {
|
|
6438
|
+
scope: "project"
|
|
6439
|
+
});
|
|
6440
|
+
}
|
|
6441
|
+
const scope = action.scope;
|
|
6315
6442
|
if (worktree && branch) {
|
|
6316
6443
|
return tr(lang, "cli", "context.commandDetail.branchCreateWithWorktree", {
|
|
6317
|
-
scope
|
|
6444
|
+
scope,
|
|
6318
6445
|
worktree,
|
|
6319
6446
|
branch
|
|
6320
6447
|
});
|
|
6321
6448
|
}
|
|
6322
6449
|
if (branch) {
|
|
6323
6450
|
return tr(lang, "cli", "context.commandDetail.branchCreateWithBranch", {
|
|
6324
|
-
scope
|
|
6451
|
+
scope,
|
|
6325
6452
|
branch
|
|
6326
6453
|
});
|
|
6327
6454
|
}
|
|
6328
6455
|
return tr(lang, "cli", "context.commandDetail.branchCreateGeneric", {
|
|
6329
|
-
scope
|
|
6456
|
+
scope
|
|
6330
6457
|
});
|
|
6331
6458
|
};
|
|
6332
6459
|
const extractCommitMessage = (command) => {
|
|
@@ -7182,12 +7309,18 @@ function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
|
|
|
7182
7309
|
if (prePrReviewPolicy.enabled && f.prePrReview.status !== "Done") {
|
|
7183
7310
|
return tr(lang, "cli", "context.list.completePrePrReview");
|
|
7184
7311
|
}
|
|
7312
|
+
if (prePrReviewPolicy.enabled && prePrReviewPolicy.findings === "required" && (!f.docs.prePrFindingsFieldExists || !f.prePrReview.findingsProvided)) {
|
|
7313
|
+
return tr(lang, "cli", "context.list.addPrePrFindings");
|
|
7314
|
+
}
|
|
7185
7315
|
if (prePrReviewPolicy.enabled && (!f.docs.prePrEvidenceFieldExists || !f.prePrReview.evidenceProvided)) {
|
|
7186
7316
|
return tr(lang, "cli", "context.list.addPrePrEvidence");
|
|
7187
7317
|
}
|
|
7188
7318
|
if (prePrReviewPolicy.enabled && (!f.docs.prePrDecisionFieldExists || !f.prePrReview.decisionProvided)) {
|
|
7189
7319
|
return tr(lang, "cli", "context.list.addPrePrDecision");
|
|
7190
7320
|
}
|
|
7321
|
+
if (prePrReviewPolicy.enabled && f.prePrReview.decisionOutcome !== "approve") {
|
|
7322
|
+
return tr(lang, "cli", "context.list.resolvePrePrDecision");
|
|
7323
|
+
}
|
|
7191
7324
|
if (workflowPolicy.requirePr && !f.pr.link) {
|
|
7192
7325
|
return tr(lang, "cli", "context.list.recordPrLink");
|
|
7193
7326
|
}
|
|
@@ -7244,7 +7377,9 @@ function toCompactFeature(feature) {
|
|
|
7244
7377
|
prePrReview: {
|
|
7245
7378
|
status: feature.prePrReview.status,
|
|
7246
7379
|
findings: feature.prePrReview.findings,
|
|
7380
|
+
findingsProvided: feature.prePrReview.findingsProvided,
|
|
7247
7381
|
evidenceProvided: feature.prePrReview.evidenceProvided,
|
|
7382
|
+
decisionOutcome: feature.prePrReview.decisionOutcome,
|
|
7248
7383
|
decisionProvided: feature.prePrReview.decisionProvided
|
|
7249
7384
|
},
|
|
7250
7385
|
prReview: {
|
|
@@ -7399,7 +7534,18 @@ async function runContext(featureName, options) {
|
|
|
7399
7534
|
const jsonMode = !!options.json || !!options.jsonCompact;
|
|
7400
7535
|
if (jsonMode) {
|
|
7401
7536
|
const primaryAction = state.actionOptions[0] ?? null;
|
|
7402
|
-
const
|
|
7537
|
+
const checkRequiredLabels = state.actionOptions.filter((option) => !!option.action.requiresUserCheck).map((option) => option.label);
|
|
7538
|
+
const checkRequiredCategories = [
|
|
7539
|
+
...new Set(
|
|
7540
|
+
state.actionOptions.filter((option) => !!option.action.requiresUserCheck).map((option) => option.action.category || "uncategorized")
|
|
7541
|
+
)
|
|
7542
|
+
];
|
|
7543
|
+
const approvalRequired = checkRequiredLabels.length > 0;
|
|
7544
|
+
const finalApprovalPrompt = approvalRequired ? buildFinalApprovalPrompt(lang, state.actionOptions) : "";
|
|
7545
|
+
const approvalUserFacingLines = approvalRequired ? [
|
|
7546
|
+
...state.actionOptions.map((o) => o.approvalPrompt),
|
|
7547
|
+
finalApprovalPrompt
|
|
7548
|
+
].filter((line) => line.length > 0) : [];
|
|
7403
7549
|
const approveCommand = buildApprovalCommand(
|
|
7404
7550
|
state,
|
|
7405
7551
|
featureName,
|
|
@@ -7452,19 +7598,20 @@ async function runContext(featureName, options) {
|
|
|
7452
7598
|
activeCategories,
|
|
7453
7599
|
knownCategories: ACTION_CATEGORIES,
|
|
7454
7600
|
uncategorizedLabels,
|
|
7601
|
+
checkRequiredLabels,
|
|
7602
|
+
checkRequiredCategories,
|
|
7603
|
+
approvalRequired,
|
|
7455
7604
|
categoryPolicyGuidance: 'For approval.mode="category", match against `actionOptions[].category`.',
|
|
7456
|
-
oneApprovalPerAction:
|
|
7605
|
+
oneApprovalPerAction: approvalRequired,
|
|
7457
7606
|
requireFreshContext: true,
|
|
7458
7607
|
contextVersion: state.contextVersion,
|
|
7459
7608
|
config: config.approval ?? { mode: "builtin" }
|
|
7460
7609
|
},
|
|
7461
7610
|
approvalRequest: {
|
|
7611
|
+
required: approvalRequired,
|
|
7462
7612
|
finalPrompt: finalApprovalPrompt,
|
|
7463
|
-
userFacingLines:
|
|
7464
|
-
|
|
7465
|
-
finalApprovalPrompt
|
|
7466
|
-
].filter((line) => line.length > 0),
|
|
7467
|
-
labels: state.actionOptions.map((o) => o.label),
|
|
7613
|
+
userFacingLines: approvalUserFacingLines,
|
|
7614
|
+
labels: approvalRequired ? state.actionOptions.map((o) => o.label) : [],
|
|
7468
7615
|
approveCommand,
|
|
7469
7616
|
executeCommand,
|
|
7470
7617
|
executeRequiresTicket: !!state.actionOptions[0]?.action?.requiresUserCheck
|
|
@@ -7523,27 +7670,28 @@ async function runContext(featureName, options) {
|
|
|
7523
7670
|
activeCategories,
|
|
7524
7671
|
knownCategories: ACTION_CATEGORIES,
|
|
7525
7672
|
uncategorizedLabels,
|
|
7673
|
+
checkRequiredLabels,
|
|
7674
|
+
checkRequiredCategories,
|
|
7675
|
+
approvalRequired,
|
|
7526
7676
|
categoryPolicyGuidance: 'For approval.mode="category", match against `actionOptions[].category`.',
|
|
7527
|
-
requireExplanationBeforeApproval:
|
|
7528
|
-
requiredExplanationFields: [
|
|
7677
|
+
requireExplanationBeforeApproval: approvalRequired,
|
|
7678
|
+
requiredExplanationFields: approvalRequired ? [
|
|
7529
7679
|
"actionOptions[].label",
|
|
7530
7680
|
"actionOptions[].detail",
|
|
7531
7681
|
"actionOptions[].approvalPrompt"
|
|
7532
|
-
],
|
|
7682
|
+
] : [],
|
|
7533
7683
|
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.",
|
|
7534
|
-
oneApprovalPerAction:
|
|
7684
|
+
oneApprovalPerAction: approvalRequired,
|
|
7535
7685
|
requireFreshContext: true,
|
|
7536
7686
|
contextVersion: state.contextVersion,
|
|
7537
7687
|
config: config.approval ?? { mode: "builtin" }
|
|
7538
7688
|
},
|
|
7539
7689
|
approvalRequest: {
|
|
7540
7690
|
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`.",
|
|
7691
|
+
required: approvalRequired,
|
|
7541
7692
|
finalPrompt: finalApprovalPrompt,
|
|
7542
|
-
userFacingLines:
|
|
7543
|
-
|
|
7544
|
-
finalApprovalPrompt
|
|
7545
|
-
].filter((line) => line.length > 0),
|
|
7546
|
-
labels: state.actionOptions.map((o) => o.label),
|
|
7693
|
+
userFacingLines: approvalUserFacingLines,
|
|
7694
|
+
labels: approvalRequired ? state.actionOptions.map((o) => o.label) : [],
|
|
7547
7695
|
approveCommand,
|
|
7548
7696
|
executeCommand,
|
|
7549
7697
|
executeRequiresTicket: !!state.actionOptions[0]?.action?.requiresUserCheck,
|
|
@@ -7823,7 +7971,7 @@ async function runContext(featureName, options) {
|
|
|
7823
7971
|
);
|
|
7824
7972
|
}
|
|
7825
7973
|
}
|
|
7826
|
-
if (actionOptions.length > 0) {
|
|
7974
|
+
if (actionOptions.length > 0 && hasCheckAction) {
|
|
7827
7975
|
const finalApprovalPrompt = buildFinalApprovalPrompt(lang, actionOptions);
|
|
7828
7976
|
const approveCommand = buildApprovalCommand(
|
|
7829
7977
|
state,
|
|
@@ -7950,7 +8098,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7950
8098
|
contextVersion: freshState.contextVersion,
|
|
7951
8099
|
executable: selectedAction.type === "command",
|
|
7952
8100
|
executeRequiresTicket,
|
|
7953
|
-
oneApprovalPerAction:
|
|
8101
|
+
oneApprovalPerAction: executeRequiresTicket,
|
|
7954
8102
|
approvalTicket: ticket ? {
|
|
7955
8103
|
token: ticket.token,
|
|
7956
8104
|
sessionId: ticket.sessionId,
|
|
@@ -9014,6 +9162,9 @@ function toAutoReasonCode(status) {
|
|
|
9014
9162
|
return "AUTO_EXECUTION_FAILED";
|
|
9015
9163
|
}
|
|
9016
9164
|
}
|
|
9165
|
+
function isAutoRunFailureStatus(status) {
|
|
9166
|
+
return status === "manual_required" || status === "selection_required" || status === "no_progress" || status === "request_label_missing" || status === "request_failed" || status === "execution_failed";
|
|
9167
|
+
}
|
|
9017
9168
|
async function runAutoUntilCategory(config, featureName, selectionOptions, untilCategories, requestText, metadata) {
|
|
9018
9169
|
const contextArgs = ["context", ...buildSelectionArgs(featureName, selectionOptions)];
|
|
9019
9170
|
const gateSet = new Set(untilCategories);
|
|
@@ -9422,9 +9573,10 @@ async function runFlow(featureName, options) {
|
|
|
9422
9573
|
}
|
|
9423
9574
|
}
|
|
9424
9575
|
if (options.json) {
|
|
9576
|
+
const autoRunFailed = !!(autoRun && isAutoRunFailureStatus(autoRun.status));
|
|
9425
9577
|
const payload = {
|
|
9426
|
-
status: "ok",
|
|
9427
|
-
reasonCode: "FLOW_SUMMARY",
|
|
9578
|
+
status: autoRunFailed ? "error" : "ok",
|
|
9579
|
+
reasonCode: autoRunFailed ? autoRun?.reasonCode || "AUTO_EXECUTION_FAILED" : "FLOW_SUMMARY",
|
|
9428
9580
|
context: {
|
|
9429
9581
|
before: {
|
|
9430
9582
|
status: before.status,
|
|
@@ -9453,6 +9605,9 @@ async function runFlow(featureName, options) {
|
|
|
9453
9605
|
suggestion: after.matchedFeature ? `npx lee-spec-kit context ${after.matchedFeature.folderName}${componentHint}` : `npx lee-spec-kit context${componentHint}`
|
|
9454
9606
|
};
|
|
9455
9607
|
console.log(JSON.stringify(payload, null, 2));
|
|
9608
|
+
if (autoRunFailed) {
|
|
9609
|
+
process.exitCode = 1;
|
|
9610
|
+
}
|
|
9456
9611
|
return;
|
|
9457
9612
|
}
|
|
9458
9613
|
console.log();
|
|
@@ -9498,6 +9653,9 @@ async function runFlow(featureName, options) {
|
|
|
9498
9653
|
console.log(chalk6.gray("Auto gate reached. Reply with one of the labels shown above (example: A OK)."));
|
|
9499
9654
|
console.log();
|
|
9500
9655
|
}
|
|
9656
|
+
if (autoRun && isAutoRunFailureStatus(autoRun.status)) {
|
|
9657
|
+
process.exitCode = 1;
|
|
9658
|
+
}
|
|
9501
9659
|
if (after.matchedFeature) {
|
|
9502
9660
|
console.log(
|
|
9503
9661
|
chalk6.blue(
|
|
@@ -11986,6 +12144,263 @@ async function runOnboard(options) {
|
|
|
11986
12144
|
process.exitCode = 1;
|
|
11987
12145
|
}
|
|
11988
12146
|
}
|
|
12147
|
+
function escapeRegExp4(value) {
|
|
12148
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12149
|
+
}
|
|
12150
|
+
function normalizeDecision(raw) {
|
|
12151
|
+
if (!raw) return null;
|
|
12152
|
+
const value = raw.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
12153
|
+
if (value === "approve" || value === "approved") return "approve";
|
|
12154
|
+
if (value === "changes_requested" || value === "change_requested" || value === "changes") {
|
|
12155
|
+
return "changes_requested";
|
|
12156
|
+
}
|
|
12157
|
+
if (value === "blocked" || value === "block") return "blocked";
|
|
12158
|
+
return null;
|
|
12159
|
+
}
|
|
12160
|
+
function parseNonNegativeInt(raw, label) {
|
|
12161
|
+
if (raw === void 0) return null;
|
|
12162
|
+
const value = Number(raw);
|
|
12163
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
12164
|
+
throw createCliError(
|
|
12165
|
+
"INVALID_ARGUMENT",
|
|
12166
|
+
`\`${label}\` must be a non-negative integer.`
|
|
12167
|
+
);
|
|
12168
|
+
}
|
|
12169
|
+
return value;
|
|
12170
|
+
}
|
|
12171
|
+
function findSpecLineIndex(lines, keys) {
|
|
12172
|
+
const escaped = keys.map((key) => escapeRegExp4(key));
|
|
12173
|
+
const re = new RegExp(`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`);
|
|
12174
|
+
return lines.findIndex((line) => re.test(line));
|
|
12175
|
+
}
|
|
12176
|
+
function replaceSpecLine(line, keys, preferredKey, value) {
|
|
12177
|
+
const escaped = keys.map((key) => escapeRegExp4(key));
|
|
12178
|
+
const re = new RegExp(
|
|
12179
|
+
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
12180
|
+
);
|
|
12181
|
+
if (!re.test(line)) return line;
|
|
12182
|
+
return line.replace(re, `$1${preferredKey}$2${value}`);
|
|
12183
|
+
}
|
|
12184
|
+
function computeInsertIndex(lines, anchorKeys) {
|
|
12185
|
+
const anchorIndex = findSpecLineIndex(lines, anchorKeys);
|
|
12186
|
+
if (anchorIndex !== -1) {
|
|
12187
|
+
let cursor = anchorIndex + 1;
|
|
12188
|
+
while (cursor < lines.length && /^\s{2,}-\s+/.test(lines[cursor])) {
|
|
12189
|
+
cursor += 1;
|
|
12190
|
+
}
|
|
12191
|
+
return cursor;
|
|
12192
|
+
}
|
|
12193
|
+
const sectionIndex = lines.findIndex(
|
|
12194
|
+
(line) => /^\s*##\s+(Task List|태스크 목록)\s*$/.test(line)
|
|
12195
|
+
);
|
|
12196
|
+
if (sectionIndex !== -1) return sectionIndex;
|
|
12197
|
+
return lines.length;
|
|
12198
|
+
}
|
|
12199
|
+
function upsertSpecLine(content, keys, preferredKey, value, anchorKeys) {
|
|
12200
|
+
const lines = content.split("\n");
|
|
12201
|
+
const index = findSpecLineIndex(lines, keys);
|
|
12202
|
+
if (index !== -1) {
|
|
12203
|
+
lines[index] = replaceSpecLine(lines[index], keys, preferredKey, value);
|
|
12204
|
+
return lines.join("\n");
|
|
12205
|
+
}
|
|
12206
|
+
const insertAt = computeInsertIndex(lines, anchorKeys);
|
|
12207
|
+
lines.splice(insertAt, 0, `- **${preferredKey}**: ${value}`);
|
|
12208
|
+
return lines.join("\n");
|
|
12209
|
+
}
|
|
12210
|
+
function normalizePathForDoc(value) {
|
|
12211
|
+
return value.replace(/\\/g, "/");
|
|
12212
|
+
}
|
|
12213
|
+
function getPreferredKeys(lang) {
|
|
12214
|
+
if (lang === "ko") {
|
|
12215
|
+
return {
|
|
12216
|
+
review: "PR \uC804 \uB9AC\uBDF0",
|
|
12217
|
+
findings: "PR \uC804 \uB9AC\uBDF0 Findings",
|
|
12218
|
+
evidence: "PR \uC804 \uB9AC\uBDF0 Evidence",
|
|
12219
|
+
decision: "PR \uC804 \uB9AC\uBDF0 Decision",
|
|
12220
|
+
prStatus: "PR \uC0C1\uD0DC"
|
|
12221
|
+
};
|
|
12222
|
+
}
|
|
12223
|
+
return {
|
|
12224
|
+
review: "Pre-PR Review",
|
|
12225
|
+
findings: "Pre-PR Findings",
|
|
12226
|
+
evidence: "Pre-PR Evidence",
|
|
12227
|
+
decision: "Pre-PR Decision",
|
|
12228
|
+
prStatus: "PR Status"
|
|
12229
|
+
};
|
|
12230
|
+
}
|
|
12231
|
+
function buildReportContent(input) {
|
|
12232
|
+
const skills = input.skills.length > 0 ? input.skills.join(", ") : "code-review-excellence";
|
|
12233
|
+
return `# Pre-PR Review Report: ${input.folderName}
|
|
12234
|
+
|
|
12235
|
+
- Date: ${input.date}
|
|
12236
|
+
- Baseline: ${input.fallback}
|
|
12237
|
+
- Skills: ${skills}
|
|
12238
|
+
- Findings: major=${input.major}, minor=${input.minor}
|
|
12239
|
+
- Decision: ${input.decision}
|
|
12240
|
+
- Note: ${input.note}
|
|
12241
|
+
|
|
12242
|
+
## Baseline Checklist
|
|
12243
|
+
|
|
12244
|
+
- [x] Checked alignment with spec/plan/tasks.
|
|
12245
|
+
- [x] Reviewed risk areas (regression, security, side effects, release readiness).
|
|
12246
|
+
- [x] Reviewed maintainability (structure, reuse, obsolete code cleanup).
|
|
12247
|
+
- [x] Reviewed test/verification coverage (or recorded reason if not run).
|
|
12248
|
+
`;
|
|
12249
|
+
}
|
|
12250
|
+
function prePrReviewCommand(program2) {
|
|
12251
|
+
program2.command("pre-pr-review [feature-name]").description("Run and record pre-PR review evidence for a feature").option("--component <component>", "Component name for multi projects").option(
|
|
12252
|
+
"--decision <outcome>",
|
|
12253
|
+
"Decision outcome: approve | changes_requested | blocked"
|
|
12254
|
+
).option("--major <count>", "Pre-PR major findings count").option("--minor <count>", "Pre-PR minor findings count").option("--note <text>", "Decision note text").option("--json", "Output JSON").action(async (featureName, options) => {
|
|
12255
|
+
try {
|
|
12256
|
+
await runPrePrReview(featureName, options);
|
|
12257
|
+
} catch (error) {
|
|
12258
|
+
const config = await getConfig(process.cwd());
|
|
12259
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
12260
|
+
const cliError = toCliError(error);
|
|
12261
|
+
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
12262
|
+
if (options.json) {
|
|
12263
|
+
console.log(
|
|
12264
|
+
JSON.stringify({
|
|
12265
|
+
status: "error",
|
|
12266
|
+
reasonCode: cliError.code,
|
|
12267
|
+
error: cliError.message,
|
|
12268
|
+
suggestions
|
|
12269
|
+
})
|
|
12270
|
+
);
|
|
12271
|
+
} else {
|
|
12272
|
+
console.error(chalk6.red(`[${cliError.code}] ${cliError.message}`));
|
|
12273
|
+
printCliErrorSuggestions(suggestions, lang);
|
|
12274
|
+
}
|
|
12275
|
+
process.exitCode = 1;
|
|
12276
|
+
}
|
|
12277
|
+
});
|
|
12278
|
+
}
|
|
12279
|
+
async function runPrePrReview(featureName, options) {
|
|
12280
|
+
const config = await getConfig(process.cwd());
|
|
12281
|
+
if (!config) {
|
|
12282
|
+
throw createCliError(
|
|
12283
|
+
"CONFIG_NOT_FOUND",
|
|
12284
|
+
"No lee-spec-kit config found in this workspace."
|
|
12285
|
+
);
|
|
12286
|
+
}
|
|
12287
|
+
const selectionOptions = {
|
|
12288
|
+
component: resolveComponentOption(options.component)
|
|
12289
|
+
};
|
|
12290
|
+
const state = await resolveContextSelection(config, featureName, selectionOptions);
|
|
12291
|
+
if (state.status !== "single_matched" || !state.matchedFeature) {
|
|
12292
|
+
throw createCliError(
|
|
12293
|
+
"CONTEXT_SELECTION_REQUIRED",
|
|
12294
|
+
"pre-pr-review requires a single matched feature. Pass <feature-name> explicitly."
|
|
12295
|
+
);
|
|
12296
|
+
}
|
|
12297
|
+
const feature = state.matchedFeature;
|
|
12298
|
+
if (!feature.docs.tasksExists) {
|
|
12299
|
+
throw createCliError(
|
|
12300
|
+
"PRECONDITION_FAILED",
|
|
12301
|
+
`tasks.md not found for feature: ${feature.folderName}`
|
|
12302
|
+
);
|
|
12303
|
+
}
|
|
12304
|
+
const tasksPath = path22.join(feature.path, "tasks.md");
|
|
12305
|
+
const tasksContent = await fs16.readFile(tasksPath, "utf-8");
|
|
12306
|
+
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
12307
|
+
const preferred = getPreferredKeys(config.lang);
|
|
12308
|
+
const date = getLocalDateString();
|
|
12309
|
+
const major = parseNonNegativeInt(options.major, "--major") ?? feature.prePrReview.findings?.major ?? 0;
|
|
12310
|
+
const minor = parseNonNegativeInt(options.minor, "--minor") ?? feature.prePrReview.findings?.minor ?? 0;
|
|
12311
|
+
const explicitDecision = normalizeDecision(options.decision);
|
|
12312
|
+
if (options.decision && !explicitDecision) {
|
|
12313
|
+
throw createCliError(
|
|
12314
|
+
"INVALID_ARGUMENT",
|
|
12315
|
+
"`--decision` must be one of: approve, changes_requested, blocked."
|
|
12316
|
+
);
|
|
12317
|
+
}
|
|
12318
|
+
const inferredDecision = major > 0 ? "changes_requested" : "approve";
|
|
12319
|
+
const decision = explicitDecision || inferredDecision;
|
|
12320
|
+
if (!policy.decisionEnum.includes(decision)) {
|
|
12321
|
+
throw createCliError(
|
|
12322
|
+
"INVALID_ARGUMENT",
|
|
12323
|
+
`Decision "${decision}" is not allowed by workflow.prePrReview.decisionEnum.`
|
|
12324
|
+
);
|
|
12325
|
+
}
|
|
12326
|
+
const note = options.note?.trim() || (decision === "approve" ? "baseline review completed without blocking findings" : decision === "changes_requested" ? "resolve findings before PR creation" : "blocked until prerequisite risk is resolved");
|
|
12327
|
+
const reportPath = path22.join(feature.path, "pre-pr-review.md");
|
|
12328
|
+
const reportContent = buildReportContent({
|
|
12329
|
+
folderName: feature.folderName,
|
|
12330
|
+
date,
|
|
12331
|
+
decision,
|
|
12332
|
+
major,
|
|
12333
|
+
minor,
|
|
12334
|
+
note,
|
|
12335
|
+
fallback: policy.fallback,
|
|
12336
|
+
skills: policy.skills
|
|
12337
|
+
});
|
|
12338
|
+
await fs16.writeFile(reportPath, reportContent, "utf-8");
|
|
12339
|
+
const reportPathFromDocs = normalizePathForDoc(
|
|
12340
|
+
path22.join(feature.docs.featurePathFromDocs, "pre-pr-review.md")
|
|
12341
|
+
);
|
|
12342
|
+
const evidencePath = path22.basename(config.docsDir) === "docs" ? normalizePathForDoc(path22.join("docs", reportPathFromDocs)) : reportPathFromDocs;
|
|
12343
|
+
let nextTasks = tasksContent;
|
|
12344
|
+
nextTasks = upsertSpecLine(
|
|
12345
|
+
nextTasks,
|
|
12346
|
+
["PR \uC804 \uB9AC\uBDF0", "Pre-PR Review"],
|
|
12347
|
+
preferred.review,
|
|
12348
|
+
"Done",
|
|
12349
|
+
["PR \uC0C1\uD0DC", "PR Status"]
|
|
12350
|
+
);
|
|
12351
|
+
nextTasks = upsertSpecLine(
|
|
12352
|
+
nextTasks,
|
|
12353
|
+
["PR \uC804 \uB9AC\uBDF0 Findings", "Pre-PR Findings"],
|
|
12354
|
+
preferred.findings,
|
|
12355
|
+
`major=${major}, minor=${minor}`,
|
|
12356
|
+
["PR \uC804 \uB9AC\uBDF0", "Pre-PR Review"]
|
|
12357
|
+
);
|
|
12358
|
+
nextTasks = upsertSpecLine(
|
|
12359
|
+
nextTasks,
|
|
12360
|
+
["PR \uC804 \uB9AC\uBDF0 Evidence", "Pre-PR Evidence"],
|
|
12361
|
+
preferred.evidence,
|
|
12362
|
+
evidencePath,
|
|
12363
|
+
["PR \uC804 \uB9AC\uBDF0 Findings", "Pre-PR Findings", "PR \uC804 \uB9AC\uBDF0", "Pre-PR Review"]
|
|
12364
|
+
);
|
|
12365
|
+
nextTasks = upsertSpecLine(
|
|
12366
|
+
nextTasks,
|
|
12367
|
+
["PR \uC804 \uB9AC\uBDF0 Decision", "Pre-PR Decision"],
|
|
12368
|
+
preferred.decision,
|
|
12369
|
+
`decision: ${decision} - ${note}`,
|
|
12370
|
+
["PR \uC804 \uB9AC\uBDF0 Evidence", "Pre-PR Evidence"]
|
|
12371
|
+
);
|
|
12372
|
+
if (nextTasks !== tasksContent) {
|
|
12373
|
+
await fs16.writeFile(tasksPath, nextTasks, "utf-8");
|
|
12374
|
+
}
|
|
12375
|
+
if (options.json) {
|
|
12376
|
+
console.log(
|
|
12377
|
+
JSON.stringify(
|
|
12378
|
+
{
|
|
12379
|
+
status: "ok",
|
|
12380
|
+
reasonCode: "PRE_PR_REVIEW_RECORDED",
|
|
12381
|
+
feature: feature.folderName,
|
|
12382
|
+
reportPath: normalizePathForDoc(reportPath),
|
|
12383
|
+
evidencePath,
|
|
12384
|
+
decision,
|
|
12385
|
+
findings: { major, minor },
|
|
12386
|
+
tasksUpdated: nextTasks !== tasksContent
|
|
12387
|
+
},
|
|
12388
|
+
null,
|
|
12389
|
+
2
|
|
12390
|
+
)
|
|
12391
|
+
);
|
|
12392
|
+
return;
|
|
12393
|
+
}
|
|
12394
|
+
console.log();
|
|
12395
|
+
console.log(chalk6.green(`\u2705 pre-pr-review completed: ${feature.folderName}`));
|
|
12396
|
+
console.log(chalk6.gray(`- Decision: ${decision}`));
|
|
12397
|
+
console.log(chalk6.gray(`- Findings: major=${major}, minor=${minor}`));
|
|
12398
|
+
console.log(chalk6.gray(`- Report: ${reportPath}`));
|
|
12399
|
+
if (nextTasks !== tasksContent) {
|
|
12400
|
+
console.log(chalk6.gray(`- tasks.md updated: ${tasksPath}`));
|
|
12401
|
+
}
|
|
12402
|
+
console.log();
|
|
12403
|
+
}
|
|
11989
12404
|
function isBannerDisabled() {
|
|
11990
12405
|
const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
|
|
11991
12406
|
return v === "1";
|
|
@@ -12166,6 +12581,7 @@ githubCommand(program);
|
|
|
12166
12581
|
docsCommand(program);
|
|
12167
12582
|
detectCommand(program);
|
|
12168
12583
|
onboardCommand(program);
|
|
12584
|
+
prePrReviewCommand(program);
|
|
12169
12585
|
await program.parseAsync();
|
|
12170
12586
|
//# sourceMappingURL=index.js.map
|
|
12171
12587
|
//# sourceMappingURL=index.js.map
|