lee-spec-kit 0.6.23 → 0.6.25
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 +629 -107
- 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/agents/skills/execute-task.md +6 -2
- 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/agents/skills/execute-task.md +4 -2
- 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
|
@@ -163,6 +163,9 @@ var ko = {
|
|
|
163
163
|
"context.suggestionHeader": "\uCD94\uCC9C \uB2E4\uC74C \uC120\uD0DD\uC9C0",
|
|
164
164
|
"context.suggestionCommandHint": "\uB77C\uBCA8 \uCC38\uACE0 \uBA85\uB839: {command}",
|
|
165
165
|
"context.suggestionFinalPrompt": "\uD604\uC7AC \uCD94\uCC9C \uB77C\uBCA8: {labels}. \uC751\uB2F5\uC740 \uB77C\uBCA8 \uD1A0\uD070 \uD3EC\uD568 \uD615\uC2DD\uC73C\uB85C \uD574\uC8FC\uC138\uC694. (\uC608: {example}, `A \uC9C4\uD589\uD574`)",
|
|
166
|
+
"context.autoRunUnavailable": "\uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8\uC5D0\uC11C\uB294 \uC790\uB3D9 \uC2E4\uD589\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
167
|
+
"context.autoRunSummary": "config \uAE30\uC900\uC73C\uB85C \uC2B9\uC778 \uD544\uC694 \uCE74\uD14C\uACE0\uB9AC \uC804\uAE4C\uC9C0 \uC5F0\uC18D \uC2E4\uD589\uD558\uC138\uC694: {categories}",
|
|
168
|
+
"context.autoRunCommandHint": "\uC790\uB3D9 \uC2E4\uD589 \uBA85\uB839(config \uAC8C\uC774\uD2B8): {command}",
|
|
166
169
|
"context.commandDetail.branchCreateWithWorktree": "({scope}) worktree {worktree}\uB97C \uC0AC\uC6A9\uD574 \uBE0C\uB79C\uCE58 {branch}\uB97C \uC0DD\uC131\uD558\uAC70\uB098 \uC7AC\uC0AC\uC6A9\uD558\uC138\uC694",
|
|
167
170
|
"context.commandDetail.branchCreateWithBranch": "({scope}) \uBE0C\uB79C\uCE58 {branch}\uC6A9 worktree\uB97C \uC0DD\uC131\uD558\uAC70\uB098 \uC7AC\uC0AC\uC6A9\uD558\uC138\uC694",
|
|
168
171
|
"context.commandDetail.branchCreateGeneric": "({scope}) feature \uBE0C\uB79C\uCE58\uC6A9 worktree\uB97C \uC0DD\uC131\uD558\uAC70\uB098 \uC7AC\uC0AC\uC6A9\uD558\uC138\uC694",
|
|
@@ -206,8 +209,10 @@ var ko = {
|
|
|
206
209
|
"context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
|
|
207
210
|
"context.list.addPrePrReviewField": "Pre-PR Review \uD544\uB4DC \uCD94\uAC00",
|
|
208
211
|
"context.list.completePrePrReview": "Pre-PR \uB9AC\uBDF0 \uC644\uB8CC \uCC98\uB9AC",
|
|
212
|
+
"context.list.addPrePrFindings": "Pre-PR Findings \uAE30\uB85D",
|
|
209
213
|
"context.list.addPrePrEvidence": "Pre-PR Evidence \uADFC\uAC70 \uCD94\uAC00",
|
|
210
214
|
"context.list.addPrePrDecision": "Pre-PR Decision \uAE30\uB85D",
|
|
215
|
+
"context.list.resolvePrePrDecision": "Pre-PR Decision\uC744 approve\uB85C \uC815\uB9AC",
|
|
211
216
|
"context.list.addPrReviewEvidence": "PR \uB9AC\uBDF0 Evidence \uC694\uC57D \uCD94\uAC00",
|
|
212
217
|
"context.list.addPrReviewDecision": "PR \uB9AC\uBDF0 Decision \uAE30\uB85D",
|
|
213
218
|
"context.list.setPrStatus": "PR \uC0C1\uD0DC \uC124\uC815",
|
|
@@ -490,8 +495,8 @@ var ko = {
|
|
|
490
495
|
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
496
|
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
497
|
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
|
-
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
|
|
494
|
-
startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4: "{title}" ({done}/{total}) \
|
|
498
|
+
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',
|
|
499
|
+
startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4: "{title}" ({done}/{total}) \uC791\uC5C5\uC744 \uC2DC\uC791\uD558\uBA74 DOING \uCC98\uB9AC',
|
|
495
500
|
checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total})",
|
|
496
501
|
taskCommitGateStrictBlock: "\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB85C \uB118\uC5B4\uAC00\uAE30 \uC804\uC5D0 `1 \uD0DC\uC2A4\uD06C = 1 \uCEE4\uBC0B` \uADDC\uCE59\uC744 \uCDA9\uC871\uD574\uC57C \uD569\uB2C8\uB2E4. \uC810\uAC80 \uACB0\uACFC: {reason}. \uD0DC\uC2A4\uD06C \uCEE4\uBC0B \uB2E8\uC704\uB97C \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC9C4\uD589\uD558\uC138\uC694.",
|
|
497
502
|
taskCommitGateWarnProceed: "\u26A0\uFE0F \uD0DC\uC2A4\uD06C \uCEE4\uBC0B \uB2E8\uC704 \uC810\uAC80 \uACBD\uACE0: {reason}. \uD604\uC7AC\uB294 \uC9C4\uD589 \uAC00\uB2A5\uD558\uC9C0\uB9CC `1 \uD0DC\uC2A4\uD06C = 1 \uCEE4\uBC0B`\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
|
|
@@ -542,6 +547,7 @@ var ko = {
|
|
|
542
547
|
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
548
|
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
549
|
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`)",
|
|
550
|
+
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
551
|
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
552
|
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
553
|
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 +566,10 @@ var ko = {
|
|
|
560
566
|
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
567
|
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
568
|
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
|
-
|
|
569
|
+
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)",
|
|
570
|
+
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.)",
|
|
571
|
+
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)",
|
|
572
|
+
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
573
|
}
|
|
566
574
|
};
|
|
567
575
|
var ko_default = ko;
|
|
@@ -645,6 +653,9 @@ var en = {
|
|
|
645
653
|
"context.suggestionHeader": "Suggested Next Options",
|
|
646
654
|
"context.suggestionCommandHint": "Reference command: {command}",
|
|
647
655
|
"context.suggestionFinalPrompt": "Recommended labels now: {labels}. Please reply with a format that includes a label token. (e.g. {example}, `A proceed`)",
|
|
656
|
+
"context.autoRunUnavailable": "Auto-run is not available in the current context.",
|
|
657
|
+
"context.autoRunSummary": "Run continuously by config until approval-required categories appear: {categories}",
|
|
658
|
+
"context.autoRunCommandHint": "Auto-run command (config-based gate): {command}",
|
|
648
659
|
"context.commandDetail.branchCreateWithWorktree": "({scope}) create or reuse worktree {worktree} for branch {branch}",
|
|
649
660
|
"context.commandDetail.branchCreateWithBranch": "({scope}) create or reuse worktree for branch {branch}",
|
|
650
661
|
"context.commandDetail.branchCreateGeneric": "({scope}) create or reuse feature branch worktree",
|
|
@@ -688,8 +699,10 @@ var en = {
|
|
|
688
699
|
"context.list.recordPrLink": "Record PR link",
|
|
689
700
|
"context.list.addPrePrReviewField": "Add Pre-PR Review field",
|
|
690
701
|
"context.list.completePrePrReview": "Complete Pre-PR review",
|
|
702
|
+
"context.list.addPrePrFindings": "Record Pre-PR Findings",
|
|
691
703
|
"context.list.addPrePrEvidence": "Add Pre-PR Evidence",
|
|
692
704
|
"context.list.addPrePrDecision": "Add Pre-PR Decision",
|
|
705
|
+
"context.list.resolvePrePrDecision": "Resolve Pre-PR decision to approve",
|
|
693
706
|
"context.list.addPrReviewEvidence": "Add PR Review Evidence summary",
|
|
694
707
|
"context.list.addPrReviewDecision": "Add PR Review Decision",
|
|
695
708
|
"context.list.setPrStatus": "Set PR Status",
|
|
@@ -972,8 +985,8 @@ var en = {
|
|
|
972
985
|
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
986
|
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
987
|
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
|
-
finishDoingTask: 'Continue working on the current DOING/REVIEW task: "{title}" ({done}/{total}) After it is complete, share outcome/verification and
|
|
976
|
-
startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total})
|
|
988
|
+
finishDoingTask: 'Continue working on the current DOING/REVIEW task: "{title}" ({done}/{total}) After it is complete, share outcome/verification and mark it DONE',
|
|
989
|
+
startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) Mark it DOING when you begin work',
|
|
977
990
|
checkTaskStatuses: "Check task statuses. ({done}/{total})",
|
|
978
991
|
taskCommitGateStrictBlock: "Before moving to the next TODO task, you must satisfy the `1 task = 1 commit` rule. Check result: {reason}. Re-align task commit boundaries, then continue.",
|
|
979
992
|
taskCommitGateWarnProceed: "\u26A0\uFE0F Task commit boundary warning: {reason}. You may continue, but `1 task = 1 commit` is recommended.",
|
|
@@ -1024,6 +1037,7 @@ var en = {
|
|
|
1024
1037
|
legacyTasksDocStatusField: "Legacy tasks.md format detected. Add a `Doc Status` field (Draft/Review/Approved) to enable tasks approval.",
|
|
1025
1038
|
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
|
|
1026
1039
|
legacyTasksPrePrReviewField: "Legacy tasks.md format detected. Add `Pre-PR Review` before PR steps. (`- **Pre-PR Review**: Pending | Done`)",
|
|
1040
|
+
legacyTasksPrePrFindingsField: "Legacy tasks.md format detected. Add `Pre-PR Findings` before PR steps. (`- **Pre-PR Findings**: major=0, minor=0`)",
|
|
1027
1041
|
legacyTasksPrePrEvidenceField: "Legacy tasks.md format detected. Add `Pre-PR Evidence` before PR steps.",
|
|
1028
1042
|
legacyTasksPrePrDecisionField: "Legacy tasks.md format detected. Add `Pre-PR Decision` before PR steps. (`- **Pre-PR Decision**: decision: ...`)",
|
|
1029
1043
|
legacyTasksPrReviewEvidenceField: "Legacy tasks.md format detected. Add `PR Review Evidence` before review iteration.",
|
|
@@ -1042,8 +1056,10 @@ var en = {
|
|
|
1042
1056
|
workflowPrRemoteChecksPending: "Remote PR has {count} pending check(s). Wait for checks to complete, then re-check.",
|
|
1043
1057
|
workflowPrePrReviewMissing: "Implementation is done but `Pre-PR Review` is missing. (Add `- **Pre-PR Review**: Pending | Done` in tasks.md.)",
|
|
1044
1058
|
workflowPrePrReviewNotDone: "Implementation is done but `Pre-PR Review` is not Done. (Run pre-PR review, then update it to Done.)",
|
|
1045
|
-
|
|
1046
|
-
|
|
1059
|
+
workflowPrePrFindingsMissing: "Implementation is done but `Pre-PR Findings` is missing/invalid. (Use `major=<n>, minor=<n>`.)",
|
|
1060
|
+
workflowPrePrEvidenceMissing: "Implementation is done but `Pre-PR Evidence` is empty/invalid. (Record a real existing path when path_required policy is enabled.)",
|
|
1061
|
+
workflowPrePrDecisionMissing: "Implementation is done but `Pre-PR Decision` is empty/invalid. (Use `decision: approve|changes_requested|blocked ...`.)",
|
|
1062
|
+
workflowPrePrDecisionNotApproved: "Implementation is done but `Pre-PR Decision` is `{outcome}`. Resolve findings and re-run pre-PR review until decision becomes `approve`."
|
|
1047
1063
|
}
|
|
1048
1064
|
};
|
|
1049
1065
|
var en_default = en;
|
|
@@ -1066,7 +1082,10 @@ var I18N = {
|
|
|
1066
1082
|
};
|
|
1067
1083
|
function tr(lang, category, key, vars = {}) {
|
|
1068
1084
|
const safeLang = normalizeLang(lang);
|
|
1069
|
-
const
|
|
1085
|
+
const safeCategory = I18N[safeLang]?.[category];
|
|
1086
|
+
const defaultCategory = I18N[DEFAULT_LANG]?.[category];
|
|
1087
|
+
const koCategory = I18N.ko?.[category];
|
|
1088
|
+
const template = safeCategory?.[key] ?? defaultCategory?.[key] ?? koCategory?.[key] ?? `${category}.${key}`;
|
|
1070
1089
|
return formatTemplate(template, vars);
|
|
1071
1090
|
}
|
|
1072
1091
|
|
|
@@ -2259,7 +2278,10 @@ async function runInit(options) {
|
|
|
2259
2278
|
},
|
|
2260
2279
|
prePrReview: {
|
|
2261
2280
|
skills: ["code-review-excellence"],
|
|
2262
|
-
|
|
2281
|
+
fallback: "builtin-checklist",
|
|
2282
|
+
evidenceMode: "path_required",
|
|
2283
|
+
findings: "required",
|
|
2284
|
+
decisionEnum: ["approve", "changes_requested", "blocked"]
|
|
2263
2285
|
}
|
|
2264
2286
|
},
|
|
2265
2287
|
pr: {
|
|
@@ -2878,6 +2900,11 @@ var ACTION_CATEGORIES = [
|
|
|
2878
2900
|
|
|
2879
2901
|
// src/utils/workflow.ts
|
|
2880
2902
|
var DEFAULT_PRE_PR_REVIEW_SKILLS = ["code-review-excellence"];
|
|
2903
|
+
var DEFAULT_PRE_PR_DECISION_ENUM = [
|
|
2904
|
+
"approve",
|
|
2905
|
+
"changes_requested",
|
|
2906
|
+
"blocked"
|
|
2907
|
+
];
|
|
2881
2908
|
function resolveWorkflowPolicy(workflow) {
|
|
2882
2909
|
const mode = workflow?.mode === "local" ? "local" : "github";
|
|
2883
2910
|
const policy = mode === "local" ? {
|
|
@@ -2939,17 +2966,42 @@ function normalizeSkillList(input) {
|
|
|
2939
2966
|
}
|
|
2940
2967
|
return [...deduped];
|
|
2941
2968
|
}
|
|
2969
|
+
function normalizeDecisionEnumList(input) {
|
|
2970
|
+
if (!Array.isArray(input)) return [];
|
|
2971
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
2972
|
+
for (const raw of input) {
|
|
2973
|
+
const value = String(raw || "").trim().toLowerCase();
|
|
2974
|
+
if (!value) continue;
|
|
2975
|
+
if (value === "approve") {
|
|
2976
|
+
deduped.add("approve");
|
|
2977
|
+
continue;
|
|
2978
|
+
}
|
|
2979
|
+
if (value === "changes_requested") {
|
|
2980
|
+
deduped.add("changes_requested");
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2983
|
+
if (value === "blocked") {
|
|
2984
|
+
deduped.add("blocked");
|
|
2985
|
+
continue;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
return [...deduped];
|
|
2989
|
+
}
|
|
2942
2990
|
function resolvePrePrReviewPolicy(workflow) {
|
|
2943
2991
|
const workflowPolicy = resolveWorkflowPolicy(workflow);
|
|
2944
2992
|
const configured = workflow?.prePrReview;
|
|
2945
2993
|
const configuredSkills = normalizeSkillList(configured?.skills);
|
|
2994
|
+
const configuredDecisionEnum = normalizeDecisionEnumList(
|
|
2995
|
+
configured?.decisionEnum
|
|
2996
|
+
);
|
|
2946
2997
|
const configuredEnabled = typeof configured?.enabled === "boolean" ? configured.enabled : workflowPolicy.requirePr;
|
|
2947
2998
|
return {
|
|
2948
2999
|
enabled: workflowPolicy.requirePr ? configuredEnabled : false,
|
|
2949
3000
|
skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
|
|
2950
3001
|
fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
|
|
2951
|
-
|
|
2952
|
-
|
|
3002
|
+
evidenceMode: configured?.evidenceMode === "any" ? "any" : "path_required",
|
|
3003
|
+
findings: configured?.findings === "optional" ? "optional" : "required",
|
|
3004
|
+
decisionEnum: configuredDecisionEnum.length > 0 ? configuredDecisionEnum : DEFAULT_PRE_PR_DECISION_ENUM
|
|
2953
3005
|
};
|
|
2954
3006
|
}
|
|
2955
3007
|
|
|
@@ -2977,17 +3029,20 @@ function isPrePrReviewSatisfied(feature, prePrReviewPolicy) {
|
|
|
2977
3029
|
if (!feature.docs.prePrEvidenceFieldExists || !feature.prePrReview.evidenceProvided) {
|
|
2978
3030
|
return false;
|
|
2979
3031
|
}
|
|
3032
|
+
if (prePrReviewPolicy.findings === "required" && (!feature.docs.prePrFindingsFieldExists || !feature.prePrReview.findingsProvided)) {
|
|
3033
|
+
return false;
|
|
3034
|
+
}
|
|
2980
3035
|
if (!feature.docs.prePrDecisionFieldExists || !feature.prePrReview.decisionProvided) {
|
|
2981
3036
|
return false;
|
|
2982
3037
|
}
|
|
3038
|
+
if (feature.prePrReview.decisionOutcome !== "approve") {
|
|
3039
|
+
return false;
|
|
3040
|
+
}
|
|
2983
3041
|
return true;
|
|
2984
3042
|
}
|
|
2985
3043
|
function isFeatureDone(feature, workflowPolicy, prePrReviewPolicy) {
|
|
2986
3044
|
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
3045
|
}
|
|
2988
|
-
function formatSkillList(skills) {
|
|
2989
|
-
return skills.join(", ");
|
|
2990
|
-
}
|
|
2991
3046
|
function getPrReviewRemoteBlockReasons(feature, lang) {
|
|
2992
3047
|
const remote = feature.pr.remote;
|
|
2993
3048
|
if (!remote || !remote.available) return [];
|
|
@@ -3024,6 +3079,14 @@ function getPrReviewRemoteBlockReasons(feature, lang) {
|
|
|
3024
3079
|
function normalizeCommitTopicText(value) {
|
|
3025
3080
|
return value.replace(/\s+/g, " ").trim();
|
|
3026
3081
|
}
|
|
3082
|
+
function toShellArg(value) {
|
|
3083
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`")}"`;
|
|
3084
|
+
}
|
|
3085
|
+
function buildSelfCliCommand(args) {
|
|
3086
|
+
const entry = process.argv[1] || "dist/index.js";
|
|
3087
|
+
const base = [process.execPath, entry, "--no-banner", ...args];
|
|
3088
|
+
return base.map((arg) => toShellArg(arg)).join(" ");
|
|
3089
|
+
}
|
|
3027
3090
|
function toShellSafeCommitTopic(value) {
|
|
3028
3091
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
|
|
3029
3092
|
}
|
|
@@ -3792,74 +3855,19 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3792
3855
|
}
|
|
3793
3856
|
];
|
|
3794
3857
|
}
|
|
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
|
-
];
|
|
3858
|
+
const commandArgs = ["pre-pr-review", f.folderName];
|
|
3859
|
+
if (f.type && f.type !== "single") {
|
|
3860
|
+
commandArgs.push("--component", f.type);
|
|
3853
3861
|
}
|
|
3854
3862
|
return [
|
|
3855
3863
|
{
|
|
3856
|
-
type: "
|
|
3864
|
+
type: "command",
|
|
3857
3865
|
category: "pre_pr_review",
|
|
3866
|
+
operationType: "local",
|
|
3858
3867
|
requiresUserCheck: true,
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
})
|
|
3868
|
+
scope: "docs",
|
|
3869
|
+
cwd: f.git.docsGitCwd,
|
|
3870
|
+
cmd: buildSelfCliCommand(commandArgs)
|
|
3863
3871
|
}
|
|
3864
3872
|
];
|
|
3865
3873
|
}
|
|
@@ -4556,6 +4564,52 @@ function hasStructuredReviewDecision(value) {
|
|
|
4556
4564
|
if (!trimmed) return false;
|
|
4557
4565
|
return /^(?:decision|결정)\s*[::]\s*\S.+$/i.test(trimmed);
|
|
4558
4566
|
}
|
|
4567
|
+
function parsePrePrDecisionOutcome(value) {
|
|
4568
|
+
if (!value) return void 0;
|
|
4569
|
+
const trimmed = value.trim();
|
|
4570
|
+
if (!trimmed) return void 0;
|
|
4571
|
+
const structured = trimmed.match(/^(?:decision|결정)\s*[::]\s*(.+)$/i);
|
|
4572
|
+
if (!structured) return void 0;
|
|
4573
|
+
const payload = structured[1].trim().toLowerCase();
|
|
4574
|
+
const normalized = payload.split(/[,\s-]+/)[0]?.replace(/[^a-z_]/g, "");
|
|
4575
|
+
if (!normalized) return void 0;
|
|
4576
|
+
if (normalized === "approve" || normalized === "approved") {
|
|
4577
|
+
return "approve";
|
|
4578
|
+
}
|
|
4579
|
+
if (normalized === "changes_requested" || normalized === "changes" || normalized === "change") {
|
|
4580
|
+
return "changes_requested";
|
|
4581
|
+
}
|
|
4582
|
+
if (normalized === "blocked" || normalized === "block") {
|
|
4583
|
+
return "blocked";
|
|
4584
|
+
}
|
|
4585
|
+
return void 0;
|
|
4586
|
+
}
|
|
4587
|
+
function resolveEvidencePathValue(value) {
|
|
4588
|
+
const trimmed = value.trim();
|
|
4589
|
+
const mdLink = trimmed.match(/\(([^)]+)\)/);
|
|
4590
|
+
if (mdLink && mdLink[1]) return mdLink[1].trim();
|
|
4591
|
+
return trimmed.split(/\s+/)[0] || "";
|
|
4592
|
+
}
|
|
4593
|
+
async function isPrePrEvidenceProvided(rawValue, policy, context) {
|
|
4594
|
+
if (isPlaceholderReviewEvidence(rawValue)) return false;
|
|
4595
|
+
if (policy.evidenceMode !== "path_required") return true;
|
|
4596
|
+
if (!rawValue) return false;
|
|
4597
|
+
const evidencePath = resolveEvidencePathValue(rawValue);
|
|
4598
|
+
if (!evidencePath) return false;
|
|
4599
|
+
if (/^https?:\/\//i.test(evidencePath)) return false;
|
|
4600
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4601
|
+
if (path22.isAbsolute(evidencePath)) {
|
|
4602
|
+
candidates.add(path22.resolve(evidencePath));
|
|
4603
|
+
} else {
|
|
4604
|
+
candidates.add(path22.resolve(context.featurePath, evidencePath));
|
|
4605
|
+
candidates.add(path22.resolve(context.docsDir, evidencePath));
|
|
4606
|
+
candidates.add(path22.resolve(path22.dirname(context.docsDir), evidencePath));
|
|
4607
|
+
}
|
|
4608
|
+
for (const candidate of candidates) {
|
|
4609
|
+
if (await fs16.pathExists(candidate)) return true;
|
|
4610
|
+
}
|
|
4611
|
+
return false;
|
|
4612
|
+
}
|
|
4559
4613
|
function parseIssueNumber(value) {
|
|
4560
4614
|
if (!value) return void 0;
|
|
4561
4615
|
const match = value.match(/#?(\d+)/);
|
|
@@ -4848,9 +4902,15 @@ function isPrePrReviewSatisfied2(feature, policy) {
|
|
|
4848
4902
|
if (!feature.docs.prePrEvidenceFieldExists || !feature.prePrReview.evidenceProvided) {
|
|
4849
4903
|
return false;
|
|
4850
4904
|
}
|
|
4905
|
+
if (policy.findings === "required" && (!feature.docs.prePrFindingsFieldExists || !feature.prePrReview.findingsProvided)) {
|
|
4906
|
+
return false;
|
|
4907
|
+
}
|
|
4851
4908
|
if (!feature.docs.prePrDecisionFieldExists || !feature.prePrReview.decisionProvided) {
|
|
4852
4909
|
return false;
|
|
4853
4910
|
}
|
|
4911
|
+
if (feature.prePrReview.decisionOutcome !== "approve") {
|
|
4912
|
+
return false;
|
|
4913
|
+
}
|
|
4854
4914
|
return true;
|
|
4855
4915
|
}
|
|
4856
4916
|
async function parseFeature(featurePath, type, context, options) {
|
|
@@ -4917,9 +4977,11 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4917
4977
|
let completionChecklist;
|
|
4918
4978
|
let prePrReviewStatus;
|
|
4919
4979
|
let prePrFindings;
|
|
4980
|
+
let prePrFindingsProvided = false;
|
|
4920
4981
|
let prePrEvidence;
|
|
4921
4982
|
let prePrEvidenceProvided = false;
|
|
4922
4983
|
let prePrDecision;
|
|
4984
|
+
let prePrDecisionOutcome;
|
|
4923
4985
|
let prePrDecisionProvided = false;
|
|
4924
4986
|
let prReviewFindings;
|
|
4925
4987
|
let prReviewEvidence;
|
|
@@ -4994,6 +5056,7 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4994
5056
|
"Pre-PR Findings"
|
|
4995
5057
|
]);
|
|
4996
5058
|
prePrFindings = parseReviewFindings(prePrFindingsValue);
|
|
5059
|
+
prePrFindingsProvided = !!prePrFindings;
|
|
4997
5060
|
const prePrEvidenceValue = extractFirstSpecValue(content, [
|
|
4998
5061
|
"PR \uC804 \uB9AC\uBDF0 Evidence",
|
|
4999
5062
|
"Pre-PR Evidence"
|
|
@@ -5003,7 +5066,11 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5003
5066
|
"Pre-PR Evidence"
|
|
5004
5067
|
]);
|
|
5005
5068
|
prePrEvidence = prePrEvidenceValue?.trim();
|
|
5006
|
-
prePrEvidenceProvided =
|
|
5069
|
+
prePrEvidenceProvided = await isPrePrEvidenceProvided(
|
|
5070
|
+
prePrEvidenceValue,
|
|
5071
|
+
prePrReviewPolicy,
|
|
5072
|
+
{ featurePath, docsDir: context.docsDir }
|
|
5073
|
+
);
|
|
5007
5074
|
const prePrDecisionValue = extractFirstSpecValue(content, [
|
|
5008
5075
|
"PR \uC804 \uB9AC\uBDF0 Decision",
|
|
5009
5076
|
"Pre-PR Decision"
|
|
@@ -5013,7 +5080,8 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5013
5080
|
"Pre-PR Decision"
|
|
5014
5081
|
]);
|
|
5015
5082
|
prePrDecision = prePrDecisionValue?.trim();
|
|
5016
|
-
|
|
5083
|
+
prePrDecisionOutcome = parsePrePrDecisionOutcome(prePrDecisionValue);
|
|
5084
|
+
prePrDecisionProvided = !isPlaceholderReviewEvidence(prePrDecisionValue) && hasStructuredReviewDecision(prePrDecisionValue) && !!prePrDecisionOutcome && prePrReviewPolicy.decisionEnum.includes(prePrDecisionOutcome);
|
|
5017
5085
|
const prReviewFindingsValue = extractFirstSpecValue(content, [
|
|
5018
5086
|
"PR \uB9AC\uBDF0 Findings",
|
|
5019
5087
|
"PR Review Findings"
|
|
@@ -5188,6 +5256,9 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5188
5256
|
if (tasksExists && prePrReviewPolicy.enabled && !prePrReviewFieldExists) {
|
|
5189
5257
|
warnings.push(tr(lang, "warnings", "legacyTasksPrePrReviewField"));
|
|
5190
5258
|
}
|
|
5259
|
+
if (tasksExists && prePrReviewPolicy.enabled && prePrReviewPolicy.findings === "required" && !prePrFindingsFieldExists) {
|
|
5260
|
+
warnings.push(tr(lang, "warnings", "legacyTasksPrePrFindingsField"));
|
|
5261
|
+
}
|
|
5191
5262
|
if (tasksExists && prePrReviewPolicy.enabled && !prePrEvidenceFieldExists) {
|
|
5192
5263
|
warnings.push(tr(lang, "warnings", "legacyTasksPrePrEvidenceField"));
|
|
5193
5264
|
}
|
|
@@ -5232,12 +5303,15 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5232
5303
|
{
|
|
5233
5304
|
docs: {
|
|
5234
5305
|
prePrReviewFieldExists,
|
|
5306
|
+
prePrFindingsFieldExists,
|
|
5235
5307
|
prePrEvidenceFieldExists,
|
|
5236
5308
|
prePrDecisionFieldExists
|
|
5237
5309
|
},
|
|
5238
5310
|
prePrReview: {
|
|
5239
5311
|
status: prePrReviewStatus,
|
|
5312
|
+
findingsProvided: prePrFindingsProvided,
|
|
5240
5313
|
evidenceProvided: prePrEvidenceProvided,
|
|
5314
|
+
decisionOutcome: prePrDecisionOutcome,
|
|
5241
5315
|
decisionProvided: prePrDecisionProvided
|
|
5242
5316
|
}
|
|
5243
5317
|
},
|
|
@@ -5278,10 +5352,18 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5278
5352
|
warnings.push(tr(lang, "warnings", "workflowPrePrReviewMissing"));
|
|
5279
5353
|
} else if (prePrReviewStatus !== "Done") {
|
|
5280
5354
|
warnings.push(tr(lang, "warnings", "workflowPrePrReviewNotDone"));
|
|
5355
|
+
} else if (prePrReviewPolicy.findings === "required" && (!prePrFindingsFieldExists || !prePrFindingsProvided)) {
|
|
5356
|
+
warnings.push(tr(lang, "warnings", "workflowPrePrFindingsMissing"));
|
|
5281
5357
|
} else if (!prePrEvidenceFieldExists || !prePrEvidenceProvided) {
|
|
5282
5358
|
warnings.push(tr(lang, "warnings", "workflowPrePrEvidenceMissing"));
|
|
5283
5359
|
} else if (!prePrDecisionFieldExists || !prePrDecisionProvided) {
|
|
5284
5360
|
warnings.push(tr(lang, "warnings", "workflowPrePrDecisionMissing"));
|
|
5361
|
+
} else if (prePrDecisionOutcome !== "approve") {
|
|
5362
|
+
warnings.push(
|
|
5363
|
+
tr(lang, "warnings", "workflowPrePrDecisionNotApproved", {
|
|
5364
|
+
outcome: prePrDecisionOutcome || "-"
|
|
5365
|
+
})
|
|
5366
|
+
);
|
|
5285
5367
|
}
|
|
5286
5368
|
}
|
|
5287
5369
|
}
|
|
@@ -5307,9 +5389,11 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
5307
5389
|
prePrReview: {
|
|
5308
5390
|
status: prePrReviewStatus,
|
|
5309
5391
|
findings: prePrFindings,
|
|
5392
|
+
findingsProvided: prePrFindingsProvided,
|
|
5310
5393
|
evidence: prePrEvidence,
|
|
5311
5394
|
evidenceProvided: prePrEvidenceProvided,
|
|
5312
5395
|
decision: prePrDecision,
|
|
5396
|
+
decisionOutcome: prePrDecisionOutcome,
|
|
5313
5397
|
decisionProvided: prePrDecisionProvided
|
|
5314
5398
|
},
|
|
5315
5399
|
prReview: {
|
|
@@ -5867,6 +5951,18 @@ function normalizeSkillList2(raw) {
|
|
|
5867
5951
|
}
|
|
5868
5952
|
return [...deduped];
|
|
5869
5953
|
}
|
|
5954
|
+
function normalizeDecisionEnumList2(raw) {
|
|
5955
|
+
if (!Array.isArray(raw)) return [];
|
|
5956
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
5957
|
+
for (const item of raw) {
|
|
5958
|
+
const value = String(item || "").trim().toLowerCase();
|
|
5959
|
+
if (!value) continue;
|
|
5960
|
+
if (value === "approve" || value === "changes_requested" || value === "blocked") {
|
|
5961
|
+
deduped.add(value);
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
return [...deduped];
|
|
5965
|
+
}
|
|
5870
5966
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
5871
5967
|
const configPath = path22.join(docsDir, ".lee-spec-kit.json");
|
|
5872
5968
|
if (!await fs16.pathExists(configPath)) {
|
|
@@ -5915,8 +6011,39 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
5915
6011
|
}
|
|
5916
6012
|
}
|
|
5917
6013
|
setIfMissing(prePrReview, "fallback", "builtin-checklist", "workflow.prePrReview.fallback");
|
|
5918
|
-
setIfMissing(
|
|
5919
|
-
|
|
6014
|
+
setIfMissing(
|
|
6015
|
+
prePrReview,
|
|
6016
|
+
"evidenceMode",
|
|
6017
|
+
"path_required",
|
|
6018
|
+
"workflow.prePrReview.evidenceMode"
|
|
6019
|
+
);
|
|
6020
|
+
if (prePrReview.evidenceMode !== void 0 && prePrReview.evidenceMode !== "path_required" && prePrReview.evidenceMode !== "any") {
|
|
6021
|
+
prePrReview.evidenceMode = "path_required";
|
|
6022
|
+
changedPaths.push("workflow.prePrReview.evidenceMode");
|
|
6023
|
+
}
|
|
6024
|
+
setIfMissing(
|
|
6025
|
+
prePrReview,
|
|
6026
|
+
"findings",
|
|
6027
|
+
"required",
|
|
6028
|
+
"workflow.prePrReview.findings"
|
|
6029
|
+
);
|
|
6030
|
+
if (prePrReview.findings !== void 0 && prePrReview.findings !== "required" && prePrReview.findings !== "optional") {
|
|
6031
|
+
prePrReview.findings = "required";
|
|
6032
|
+
changedPaths.push("workflow.prePrReview.findings");
|
|
6033
|
+
}
|
|
6034
|
+
if (prePrReview.decisionEnum === void 0) {
|
|
6035
|
+
prePrReview.decisionEnum = ["approve", "changes_requested", "blocked"];
|
|
6036
|
+
changedPaths.push("workflow.prePrReview.decisionEnum");
|
|
6037
|
+
} else {
|
|
6038
|
+
const normalizedDecisionEnum = normalizeDecisionEnumList2(prePrReview.decisionEnum);
|
|
6039
|
+
if (normalizedDecisionEnum.length === 0) {
|
|
6040
|
+
prePrReview.decisionEnum = ["approve", "changes_requested", "blocked"];
|
|
6041
|
+
changedPaths.push("workflow.prePrReview.decisionEnum");
|
|
6042
|
+
} else if (JSON.stringify(normalizedDecisionEnum) !== JSON.stringify(prePrReview.decisionEnum)) {
|
|
6043
|
+
prePrReview.decisionEnum = normalizedDecisionEnum;
|
|
6044
|
+
changedPaths.push("workflow.prePrReview.decisionEnum");
|
|
6045
|
+
}
|
|
6046
|
+
}
|
|
5920
6047
|
if (!isPlainObject(raw.pr)) {
|
|
5921
6048
|
raw.pr = {};
|
|
5922
6049
|
changedPaths.push("pr");
|
|
@@ -6312,21 +6439,27 @@ function buildActionDetail(action, lang) {
|
|
|
6312
6439
|
const branchMatch = command.match(/\bfeat\/([A-Za-z0-9._-]+)/);
|
|
6313
6440
|
const worktree = worktreeMatch ? `.worktrees/${worktreeMatch[1]}` : null;
|
|
6314
6441
|
const branch = branchMatch ? `feat/${branchMatch[1]}` : null;
|
|
6442
|
+
if (action.type !== "command") {
|
|
6443
|
+
return tr(lang, "cli", "context.commandDetail.branchCreateGeneric", {
|
|
6444
|
+
scope: "project"
|
|
6445
|
+
});
|
|
6446
|
+
}
|
|
6447
|
+
const scope = action.scope;
|
|
6315
6448
|
if (worktree && branch) {
|
|
6316
6449
|
return tr(lang, "cli", "context.commandDetail.branchCreateWithWorktree", {
|
|
6317
|
-
scope
|
|
6450
|
+
scope,
|
|
6318
6451
|
worktree,
|
|
6319
6452
|
branch
|
|
6320
6453
|
});
|
|
6321
6454
|
}
|
|
6322
6455
|
if (branch) {
|
|
6323
6456
|
return tr(lang, "cli", "context.commandDetail.branchCreateWithBranch", {
|
|
6324
|
-
scope
|
|
6457
|
+
scope,
|
|
6325
6458
|
branch
|
|
6326
6459
|
});
|
|
6327
6460
|
}
|
|
6328
6461
|
return tr(lang, "cli", "context.commandDetail.branchCreateGeneric", {
|
|
6329
|
-
scope
|
|
6462
|
+
scope
|
|
6330
6463
|
});
|
|
6331
6464
|
};
|
|
6332
6465
|
const extractCommitMessage = (command) => {
|
|
@@ -6989,6 +7122,72 @@ function buildSuggestionFinalPrompt(lang, suggestionOptions) {
|
|
|
6989
7122
|
example
|
|
6990
7123
|
});
|
|
6991
7124
|
}
|
|
7125
|
+
function normalizeCategoryToken(value) {
|
|
7126
|
+
if (typeof value !== "string") return null;
|
|
7127
|
+
const normalized = value.trim().toLowerCase();
|
|
7128
|
+
if (!normalized) return null;
|
|
7129
|
+
return normalized;
|
|
7130
|
+
}
|
|
7131
|
+
function resolveAutoRunCategories(approval) {
|
|
7132
|
+
const known = new Set(ACTION_CATEGORIES);
|
|
7133
|
+
const unique2 = /* @__PURE__ */ new Set();
|
|
7134
|
+
const unknown = /* @__PURE__ */ new Set();
|
|
7135
|
+
for (const raw of approval?.requireCheckCategories ?? approval?.requireOkCategories ?? []) {
|
|
7136
|
+
const normalized = normalizeCategoryToken(raw);
|
|
7137
|
+
if (!normalized) continue;
|
|
7138
|
+
if (known.has(normalized)) {
|
|
7139
|
+
unique2.add(normalized);
|
|
7140
|
+
} else {
|
|
7141
|
+
unknown.add(normalized);
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7144
|
+
return {
|
|
7145
|
+
untilCategories: Array.from(unique2),
|
|
7146
|
+
unknownCategories: Array.from(unknown)
|
|
7147
|
+
};
|
|
7148
|
+
}
|
|
7149
|
+
function buildAutoRunCommand(state, featureName, selectedComponent, untilCategories) {
|
|
7150
|
+
if (untilCategories.length === 0) return "";
|
|
7151
|
+
const featureRef = resolveFeatureRefForApproval(state, featureName);
|
|
7152
|
+
const componentArg = selectedComponent ? ` --component ${selectedComponent}` : "";
|
|
7153
|
+
return `npx lee-spec-kit flow ${featureRef}${componentArg} --auto-until-category ${untilCategories.join(",")}`;
|
|
7154
|
+
}
|
|
7155
|
+
function resolveAutoRunPlan(lang, state, featureName, selectedComponent, approval, approvalRequired) {
|
|
7156
|
+
const base = (reasonCode, untilCategories2 = [], unknownCategories2 = []) => ({
|
|
7157
|
+
available: false,
|
|
7158
|
+
reasonCode,
|
|
7159
|
+
summary: tr(lang, "cli", "context.autoRunUnavailable"),
|
|
7160
|
+
command: "",
|
|
7161
|
+
untilCategories: untilCategories2,
|
|
7162
|
+
unknownCategories: unknownCategories2
|
|
7163
|
+
});
|
|
7164
|
+
if (state.status !== "single_matched") return base("NOT_SINGLE_MATCHED");
|
|
7165
|
+
if (state.actionOptions.length === 0) return base("NO_ACTION_OPTIONS");
|
|
7166
|
+
if (approvalRequired) return base("APPROVAL_REQUIRED");
|
|
7167
|
+
const mode = approval?.mode ?? "builtin";
|
|
7168
|
+
if (mode !== "category") return base("APPROVAL_MODE_NOT_CATEGORY");
|
|
7169
|
+
const defaultPolicy = approval?.default ?? "keep";
|
|
7170
|
+
if (defaultPolicy !== "skip") return base("DEFAULT_NOT_SKIP");
|
|
7171
|
+
const { untilCategories, unknownCategories } = resolveAutoRunCategories(approval);
|
|
7172
|
+
if (untilCategories.length === 0) {
|
|
7173
|
+
return base("NO_REQUIRE_CHECK_CATEGORIES", [], unknownCategories);
|
|
7174
|
+
}
|
|
7175
|
+
return {
|
|
7176
|
+
available: true,
|
|
7177
|
+
reasonCode: "AVAILABLE",
|
|
7178
|
+
summary: tr(lang, "cli", "context.autoRunSummary", {
|
|
7179
|
+
categories: untilCategories.join(", ")
|
|
7180
|
+
}),
|
|
7181
|
+
command: buildAutoRunCommand(
|
|
7182
|
+
state,
|
|
7183
|
+
featureName,
|
|
7184
|
+
selectedComponent,
|
|
7185
|
+
untilCategories
|
|
7186
|
+
),
|
|
7187
|
+
untilCategories,
|
|
7188
|
+
unknownCategories
|
|
7189
|
+
};
|
|
7190
|
+
}
|
|
6992
7191
|
function toSuggestionLabel(index) {
|
|
6993
7192
|
let n = index + 1;
|
|
6994
7193
|
let label = "";
|
|
@@ -7182,12 +7381,18 @@ function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
|
|
|
7182
7381
|
if (prePrReviewPolicy.enabled && f.prePrReview.status !== "Done") {
|
|
7183
7382
|
return tr(lang, "cli", "context.list.completePrePrReview");
|
|
7184
7383
|
}
|
|
7384
|
+
if (prePrReviewPolicy.enabled && prePrReviewPolicy.findings === "required" && (!f.docs.prePrFindingsFieldExists || !f.prePrReview.findingsProvided)) {
|
|
7385
|
+
return tr(lang, "cli", "context.list.addPrePrFindings");
|
|
7386
|
+
}
|
|
7185
7387
|
if (prePrReviewPolicy.enabled && (!f.docs.prePrEvidenceFieldExists || !f.prePrReview.evidenceProvided)) {
|
|
7186
7388
|
return tr(lang, "cli", "context.list.addPrePrEvidence");
|
|
7187
7389
|
}
|
|
7188
7390
|
if (prePrReviewPolicy.enabled && (!f.docs.prePrDecisionFieldExists || !f.prePrReview.decisionProvided)) {
|
|
7189
7391
|
return tr(lang, "cli", "context.list.addPrePrDecision");
|
|
7190
7392
|
}
|
|
7393
|
+
if (prePrReviewPolicy.enabled && f.prePrReview.decisionOutcome !== "approve") {
|
|
7394
|
+
return tr(lang, "cli", "context.list.resolvePrePrDecision");
|
|
7395
|
+
}
|
|
7191
7396
|
if (workflowPolicy.requirePr && !f.pr.link) {
|
|
7192
7397
|
return tr(lang, "cli", "context.list.recordPrLink");
|
|
7193
7398
|
}
|
|
@@ -7244,7 +7449,9 @@ function toCompactFeature(feature) {
|
|
|
7244
7449
|
prePrReview: {
|
|
7245
7450
|
status: feature.prePrReview.status,
|
|
7246
7451
|
findings: feature.prePrReview.findings,
|
|
7452
|
+
findingsProvided: feature.prePrReview.findingsProvided,
|
|
7247
7453
|
evidenceProvided: feature.prePrReview.evidenceProvided,
|
|
7454
|
+
decisionOutcome: feature.prePrReview.decisionOutcome,
|
|
7248
7455
|
decisionProvided: feature.prePrReview.decisionProvided
|
|
7249
7456
|
},
|
|
7250
7457
|
prReview: {
|
|
@@ -7385,6 +7592,26 @@ async function runContext(featureName, options) {
|
|
|
7385
7592
|
selectedComponent
|
|
7386
7593
|
);
|
|
7387
7594
|
const suggestionFinalPrompt = buildSuggestionFinalPrompt(lang, suggestionOptions);
|
|
7595
|
+
const checkRequiredLabels = state.actionOptions.filter((option) => !!option.action.requiresUserCheck).map((option) => option.label);
|
|
7596
|
+
const checkRequiredCategories = [
|
|
7597
|
+
...new Set(
|
|
7598
|
+
state.actionOptions.filter((option) => !!option.action.requiresUserCheck).map((option) => option.action.category || "uncategorized")
|
|
7599
|
+
)
|
|
7600
|
+
];
|
|
7601
|
+
const approvalRequired = checkRequiredLabels.length > 0;
|
|
7602
|
+
const finalApprovalPrompt = approvalRequired ? buildFinalApprovalPrompt(lang, state.actionOptions) : "";
|
|
7603
|
+
const approvalUserFacingLines = approvalRequired ? [
|
|
7604
|
+
...state.actionOptions.map((o) => o.approvalPrompt),
|
|
7605
|
+
finalApprovalPrompt
|
|
7606
|
+
].filter((line) => line.length > 0) : [];
|
|
7607
|
+
const autoRunPlan = resolveAutoRunPlan(
|
|
7608
|
+
lang,
|
|
7609
|
+
state,
|
|
7610
|
+
featureName,
|
|
7611
|
+
selectedComponent,
|
|
7612
|
+
config.approval,
|
|
7613
|
+
approvalRequired
|
|
7614
|
+
);
|
|
7388
7615
|
if (options.approve || options.execute) {
|
|
7389
7616
|
await runApprovedOption(
|
|
7390
7617
|
state,
|
|
@@ -7399,7 +7626,6 @@ async function runContext(featureName, options) {
|
|
|
7399
7626
|
const jsonMode = !!options.json || !!options.jsonCompact;
|
|
7400
7627
|
if (jsonMode) {
|
|
7401
7628
|
const primaryAction = state.actionOptions[0] ?? null;
|
|
7402
|
-
const finalApprovalPrompt = buildFinalApprovalPrompt(lang, state.actionOptions);
|
|
7403
7629
|
const approveCommand = buildApprovalCommand(
|
|
7404
7630
|
state,
|
|
7405
7631
|
featureName,
|
|
@@ -7452,19 +7678,28 @@ async function runContext(featureName, options) {
|
|
|
7452
7678
|
activeCategories,
|
|
7453
7679
|
knownCategories: ACTION_CATEGORIES,
|
|
7454
7680
|
uncategorizedLabels,
|
|
7681
|
+
checkRequiredLabels,
|
|
7682
|
+
checkRequiredCategories,
|
|
7683
|
+
approvalRequired,
|
|
7455
7684
|
categoryPolicyGuidance: 'For approval.mode="category", match against `actionOptions[].category`.',
|
|
7456
|
-
oneApprovalPerAction:
|
|
7685
|
+
oneApprovalPerAction: approvalRequired,
|
|
7457
7686
|
requireFreshContext: true,
|
|
7458
7687
|
contextVersion: state.contextVersion,
|
|
7459
7688
|
config: config.approval ?? { mode: "builtin" }
|
|
7460
7689
|
},
|
|
7690
|
+
autoRun: {
|
|
7691
|
+
available: autoRunPlan.available,
|
|
7692
|
+
reasonCode: autoRunPlan.reasonCode,
|
|
7693
|
+
summary: autoRunPlan.summary,
|
|
7694
|
+
command: autoRunPlan.command,
|
|
7695
|
+
untilCategories: autoRunPlan.untilCategories,
|
|
7696
|
+
unknownCategories: autoRunPlan.unknownCategories
|
|
7697
|
+
},
|
|
7461
7698
|
approvalRequest: {
|
|
7699
|
+
required: approvalRequired,
|
|
7462
7700
|
finalPrompt: finalApprovalPrompt,
|
|
7463
|
-
userFacingLines:
|
|
7464
|
-
|
|
7465
|
-
finalApprovalPrompt
|
|
7466
|
-
].filter((line) => line.length > 0),
|
|
7467
|
-
labels: state.actionOptions.map((o) => o.label),
|
|
7701
|
+
userFacingLines: approvalUserFacingLines,
|
|
7702
|
+
labels: approvalRequired ? state.actionOptions.map((o) => o.label) : [],
|
|
7468
7703
|
approveCommand,
|
|
7469
7704
|
executeCommand,
|
|
7470
7705
|
executeRequiresTicket: !!state.actionOptions[0]?.action?.requiresUserCheck
|
|
@@ -7523,27 +7758,37 @@ async function runContext(featureName, options) {
|
|
|
7523
7758
|
activeCategories,
|
|
7524
7759
|
knownCategories: ACTION_CATEGORIES,
|
|
7525
7760
|
uncategorizedLabels,
|
|
7761
|
+
checkRequiredLabels,
|
|
7762
|
+
checkRequiredCategories,
|
|
7763
|
+
approvalRequired,
|
|
7526
7764
|
categoryPolicyGuidance: 'For approval.mode="category", match against `actionOptions[].category`.',
|
|
7527
|
-
requireExplanationBeforeApproval:
|
|
7528
|
-
requiredExplanationFields: [
|
|
7765
|
+
requireExplanationBeforeApproval: approvalRequired,
|
|
7766
|
+
requiredExplanationFields: approvalRequired ? [
|
|
7529
7767
|
"actionOptions[].label",
|
|
7530
7768
|
"actionOptions[].detail",
|
|
7531
7769
|
"actionOptions[].approvalPrompt"
|
|
7532
|
-
],
|
|
7770
|
+
] : [],
|
|
7533
7771
|
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:
|
|
7772
|
+
oneApprovalPerAction: approvalRequired,
|
|
7535
7773
|
requireFreshContext: true,
|
|
7536
7774
|
contextVersion: state.contextVersion,
|
|
7537
7775
|
config: config.approval ?? { mode: "builtin" }
|
|
7538
7776
|
},
|
|
7777
|
+
autoRun: {
|
|
7778
|
+
available: autoRunPlan.available,
|
|
7779
|
+
reasonCode: autoRunPlan.reasonCode,
|
|
7780
|
+
summary: autoRunPlan.summary,
|
|
7781
|
+
command: autoRunPlan.command,
|
|
7782
|
+
untilCategories: autoRunPlan.untilCategories,
|
|
7783
|
+
unknownCategories: autoRunPlan.unknownCategories,
|
|
7784
|
+
guidance: "Use auto-run only when `autoRun.available=true`. Stop and request approval when `approvalRequest.required=true` or when auto mode reaches configured gate categories."
|
|
7785
|
+
},
|
|
7539
7786
|
approvalRequest: {
|
|
7540
7787
|
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`.",
|
|
7788
|
+
required: approvalRequired,
|
|
7541
7789
|
finalPrompt: finalApprovalPrompt,
|
|
7542
|
-
userFacingLines:
|
|
7543
|
-
|
|
7544
|
-
finalApprovalPrompt
|
|
7545
|
-
].filter((line) => line.length > 0),
|
|
7546
|
-
labels: state.actionOptions.map((o) => o.label),
|
|
7790
|
+
userFacingLines: approvalUserFacingLines,
|
|
7791
|
+
labels: approvalRequired ? state.actionOptions.map((o) => o.label) : [],
|
|
7547
7792
|
approveCommand,
|
|
7548
7793
|
executeCommand,
|
|
7549
7794
|
executeRequiresTicket: !!state.actionOptions[0]?.action?.requiresUserCheck,
|
|
@@ -7751,7 +7996,7 @@ async function runContext(featureName, options) {
|
|
|
7751
7996
|
const f = state.targetFeatures[0];
|
|
7752
7997
|
const stepName = stepsMap[f.currentStep] || "Unknown";
|
|
7753
7998
|
const checkTag = (requiresUserCheck) => requiresUserCheck ? chalk6.yellow(tr(lang, "cli", "context.checkRequired")) : "";
|
|
7754
|
-
const hasCheckAction =
|
|
7999
|
+
const hasCheckAction = approvalRequired;
|
|
7755
8000
|
console.log(
|
|
7756
8001
|
`\u{1F539} Feature: ${chalk6.bold(f.folderName)} ${config.projectType === "multi" ? chalk6.cyan(`(${f.type})`) : ""}`
|
|
7757
8002
|
);
|
|
@@ -7823,8 +8068,17 @@ async function runContext(featureName, options) {
|
|
|
7823
8068
|
);
|
|
7824
8069
|
}
|
|
7825
8070
|
}
|
|
7826
|
-
if (
|
|
7827
|
-
|
|
8071
|
+
if (autoRunPlan.available) {
|
|
8072
|
+
console.log(chalk6.gray(` \u21B3 ${autoRunPlan.summary}`));
|
|
8073
|
+
console.log(
|
|
8074
|
+
chalk6.gray(
|
|
8075
|
+
` \u21B3 ${tr(lang, "cli", "context.autoRunCommandHint", {
|
|
8076
|
+
command: autoRunPlan.command
|
|
8077
|
+
})}`
|
|
8078
|
+
)
|
|
8079
|
+
);
|
|
8080
|
+
}
|
|
8081
|
+
if (actionOptions.length > 0 && hasCheckAction) {
|
|
7828
8082
|
const approveCommand = buildApprovalCommand(
|
|
7829
8083
|
state,
|
|
7830
8084
|
featureName,
|
|
@@ -7950,7 +8204,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7950
8204
|
contextVersion: freshState.contextVersion,
|
|
7951
8205
|
executable: selectedAction.type === "command",
|
|
7952
8206
|
executeRequiresTicket,
|
|
7953
|
-
oneApprovalPerAction:
|
|
8207
|
+
oneApprovalPerAction: executeRequiresTicket,
|
|
7954
8208
|
approvalTicket: ticket ? {
|
|
7955
8209
|
token: ticket.token,
|
|
7956
8210
|
sessionId: ticket.sessionId,
|
|
@@ -9014,6 +9268,9 @@ function toAutoReasonCode(status) {
|
|
|
9014
9268
|
return "AUTO_EXECUTION_FAILED";
|
|
9015
9269
|
}
|
|
9016
9270
|
}
|
|
9271
|
+
function isAutoRunFailureStatus(status) {
|
|
9272
|
+
return status === "manual_required" || status === "selection_required" || status === "no_progress" || status === "request_label_missing" || status === "request_failed" || status === "execution_failed";
|
|
9273
|
+
}
|
|
9017
9274
|
async function runAutoUntilCategory(config, featureName, selectionOptions, untilCategories, requestText, metadata) {
|
|
9018
9275
|
const contextArgs = ["context", ...buildSelectionArgs(featureName, selectionOptions)];
|
|
9019
9276
|
const gateSet = new Set(untilCategories);
|
|
@@ -9422,9 +9679,10 @@ async function runFlow(featureName, options) {
|
|
|
9422
9679
|
}
|
|
9423
9680
|
}
|
|
9424
9681
|
if (options.json) {
|
|
9682
|
+
const autoRunFailed = !!(autoRun && isAutoRunFailureStatus(autoRun.status));
|
|
9425
9683
|
const payload = {
|
|
9426
|
-
status: "ok",
|
|
9427
|
-
reasonCode: "FLOW_SUMMARY",
|
|
9684
|
+
status: autoRunFailed ? "error" : "ok",
|
|
9685
|
+
reasonCode: autoRunFailed ? autoRun?.reasonCode || "AUTO_EXECUTION_FAILED" : "FLOW_SUMMARY",
|
|
9428
9686
|
context: {
|
|
9429
9687
|
before: {
|
|
9430
9688
|
status: before.status,
|
|
@@ -9453,6 +9711,9 @@ async function runFlow(featureName, options) {
|
|
|
9453
9711
|
suggestion: after.matchedFeature ? `npx lee-spec-kit context ${after.matchedFeature.folderName}${componentHint}` : `npx lee-spec-kit context${componentHint}`
|
|
9454
9712
|
};
|
|
9455
9713
|
console.log(JSON.stringify(payload, null, 2));
|
|
9714
|
+
if (autoRunFailed) {
|
|
9715
|
+
process.exitCode = 1;
|
|
9716
|
+
}
|
|
9456
9717
|
return;
|
|
9457
9718
|
}
|
|
9458
9719
|
console.log();
|
|
@@ -9498,6 +9759,9 @@ async function runFlow(featureName, options) {
|
|
|
9498
9759
|
console.log(chalk6.gray("Auto gate reached. Reply with one of the labels shown above (example: A OK)."));
|
|
9499
9760
|
console.log();
|
|
9500
9761
|
}
|
|
9762
|
+
if (autoRun && isAutoRunFailureStatus(autoRun.status)) {
|
|
9763
|
+
process.exitCode = 1;
|
|
9764
|
+
}
|
|
9501
9765
|
if (after.matchedFeature) {
|
|
9502
9766
|
console.log(
|
|
9503
9767
|
chalk6.blue(
|
|
@@ -11986,6 +12250,263 @@ async function runOnboard(options) {
|
|
|
11986
12250
|
process.exitCode = 1;
|
|
11987
12251
|
}
|
|
11988
12252
|
}
|
|
12253
|
+
function escapeRegExp4(value) {
|
|
12254
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12255
|
+
}
|
|
12256
|
+
function normalizeDecision(raw) {
|
|
12257
|
+
if (!raw) return null;
|
|
12258
|
+
const value = raw.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
12259
|
+
if (value === "approve" || value === "approved") return "approve";
|
|
12260
|
+
if (value === "changes_requested" || value === "change_requested" || value === "changes") {
|
|
12261
|
+
return "changes_requested";
|
|
12262
|
+
}
|
|
12263
|
+
if (value === "blocked" || value === "block") return "blocked";
|
|
12264
|
+
return null;
|
|
12265
|
+
}
|
|
12266
|
+
function parseNonNegativeInt(raw, label) {
|
|
12267
|
+
if (raw === void 0) return null;
|
|
12268
|
+
const value = Number(raw);
|
|
12269
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
12270
|
+
throw createCliError(
|
|
12271
|
+
"INVALID_ARGUMENT",
|
|
12272
|
+
`\`${label}\` must be a non-negative integer.`
|
|
12273
|
+
);
|
|
12274
|
+
}
|
|
12275
|
+
return value;
|
|
12276
|
+
}
|
|
12277
|
+
function findSpecLineIndex(lines, keys) {
|
|
12278
|
+
const escaped = keys.map((key) => escapeRegExp4(key));
|
|
12279
|
+
const re = new RegExp(`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`);
|
|
12280
|
+
return lines.findIndex((line) => re.test(line));
|
|
12281
|
+
}
|
|
12282
|
+
function replaceSpecLine(line, keys, preferredKey, value) {
|
|
12283
|
+
const escaped = keys.map((key) => escapeRegExp4(key));
|
|
12284
|
+
const re = new RegExp(
|
|
12285
|
+
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
12286
|
+
);
|
|
12287
|
+
if (!re.test(line)) return line;
|
|
12288
|
+
return line.replace(re, `$1${preferredKey}$2${value}`);
|
|
12289
|
+
}
|
|
12290
|
+
function computeInsertIndex(lines, anchorKeys) {
|
|
12291
|
+
const anchorIndex = findSpecLineIndex(lines, anchorKeys);
|
|
12292
|
+
if (anchorIndex !== -1) {
|
|
12293
|
+
let cursor = anchorIndex + 1;
|
|
12294
|
+
while (cursor < lines.length && /^\s{2,}-\s+/.test(lines[cursor])) {
|
|
12295
|
+
cursor += 1;
|
|
12296
|
+
}
|
|
12297
|
+
return cursor;
|
|
12298
|
+
}
|
|
12299
|
+
const sectionIndex = lines.findIndex(
|
|
12300
|
+
(line) => /^\s*##\s+(Task List|태스크 목록)\s*$/.test(line)
|
|
12301
|
+
);
|
|
12302
|
+
if (sectionIndex !== -1) return sectionIndex;
|
|
12303
|
+
return lines.length;
|
|
12304
|
+
}
|
|
12305
|
+
function upsertSpecLine(content, keys, preferredKey, value, anchorKeys) {
|
|
12306
|
+
const lines = content.split("\n");
|
|
12307
|
+
const index = findSpecLineIndex(lines, keys);
|
|
12308
|
+
if (index !== -1) {
|
|
12309
|
+
lines[index] = replaceSpecLine(lines[index], keys, preferredKey, value);
|
|
12310
|
+
return lines.join("\n");
|
|
12311
|
+
}
|
|
12312
|
+
const insertAt = computeInsertIndex(lines, anchorKeys);
|
|
12313
|
+
lines.splice(insertAt, 0, `- **${preferredKey}**: ${value}`);
|
|
12314
|
+
return lines.join("\n");
|
|
12315
|
+
}
|
|
12316
|
+
function normalizePathForDoc(value) {
|
|
12317
|
+
return value.replace(/\\/g, "/");
|
|
12318
|
+
}
|
|
12319
|
+
function getPreferredKeys(lang) {
|
|
12320
|
+
if (lang === "ko") {
|
|
12321
|
+
return {
|
|
12322
|
+
review: "PR \uC804 \uB9AC\uBDF0",
|
|
12323
|
+
findings: "PR \uC804 \uB9AC\uBDF0 Findings",
|
|
12324
|
+
evidence: "PR \uC804 \uB9AC\uBDF0 Evidence",
|
|
12325
|
+
decision: "PR \uC804 \uB9AC\uBDF0 Decision",
|
|
12326
|
+
prStatus: "PR \uC0C1\uD0DC"
|
|
12327
|
+
};
|
|
12328
|
+
}
|
|
12329
|
+
return {
|
|
12330
|
+
review: "Pre-PR Review",
|
|
12331
|
+
findings: "Pre-PR Findings",
|
|
12332
|
+
evidence: "Pre-PR Evidence",
|
|
12333
|
+
decision: "Pre-PR Decision",
|
|
12334
|
+
prStatus: "PR Status"
|
|
12335
|
+
};
|
|
12336
|
+
}
|
|
12337
|
+
function buildReportContent(input) {
|
|
12338
|
+
const skills = input.skills.length > 0 ? input.skills.join(", ") : "code-review-excellence";
|
|
12339
|
+
return `# Pre-PR Review Report: ${input.folderName}
|
|
12340
|
+
|
|
12341
|
+
- Date: ${input.date}
|
|
12342
|
+
- Baseline: ${input.fallback}
|
|
12343
|
+
- Skills: ${skills}
|
|
12344
|
+
- Findings: major=${input.major}, minor=${input.minor}
|
|
12345
|
+
- Decision: ${input.decision}
|
|
12346
|
+
- Note: ${input.note}
|
|
12347
|
+
|
|
12348
|
+
## Baseline Checklist
|
|
12349
|
+
|
|
12350
|
+
- [x] Checked alignment with spec/plan/tasks.
|
|
12351
|
+
- [x] Reviewed risk areas (regression, security, side effects, release readiness).
|
|
12352
|
+
- [x] Reviewed maintainability (structure, reuse, obsolete code cleanup).
|
|
12353
|
+
- [x] Reviewed test/verification coverage (or recorded reason if not run).
|
|
12354
|
+
`;
|
|
12355
|
+
}
|
|
12356
|
+
function prePrReviewCommand(program2) {
|
|
12357
|
+
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(
|
|
12358
|
+
"--decision <outcome>",
|
|
12359
|
+
"Decision outcome: approve | changes_requested | blocked"
|
|
12360
|
+
).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) => {
|
|
12361
|
+
try {
|
|
12362
|
+
await runPrePrReview(featureName, options);
|
|
12363
|
+
} catch (error) {
|
|
12364
|
+
const config = await getConfig(process.cwd());
|
|
12365
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
12366
|
+
const cliError = toCliError(error);
|
|
12367
|
+
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
12368
|
+
if (options.json) {
|
|
12369
|
+
console.log(
|
|
12370
|
+
JSON.stringify({
|
|
12371
|
+
status: "error",
|
|
12372
|
+
reasonCode: cliError.code,
|
|
12373
|
+
error: cliError.message,
|
|
12374
|
+
suggestions
|
|
12375
|
+
})
|
|
12376
|
+
);
|
|
12377
|
+
} else {
|
|
12378
|
+
console.error(chalk6.red(`[${cliError.code}] ${cliError.message}`));
|
|
12379
|
+
printCliErrorSuggestions(suggestions, lang);
|
|
12380
|
+
}
|
|
12381
|
+
process.exitCode = 1;
|
|
12382
|
+
}
|
|
12383
|
+
});
|
|
12384
|
+
}
|
|
12385
|
+
async function runPrePrReview(featureName, options) {
|
|
12386
|
+
const config = await getConfig(process.cwd());
|
|
12387
|
+
if (!config) {
|
|
12388
|
+
throw createCliError(
|
|
12389
|
+
"CONFIG_NOT_FOUND",
|
|
12390
|
+
"No lee-spec-kit config found in this workspace."
|
|
12391
|
+
);
|
|
12392
|
+
}
|
|
12393
|
+
const selectionOptions = {
|
|
12394
|
+
component: resolveComponentOption(options.component)
|
|
12395
|
+
};
|
|
12396
|
+
const state = await resolveContextSelection(config, featureName, selectionOptions);
|
|
12397
|
+
if (state.status !== "single_matched" || !state.matchedFeature) {
|
|
12398
|
+
throw createCliError(
|
|
12399
|
+
"CONTEXT_SELECTION_REQUIRED",
|
|
12400
|
+
"pre-pr-review requires a single matched feature. Pass <feature-name> explicitly."
|
|
12401
|
+
);
|
|
12402
|
+
}
|
|
12403
|
+
const feature = state.matchedFeature;
|
|
12404
|
+
if (!feature.docs.tasksExists) {
|
|
12405
|
+
throw createCliError(
|
|
12406
|
+
"PRECONDITION_FAILED",
|
|
12407
|
+
`tasks.md not found for feature: ${feature.folderName}`
|
|
12408
|
+
);
|
|
12409
|
+
}
|
|
12410
|
+
const tasksPath = path22.join(feature.path, "tasks.md");
|
|
12411
|
+
const tasksContent = await fs16.readFile(tasksPath, "utf-8");
|
|
12412
|
+
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
12413
|
+
const preferred = getPreferredKeys(config.lang);
|
|
12414
|
+
const date = getLocalDateString();
|
|
12415
|
+
const major = parseNonNegativeInt(options.major, "--major") ?? feature.prePrReview.findings?.major ?? 0;
|
|
12416
|
+
const minor = parseNonNegativeInt(options.minor, "--minor") ?? feature.prePrReview.findings?.minor ?? 0;
|
|
12417
|
+
const explicitDecision = normalizeDecision(options.decision);
|
|
12418
|
+
if (options.decision && !explicitDecision) {
|
|
12419
|
+
throw createCliError(
|
|
12420
|
+
"INVALID_ARGUMENT",
|
|
12421
|
+
"`--decision` must be one of: approve, changes_requested, blocked."
|
|
12422
|
+
);
|
|
12423
|
+
}
|
|
12424
|
+
const inferredDecision = major > 0 ? "changes_requested" : "approve";
|
|
12425
|
+
const decision = explicitDecision || inferredDecision;
|
|
12426
|
+
if (!policy.decisionEnum.includes(decision)) {
|
|
12427
|
+
throw createCliError(
|
|
12428
|
+
"INVALID_ARGUMENT",
|
|
12429
|
+
`Decision "${decision}" is not allowed by workflow.prePrReview.decisionEnum.`
|
|
12430
|
+
);
|
|
12431
|
+
}
|
|
12432
|
+
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");
|
|
12433
|
+
const reportPath = path22.join(feature.path, "pre-pr-review.md");
|
|
12434
|
+
const reportContent = buildReportContent({
|
|
12435
|
+
folderName: feature.folderName,
|
|
12436
|
+
date,
|
|
12437
|
+
decision,
|
|
12438
|
+
major,
|
|
12439
|
+
minor,
|
|
12440
|
+
note,
|
|
12441
|
+
fallback: policy.fallback,
|
|
12442
|
+
skills: policy.skills
|
|
12443
|
+
});
|
|
12444
|
+
await fs16.writeFile(reportPath, reportContent, "utf-8");
|
|
12445
|
+
const reportPathFromDocs = normalizePathForDoc(
|
|
12446
|
+
path22.join(feature.docs.featurePathFromDocs, "pre-pr-review.md")
|
|
12447
|
+
);
|
|
12448
|
+
const evidencePath = path22.basename(config.docsDir) === "docs" ? normalizePathForDoc(path22.join("docs", reportPathFromDocs)) : reportPathFromDocs;
|
|
12449
|
+
let nextTasks = tasksContent;
|
|
12450
|
+
nextTasks = upsertSpecLine(
|
|
12451
|
+
nextTasks,
|
|
12452
|
+
["PR \uC804 \uB9AC\uBDF0", "Pre-PR Review"],
|
|
12453
|
+
preferred.review,
|
|
12454
|
+
"Done",
|
|
12455
|
+
["PR \uC0C1\uD0DC", "PR Status"]
|
|
12456
|
+
);
|
|
12457
|
+
nextTasks = upsertSpecLine(
|
|
12458
|
+
nextTasks,
|
|
12459
|
+
["PR \uC804 \uB9AC\uBDF0 Findings", "Pre-PR Findings"],
|
|
12460
|
+
preferred.findings,
|
|
12461
|
+
`major=${major}, minor=${minor}`,
|
|
12462
|
+
["PR \uC804 \uB9AC\uBDF0", "Pre-PR Review"]
|
|
12463
|
+
);
|
|
12464
|
+
nextTasks = upsertSpecLine(
|
|
12465
|
+
nextTasks,
|
|
12466
|
+
["PR \uC804 \uB9AC\uBDF0 Evidence", "Pre-PR Evidence"],
|
|
12467
|
+
preferred.evidence,
|
|
12468
|
+
evidencePath,
|
|
12469
|
+
["PR \uC804 \uB9AC\uBDF0 Findings", "Pre-PR Findings", "PR \uC804 \uB9AC\uBDF0", "Pre-PR Review"]
|
|
12470
|
+
);
|
|
12471
|
+
nextTasks = upsertSpecLine(
|
|
12472
|
+
nextTasks,
|
|
12473
|
+
["PR \uC804 \uB9AC\uBDF0 Decision", "Pre-PR Decision"],
|
|
12474
|
+
preferred.decision,
|
|
12475
|
+
`decision: ${decision} - ${note}`,
|
|
12476
|
+
["PR \uC804 \uB9AC\uBDF0 Evidence", "Pre-PR Evidence"]
|
|
12477
|
+
);
|
|
12478
|
+
if (nextTasks !== tasksContent) {
|
|
12479
|
+
await fs16.writeFile(tasksPath, nextTasks, "utf-8");
|
|
12480
|
+
}
|
|
12481
|
+
if (options.json) {
|
|
12482
|
+
console.log(
|
|
12483
|
+
JSON.stringify(
|
|
12484
|
+
{
|
|
12485
|
+
status: "ok",
|
|
12486
|
+
reasonCode: "PRE_PR_REVIEW_RECORDED",
|
|
12487
|
+
feature: feature.folderName,
|
|
12488
|
+
reportPath: normalizePathForDoc(reportPath),
|
|
12489
|
+
evidencePath,
|
|
12490
|
+
decision,
|
|
12491
|
+
findings: { major, minor },
|
|
12492
|
+
tasksUpdated: nextTasks !== tasksContent
|
|
12493
|
+
},
|
|
12494
|
+
null,
|
|
12495
|
+
2
|
|
12496
|
+
)
|
|
12497
|
+
);
|
|
12498
|
+
return;
|
|
12499
|
+
}
|
|
12500
|
+
console.log();
|
|
12501
|
+
console.log(chalk6.green(`\u2705 pre-pr-review completed: ${feature.folderName}`));
|
|
12502
|
+
console.log(chalk6.gray(`- Decision: ${decision}`));
|
|
12503
|
+
console.log(chalk6.gray(`- Findings: major=${major}, minor=${minor}`));
|
|
12504
|
+
console.log(chalk6.gray(`- Report: ${reportPath}`));
|
|
12505
|
+
if (nextTasks !== tasksContent) {
|
|
12506
|
+
console.log(chalk6.gray(`- tasks.md updated: ${tasksPath}`));
|
|
12507
|
+
}
|
|
12508
|
+
console.log();
|
|
12509
|
+
}
|
|
11989
12510
|
function isBannerDisabled() {
|
|
11990
12511
|
const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
|
|
11991
12512
|
return v === "1";
|
|
@@ -12166,6 +12687,7 @@ githubCommand(program);
|
|
|
12166
12687
|
docsCommand(program);
|
|
12167
12688
|
detectCommand(program);
|
|
12168
12689
|
onboardCommand(program);
|
|
12690
|
+
prePrReviewCommand(program);
|
|
12169
12691
|
await program.parseAsync();
|
|
12170
12692
|
//# sourceMappingURL=index.js.map
|
|
12171
12693
|
//# sourceMappingURL=index.js.map
|