lee-spec-kit 0.6.33 → 0.6.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -512,6 +512,10 @@ var koContext = {
|
|
|
512
512
|
"context.actionDetail.codeReviewRemoteBlocked": "\uC6D0\uACA9 PR \uCC28\uB2E8 \uC0AC\uC720\uB97C \uD574\uC18C\uD55C \uB4A4 \uBA38\uC9C0\uB97C \uC9C4\uD589\uD558\uC138\uC694",
|
|
513
513
|
"context.actionDetail.codeReviewMergeAfterOk": "\uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 PR\uC744 \uBA38\uC9C0\uD558\uC138\uC694",
|
|
514
514
|
"context.actionDetail.codeReviewRequestReview": "\uB9AC\uBDF0 \uC694\uCCAD\uC744 \uC9C4\uD589\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC720\uC9C0\uD558\uC138\uC694",
|
|
515
|
+
"context.actionDetail.featureScopeSplit": "\uC774 Feature\uB97C \uB354 \uC791\uC740 \uC774\uC288 \uB2E8\uC704\uB85C \uBD84\uB9AC\uD560\uC9C0 \uAC80\uD1A0\uD558\uC138\uC694",
|
|
516
|
+
"context.actionDetail.featureScopeSplitKeep": "\uBD84\uD560 \uAC00\uC774\uB4DC\uB97C \uD655\uC778\uD55C \uB4A4 \uD604\uC7AC \uC774\uC288 \uBC94\uC704\uB97C \uC720\uC9C0\uD558\uACE0 \uC9C4\uD589\uD558\uC138\uC694",
|
|
517
|
+
"context.actionDetail.featureScopeSplitTwo": "\uACB0\uD569\uB3C4/\uD30C\uC77C\uACB9\uCE68/\uD14C\uC2A4\uD2B8/\uBC30\uD3EC \uAE30\uC900\uC73C\uB85C 2\uAC1C \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uC138\uC694",
|
|
518
|
+
"context.actionDetail.featureScopeSplitFour": "\uAE30\uC900 \uAE30\uBC18\uC73C\uB85C 4\uAC1C \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uACE0 \uC758\uC874 \uC21C\uC11C\uB300\uB85C PR\uC744 \uBA38\uC9C0\uD558\uC138\uC694",
|
|
515
519
|
"context.actionDetail.worktreeCleanup": "\uC644\uB8CC\uB41C feature worktree\uB97C \uC815\uB9AC\uD558\uC138\uC694",
|
|
516
520
|
"context.actionDetail.prMetadataMigrate": "tasks.md\uC758 PR \uD56D\uBAA9 \uD615\uC2DD\uC744 \uCD5C\uC2E0 \uD15C\uD50C\uB9BF\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694",
|
|
517
521
|
"context.actionDetail.prMetadataMigratePrFields": "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694",
|
|
@@ -636,6 +640,9 @@ var koMessages = {
|
|
|
636
640
|
prReviewMerge: "\uBA38\uC9C0 \uC900\uBE44\uAC00 \uB418\uBA74 \uBA85\uC2DC\uC801\uC778 \uC2B9\uC778(\uB77C\uBCA8)\uC744 \uBC1B\uC740 \uB4A4 \uBA38\uC9C0 \uC635\uC158\uC744 \uC2E4\uD589\uD558\uC138\uC694. (\uC131\uACF5 \uC2DC PR \uC0C1\uD0DC\uAC00 Approved\uB85C \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4.)",
|
|
637
641
|
prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
|
|
638
642
|
prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC124\uC815/\uC720\uC9C0\uD558\uC138\uC694.",
|
|
643
|
+
featureScopeSplitKeep: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}; \uBD84\uD560 \uC81C\uC548 \uAE30\uC900: {taskThreshold}/{decisionsThreshold}) \uD604\uC7AC \uAD8C\uC7A5 \uBD84\uD560\uC740 {recommendedIssues}\uAC1C \uC774\uC288\uC785\uB2C8\uB2E4. \uBA3C\uC800 `{guideCommand}`\uB97C \uD655\uC778\uD558\uACE0 \uACB0\uD569\uB3C4, \uBCC0\uACBD \uD30C\uC77C \uACB9\uCE68, \uD14C\uC2A4\uD2B8 \uACBD\uACC4, \uBC30\uD3EC \uB3C5\uB9BD\uC131 \uAE30\uC900\uC73C\uB85C \uD310\uB2E8\uD558\uC138\uC694. \uC774\uC288\uB97C \uC720\uC9C0\uD560 \uACBD\uC6B0 tasks.md \uBC94\uC704\uB97C \uCD95\uC18C\uD558\uACE0 \uC800\uC6B0\uC120 TODO\uB294 \uD65C\uC131 \uBC30\uCE58\uC5D0\uC11C \uC81C\uC678\uD558\uC138\uC694.",
|
|
644
|
+
featureScopeSplitTwo: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}) \uAD8C\uC7A5 \uADDC\uCE59\uC0C1 40~79 \uD0DC\uC2A4\uD06C\uC774\uBA74\uC11C \uD558\uB4DC \uAE30\uC900 \uBBF8\uB9CC\uC774\uBA74 2\uAC1C \uC774\uC288 \uBD84\uD560\uC774 \uAE30\uBCF8\uC785\uB2C8\uB2E4. `{guideCommand}`\uB97C \uB530\uB77C \uACB0\uD569\uB3C4/\uD30C\uC77C\uACB9\uCE68/\uD14C\uC2A4\uD2B8/\uBC30\uD3EC \uAE30\uC900\uC73C\uB85C \uBD84\uD560\uD558\uC138\uC694. \uAC01 \uC774\uC288\uC5D0\uB294 \uB2E4\uC74C \uD15C\uD50C\uB9BF\uC744 \uAE30\uB85D\uD558\uC138\uC694: \uBAA9\uD45C, \uD3EC\uD568 \uBC94\uC704, \uC81C\uC678 \uBC94\uC704, \uC120\uD589 \uC758\uC874\uC131, PR \uC644\uB8CC \uAE30\uC900.",
|
|
645
|
+
featureScopeSplitFour: "Feature \uBC94\uC704\uAC00 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}) tasks >= {recommendFourTaskThreshold} \uB610\uB294 decisions \uC904\uC218 >= {recommendFourDecisionsThreshold}\uC774\uBA74 4\uAC1C \uC774\uC288 \uBD84\uD560\uC744 \uAC15\uD558\uAC8C \uAD8C\uC7A5\uD569\uB2C8\uB2E4. `{guideCommand}`\uB97C \uB530\uB77C 4\uAC1C\uC758 \uC5F0\uAD00 \uC774\uC288\uB85C \uBD84\uB9AC\uD558\uACE0 \uC758\uC874 \uC21C\uC11C\uB97C \uBA85\uC2DC\uD55C \uB4A4 PR\uC744 \uC21C\uCC28 \uBA38\uC9C0\uD558\uC138\uC694. \uAC01 \uC774\uC288 \uD15C\uD50C\uB9BF: \uBAA9\uD45C, \uD3EC\uD568 \uBC94\uC704, \uC81C\uC678 \uBC94\uC704, \uC120\uD589 \uC758\uC874\uC131, PR \uC644\uB8CC \uAE30\uC900.",
|
|
639
646
|
userRequestReplan: "\uD604\uC7AC \uB2E8\uACC4\uC640 \uBCC4\uAC1C\uB85C \uC0AC\uC6A9\uC790\uAC00 \uC81C\uC548\uD55C \uC0C8 \uC694\uAD6C\uB97C \uBA3C\uC800 \uBC18\uC601\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC694\uAD6C\uC0AC\uD56D\uC744 \uC694\uC57D\uD574 tasks.md\uC5D0 \uCD94\uAC00\uD558\uAC70\uB098 \uBCC4\uB3C4 Feature\uB85C \uBD84\uB9AC\uD55C \uB4A4, \uBB38\uC11C \uC0C1\uD0DC\uB97C \uB9DE\uCD94\uACE0 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
|
|
640
647
|
featureDone: "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC694\uAD6C\uC0AC\uD56D\uACFC \uBAA8\uB4E0 \uD0DC\uC2A4\uD06C/\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uB8CC \uC0C1\uD0DC\uC785\uB2C8\uB2E4.",
|
|
641
648
|
fallbackRerunContext: "\uC0C1\uD0DC\uB97C \uD310\uBCC4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694."
|
|
@@ -648,6 +655,7 @@ var koWarnings = {
|
|
|
648
655
|
docsPathIgnored: "\uD604\uC7AC Feature \uBB38\uC11C \uACBD\uB85C\uAC00 git ignore \uB300\uC0C1\uC785\uB2C8\uB2E4: {path} (docs \uCEE4\uBC0B \uAC10\uC9C0\uAC00 \uC81C\uD55C\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.)",
|
|
649
656
|
docsUncommittedChanges: "\uBB38\uC11C \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uBB38\uC11C \uCEE4\uBC0B \uD544\uC694) \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59\uC740 git-workflow \uAC00\uC774\uB4DC\uB97C \uAE30\uC900\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694.",
|
|
650
657
|
projectUncommittedChanges: "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694)",
|
|
658
|
+
featureScopeSplitSuggested: "Feature \uBC94\uC704\uAC00 \uB2E8\uC77C \uC774\uC288\uB85C \uCC98\uB9AC\uD558\uAE30\uC5D0 \uD07D\uB2C8\uB2E4. (tasks: {taskCount}, decisions \uC904\uC218: {decisionsLineCount}; \uBD84\uD560 \uC81C\uC548 \uAE30\uC900: tasks {taskThreshold}\uAC1C \uB610\uB294 decisions {decisionsThreshold}\uC904) \uD604\uC7AC \uAD8C\uC7A5 \uBD84\uD560: {recommendedIssues}\uAC1C \uC774\uC288 (4\uBD84\uD560 \uD558\uB4DC \uAE30\uC900: tasks >= {recommendFourTaskThreshold} \uB610\uB294 decisions \uC904\uC218 >= {recommendFourDecisionsThreshold}).",
|
|
651
659
|
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.",
|
|
652
660
|
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.",
|
|
653
661
|
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`)",
|
|
@@ -1041,6 +1049,10 @@ var enContext = {
|
|
|
1041
1049
|
"context.actionDetail.codeReviewRemoteBlocked": "Resolve remote PR blockers before merge",
|
|
1042
1050
|
"context.actionDetail.codeReviewMergeAfterOk": "Merge PR after explicit OK",
|
|
1043
1051
|
"context.actionDetail.codeReviewRequestReview": "Request review and keep PR Status as Review",
|
|
1052
|
+
"context.actionDetail.featureScopeSplit": "Review whether this feature should be split into smaller issue units",
|
|
1053
|
+
"context.actionDetail.featureScopeSplitKeep": "Keep current issue scope and continue (after split-guide check)",
|
|
1054
|
+
"context.actionDetail.featureScopeSplitTwo": "Split into 2 linked issues using coupling/file-overlap/test/deploy criteria",
|
|
1055
|
+
"context.actionDetail.featureScopeSplitFour": "Split into 4 linked issues (criteria-based) and merge PRs in dependency order",
|
|
1044
1056
|
"context.actionDetail.worktreeCleanup": "Clean up the completed feature worktree",
|
|
1045
1057
|
"context.actionDetail.prMetadataMigrate": "Update tasks.md PR fields to the latest template format",
|
|
1046
1058
|
"context.actionDetail.prMetadataMigratePrFields": "Update tasks.md with PR/PR Status fields",
|
|
@@ -1165,6 +1177,9 @@ var enMessages = {
|
|
|
1165
1177
|
prReviewMerge: "When ready to merge, get explicit approval (label) and run the merge option. (On success, PR Status is synced to Approved.)",
|
|
1166
1178
|
prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
|
|
1167
1179
|
prRequestReview: "Request review and set/keep PR Status as Review.",
|
|
1180
|
+
featureScopeSplitKeep: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}; split suggestion threshold: {taskThreshold}/{decisionsThreshold}). Current recommendation is {recommendedIssues} issues. Before deciding, read `{guideCommand}` and evaluate coupling, changed-file overlap, test boundary, and deployment independence. If you keep one issue, tighten scope in tasks.md and move low-priority TODO items out of the active batch.",
|
|
1181
|
+
featureScopeSplitTwo: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}). Recommendation rule: 40~79 tasks and below the hard threshold usually maps to 2 issues. Follow `{guideCommand}` and split by coupling/file-overlap/test/deploy criteria. For each new issue, document this template: Goal, Included Scope, Excluded Scope, Depends On, PR Done Criteria.",
|
|
1182
|
+
featureScopeSplitFour: "Feature scope is large (tasks: {taskCount}, decisions lines: {decisionsLineCount}). Hard recommendation is 4 issues when tasks >= {recommendFourTaskThreshold} or decisions lines >= {recommendFourDecisionsThreshold}. Follow `{guideCommand}` and split into 4 linked issues with explicit dependency order and sequential PR merges. Use the per-issue template: Goal, Included Scope, Excluded Scope, Depends On, PR Done Criteria.",
|
|
1168
1183
|
userRequestReplan: "You can pause this step and handle a newly requested user requirement first. Summarize it, add it to tasks.md or split it into a separate Feature, then align document statuses and rerun context.",
|
|
1169
1184
|
featureDone: "Workflow requirements and all tasks/completion criteria are satisfied. This feature is done.",
|
|
1170
1185
|
fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
|
|
@@ -1177,6 +1192,7 @@ var enWarnings = {
|
|
|
1177
1192
|
docsPathIgnored: "Current feature docs path is ignored by git: {path} (docs commit detection may be limited).",
|
|
1178
1193
|
docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Check commit message rules against the git-workflow guide.",
|
|
1179
1194
|
projectUncommittedChanges: "Project code changes are not committed. (Additional code commit needed.)",
|
|
1195
|
+
featureScopeSplitSuggested: "Feature scope may be too large for one issue (tasks: {taskCount}, decisions lines: {decisionsLineCount}; suggest split at {taskThreshold} tasks or {decisionsThreshold} decision lines). Current recommendation: split into {recommendedIssues} issue units (hard 4-way rule: tasks >= {recommendFourTaskThreshold} or decisions lines >= {recommendFourDecisionsThreshold}).",
|
|
1180
1196
|
legacyTasksDocStatusField: "Legacy tasks.md format detected. Add a `Doc Status` field (Draft/Review/Approved) to enable tasks approval.",
|
|
1181
1197
|
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
|
|
1182
1198
|
legacyTasksPrePrReviewField: "Legacy tasks.md format detected. Add `Pre-PR Review` before PR steps. (`- **Pre-PR Review**: Pending | Done`)",
|
|
@@ -3163,6 +3179,7 @@ var ACTION_CATEGORIES = [
|
|
|
3163
3179
|
"pre_pr_review",
|
|
3164
3180
|
"pr_status_update",
|
|
3165
3181
|
"code_review",
|
|
3182
|
+
"feature_scope_split",
|
|
3166
3183
|
"worktree_cleanup",
|
|
3167
3184
|
"user_request_replan",
|
|
3168
3185
|
"feature_done",
|
|
@@ -4575,6 +4592,52 @@ function applyTaskExecutePhaseCheck(action, requiresUserCheck, policy, explicitl
|
|
|
4575
4592
|
if (explicitlyRequired) return requiresUserCheck;
|
|
4576
4593
|
return false;
|
|
4577
4594
|
}
|
|
4595
|
+
function withFeatureScopeSplitOptions(actions, feature, lang) {
|
|
4596
|
+
if (!feature.scopeSplit.suggested) return actions;
|
|
4597
|
+
if (feature.tasks.total === 0 || feature.tasks.done >= feature.tasks.total) {
|
|
4598
|
+
return actions;
|
|
4599
|
+
}
|
|
4600
|
+
if (actions.some((action) => action.category === "feature_scope_split")) {
|
|
4601
|
+
return actions;
|
|
4602
|
+
}
|
|
4603
|
+
const recommendedIssues = feature.scopeSplit.recommendation === "split_4" ? 4 : 2;
|
|
4604
|
+
const recommendationLabel = feature.scopeSplit.recommendation === "split_4" ? "split_4" : feature.scopeSplit.recommendation === "split_2" ? "split_2" : "none";
|
|
4605
|
+
const vars = {
|
|
4606
|
+
taskCount: feature.scopeSplit.taskCount,
|
|
4607
|
+
decisionsLineCount: feature.scopeSplit.decisionsLineCount,
|
|
4608
|
+
taskThreshold: feature.scopeSplit.suggestTaskCountThreshold,
|
|
4609
|
+
decisionsThreshold: feature.scopeSplit.suggestDecisionsLineCountThreshold,
|
|
4610
|
+
recommendFourTaskThreshold: feature.scopeSplit.recommendSplitFourTaskCountThreshold,
|
|
4611
|
+
recommendFourDecisionsThreshold: feature.scopeSplit.recommendSplitFourDecisionsLineCountThreshold,
|
|
4612
|
+
recommendedIssues,
|
|
4613
|
+
recommendationLabel,
|
|
4614
|
+
guideCommand: "npx lee-spec-kit docs get split-feature --json"
|
|
4615
|
+
};
|
|
4616
|
+
return [
|
|
4617
|
+
...actions,
|
|
4618
|
+
{
|
|
4619
|
+
type: "instruction",
|
|
4620
|
+
category: "feature_scope_split",
|
|
4621
|
+
requiresUserCheck: true,
|
|
4622
|
+
uiDetailKey: "context.actionDetail.featureScopeSplitKeep",
|
|
4623
|
+
message: tr(lang, "messages", "featureScopeSplitKeep", vars)
|
|
4624
|
+
},
|
|
4625
|
+
{
|
|
4626
|
+
type: "instruction",
|
|
4627
|
+
category: "feature_scope_split",
|
|
4628
|
+
requiresUserCheck: true,
|
|
4629
|
+
uiDetailKey: "context.actionDetail.featureScopeSplitTwo",
|
|
4630
|
+
message: tr(lang, "messages", "featureScopeSplitTwo", vars)
|
|
4631
|
+
},
|
|
4632
|
+
{
|
|
4633
|
+
type: "instruction",
|
|
4634
|
+
category: "feature_scope_split",
|
|
4635
|
+
requiresUserCheck: true,
|
|
4636
|
+
uiDetailKey: "context.actionDetail.featureScopeSplitFour",
|
|
4637
|
+
message: tr(lang, "messages", "featureScopeSplitFour", vars)
|
|
4638
|
+
}
|
|
4639
|
+
];
|
|
4640
|
+
}
|
|
4578
4641
|
function withUserRequestReplanOption(actions, lang) {
|
|
4579
4642
|
if (actions.some((action) => action.category === "user_request_replan")) {
|
|
4580
4643
|
return actions;
|
|
@@ -4594,8 +4657,13 @@ function resolveFeatureProgress(feature, stepDefinitions, lang, approval) {
|
|
|
4594
4657
|
for (const definition of ordered) {
|
|
4595
4658
|
if (!definition.current) continue;
|
|
4596
4659
|
if (definition.current.when(feature)) {
|
|
4597
|
-
const
|
|
4660
|
+
const actionsWithScopeSplit = withFeatureScopeSplitOptions(
|
|
4598
4661
|
definition.current.actions(feature),
|
|
4662
|
+
feature,
|
|
4663
|
+
lang
|
|
4664
|
+
);
|
|
4665
|
+
const currentActions = withUserRequestReplanOption(
|
|
4666
|
+
actionsWithScopeSplit,
|
|
4599
4667
|
lang
|
|
4600
4668
|
);
|
|
4601
4669
|
const actions = applyApprovalPolicy(
|
|
@@ -4843,6 +4911,17 @@ function isExpectedFeatureBranch(branchName, issueNumber, slug, folderName) {
|
|
|
4843
4911
|
const rest = match[1];
|
|
4844
4912
|
return rest === slug || rest === folderName;
|
|
4845
4913
|
}
|
|
4914
|
+
var FEATURE_SCOPE_SPLIT_TASK_THRESHOLD = 40;
|
|
4915
|
+
var FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD = 1200;
|
|
4916
|
+
var FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD = 80;
|
|
4917
|
+
var FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD = 2500;
|
|
4918
|
+
function countDocumentLines(content) {
|
|
4919
|
+
if (!content) return 0;
|
|
4920
|
+
const lines = content.split(/\r?\n/);
|
|
4921
|
+
if (lines.length === 0) return 0;
|
|
4922
|
+
if (lines[lines.length - 1] === "") return lines.length - 1;
|
|
4923
|
+
return lines.length;
|
|
4924
|
+
}
|
|
4846
4925
|
function escapeRegExp(value) {
|
|
4847
4926
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4848
4927
|
}
|
|
@@ -5466,6 +5545,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5466
5545
|
const specPath = path12.join(featurePath, "spec.md");
|
|
5467
5546
|
const planPath = path12.join(featurePath, "plan.md");
|
|
5468
5547
|
const tasksPath = path12.join(featurePath, "tasks.md");
|
|
5548
|
+
const decisionsPath = path12.join(featurePath, "decisions.md");
|
|
5469
5549
|
const issueDocPath = path12.join(featurePath, "issue.md");
|
|
5470
5550
|
const prDocPath = path12.join(featurePath, "pr.md");
|
|
5471
5551
|
let specStatus;
|
|
@@ -5514,6 +5594,12 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5514
5594
|
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
5515
5595
|
planStatus = parseDocStatus(statusValue);
|
|
5516
5596
|
}
|
|
5597
|
+
const decisionsExists = await ctx.fs.pathExists(decisionsPath);
|
|
5598
|
+
let decisionsLineCount = 0;
|
|
5599
|
+
if (decisionsExists) {
|
|
5600
|
+
const content = await ctx.fs.readFile(decisionsPath, "utf-8");
|
|
5601
|
+
decisionsLineCount = countDocumentLines(content);
|
|
5602
|
+
}
|
|
5517
5603
|
const tasksExists = await ctx.fs.pathExists(tasksPath);
|
|
5518
5604
|
const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
|
|
5519
5605
|
let activeTask;
|
|
@@ -5701,6 +5787,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5701
5787
|
if (workflowPolicy.requireMerge && prStatus === "Review" && prLink && effectiveProjectGitCwd) {
|
|
5702
5788
|
prRemote = resolvePrRemoteStatus(ctx, prLink, effectiveProjectGitCwd) || void 0;
|
|
5703
5789
|
}
|
|
5790
|
+
const scopeSplitReasons = [];
|
|
5791
|
+
if (tasksSummary.total >= FEATURE_SCOPE_SPLIT_TASK_THRESHOLD) {
|
|
5792
|
+
scopeSplitReasons.push("task_count");
|
|
5793
|
+
}
|
|
5794
|
+
if (decisionsLineCount >= FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD) {
|
|
5795
|
+
scopeSplitReasons.push("decisions_lines");
|
|
5796
|
+
}
|
|
5797
|
+
const scopeSplitSuggested = scopeSplitReasons.length > 0;
|
|
5798
|
+
const scopeSplitRecommendFour = tasksSummary.total >= FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD || decisionsLineCount >= FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD;
|
|
5799
|
+
const scopeSplitRecommendation = !scopeSplitSuggested ? "none" : scopeSplitRecommendFour ? "split_4" : "split_2";
|
|
5704
5800
|
const warnings = [];
|
|
5705
5801
|
if (effectiveProjectBranchAvailable === false) {
|
|
5706
5802
|
warnings.push(tr(lang, "warnings", "projectBranchUnavailable"));
|
|
@@ -5835,6 +5931,19 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5835
5931
|
if (tasksExists && !tasksDocStatusFieldExists) {
|
|
5836
5932
|
warnings.push(tr(lang, "warnings", "legacyTasksDocStatusField"));
|
|
5837
5933
|
}
|
|
5934
|
+
if (scopeSplitSuggested && tasksSummary.total > tasksSummary.done) {
|
|
5935
|
+
warnings.push(
|
|
5936
|
+
tr(lang, "warnings", "featureScopeSplitSuggested", {
|
|
5937
|
+
taskCount: tasksSummary.total,
|
|
5938
|
+
decisionsLineCount,
|
|
5939
|
+
taskThreshold: FEATURE_SCOPE_SPLIT_TASK_THRESHOLD,
|
|
5940
|
+
decisionsThreshold: FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD,
|
|
5941
|
+
recommendFourTaskThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD,
|
|
5942
|
+
recommendFourDecisionsThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD,
|
|
5943
|
+
recommendedIssues: scopeSplitRecommendation === "split_4" ? 4 : 2
|
|
5944
|
+
})
|
|
5945
|
+
);
|
|
5946
|
+
}
|
|
5838
5947
|
if (docsEverCommitted && docsHasUncommittedChanges) {
|
|
5839
5948
|
warnings.push(tr(lang, "warnings", "docsUncommittedChanges"));
|
|
5840
5949
|
}
|
|
@@ -5946,6 +6055,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5946
6055
|
planStatus,
|
|
5947
6056
|
tasksDocStatus,
|
|
5948
6057
|
tasks: tasksSummary,
|
|
6058
|
+
scopeSplit: {
|
|
6059
|
+
suggested: scopeSplitSuggested,
|
|
6060
|
+
reasons: scopeSplitReasons,
|
|
6061
|
+
recommendation: scopeSplitRecommendation,
|
|
6062
|
+
taskCount: tasksSummary.total,
|
|
6063
|
+
decisionsLineCount,
|
|
6064
|
+
suggestTaskCountThreshold: FEATURE_SCOPE_SPLIT_TASK_THRESHOLD,
|
|
6065
|
+
suggestDecisionsLineCountThreshold: FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD,
|
|
6066
|
+
recommendSplitFourTaskCountThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD,
|
|
6067
|
+
recommendSplitFourDecisionsLineCountThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD
|
|
6068
|
+
},
|
|
5949
6069
|
activeTask,
|
|
5950
6070
|
lastDoneTask,
|
|
5951
6071
|
nextTodoTask,
|
|
@@ -6966,12 +7086,18 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
6966
7086
|
id: "create-pr",
|
|
6967
7087
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
6968
7088
|
relativePath: (_, lang) => path12.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
7089
|
+
},
|
|
7090
|
+
{
|
|
7091
|
+
id: "split-feature",
|
|
7092
|
+
title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
|
|
7093
|
+
relativePath: (_, lang) => path12.join(lang, "common", "agents", "skills", "split-feature.md")
|
|
6969
7094
|
}
|
|
6970
7095
|
];
|
|
6971
7096
|
var DOC_FOLLOWUPS = {
|
|
6972
7097
|
agents: [
|
|
6973
7098
|
"create-feature",
|
|
6974
7099
|
"execute-task",
|
|
7100
|
+
"split-feature",
|
|
6975
7101
|
"git-workflow",
|
|
6976
7102
|
"create-issue",
|
|
6977
7103
|
"issue-doc",
|
|
@@ -6982,9 +7108,10 @@ var DOC_FOLLOWUPS = {
|
|
|
6982
7108
|
"issue-doc": [],
|
|
6983
7109
|
"pr-doc": [],
|
|
6984
7110
|
"create-feature": ["execute-task"],
|
|
6985
|
-
"execute-task": ["git-workflow"],
|
|
7111
|
+
"execute-task": ["git-workflow", "split-feature"],
|
|
6986
7112
|
"create-issue": ["issue-doc"],
|
|
6987
|
-
"create-pr": ["pr-doc"]
|
|
7113
|
+
"create-pr": ["pr-doc"],
|
|
7114
|
+
"split-feature": []
|
|
6988
7115
|
};
|
|
6989
7116
|
var CATEGORY_DOC_MAP = {
|
|
6990
7117
|
spec_write: ["agents"],
|
|
@@ -7002,6 +7129,7 @@ var CATEGORY_DOC_MAP = {
|
|
|
7002
7129
|
pr_create: ["create-pr", "pr-doc", "git-workflow"],
|
|
7003
7130
|
pr_status_update: ["create-pr"],
|
|
7004
7131
|
code_review: ["create-pr"],
|
|
7132
|
+
feature_scope_split: ["split-feature", "execute-task"],
|
|
7005
7133
|
worktree_cleanup: ["git-workflow"],
|
|
7006
7134
|
user_request_replan: ["agents", "execute-task"]
|
|
7007
7135
|
};
|
|
@@ -7019,6 +7147,9 @@ function normalizeBuiltinDocId(input) {
|
|
|
7019
7147
|
if (normalized === "execute-task") return "execute-task";
|
|
7020
7148
|
if (normalized === "create-issue") return "create-issue";
|
|
7021
7149
|
if (normalized === "create-pr") return "create-pr";
|
|
7150
|
+
if (normalized === "split-feature" || normalized === "feature-split") {
|
|
7151
|
+
return "split-feature";
|
|
7152
|
+
}
|
|
7022
7153
|
if (normalized === "agents") return "agents";
|
|
7023
7154
|
return null;
|
|
7024
7155
|
}
|
|
@@ -7107,6 +7238,7 @@ var ACTION_DETAIL_KEY_BY_CATEGORY = {
|
|
|
7107
7238
|
pr_create: "context.actionDetail.prCreate",
|
|
7108
7239
|
pr_status_update: "context.actionDetail.prStatusUpdate",
|
|
7109
7240
|
code_review: "context.actionDetail.codeReview",
|
|
7241
|
+
feature_scope_split: "context.actionDetail.featureScopeSplit",
|
|
7110
7242
|
worktree_cleanup: "context.actionDetail.worktreeCleanup",
|
|
7111
7243
|
pr_metadata_migrate: "context.actionDetail.prMetadataMigrate",
|
|
7112
7244
|
user_request_replan: "context.actionDetail.userRequestReplan",
|
|
@@ -13877,6 +14009,22 @@ function normalizeCommandsExecuted(value) {
|
|
|
13877
14009
|
if (!Array.isArray(value)) return [];
|
|
13878
14010
|
return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
13879
14011
|
}
|
|
14012
|
+
function normalizeGitPath3(value) {
|
|
14013
|
+
return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
14014
|
+
}
|
|
14015
|
+
function parseGitPathList(stdout) {
|
|
14016
|
+
return stdout.split("\n").map((entry) => normalizeGitPath3(entry)).filter(Boolean);
|
|
14017
|
+
}
|
|
14018
|
+
function uniquePaths(values) {
|
|
14019
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14020
|
+
const out = [];
|
|
14021
|
+
for (const value of values) {
|
|
14022
|
+
if (!value || seen.has(value)) continue;
|
|
14023
|
+
seen.add(value);
|
|
14024
|
+
out.push(value);
|
|
14025
|
+
}
|
|
14026
|
+
return out;
|
|
14027
|
+
}
|
|
13880
14028
|
function normalizeEvidenceFiles(value) {
|
|
13881
14029
|
if (!Array.isArray(value)) {
|
|
13882
14030
|
throw createCliError(
|
|
@@ -13920,6 +14068,10 @@ var PrePrReviewValidator = class {
|
|
|
13920
14068
|
this.ctx = ctx;
|
|
13921
14069
|
}
|
|
13922
14070
|
async validateEvidence(evidencePath, projectRoot) {
|
|
14071
|
+
const result = await this.validateEvidenceWithScope(evidencePath, projectRoot);
|
|
14072
|
+
return result.evidence;
|
|
14073
|
+
}
|
|
14074
|
+
async validateEvidenceWithScope(evidencePath, projectRoot) {
|
|
13923
14075
|
const fullPath = path12.resolve(evidencePath);
|
|
13924
14076
|
if (!await fs.pathExists(fullPath)) {
|
|
13925
14077
|
throw createCliError(
|
|
@@ -13958,14 +14110,18 @@ var PrePrReviewValidator = class {
|
|
|
13958
14110
|
);
|
|
13959
14111
|
}
|
|
13960
14112
|
}
|
|
13961
|
-
const
|
|
14113
|
+
const scope = await this.collectReviewScope(projectRoot);
|
|
14114
|
+
const changedFiles = uniquePaths([
|
|
14115
|
+
...scope.mainChangedFiles,
|
|
14116
|
+
...scope.worktreeChangedFiles
|
|
14117
|
+
]);
|
|
13962
14118
|
const reviewedFiles = new Set(
|
|
13963
14119
|
normalizedEvidence.files.map(
|
|
13964
14120
|
(f) => path12.relative(
|
|
13965
14121
|
projectRoot,
|
|
13966
14122
|
path12.resolve(projectRoot, f.path)
|
|
13967
14123
|
)
|
|
13968
|
-
)
|
|
14124
|
+
).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
|
|
13969
14125
|
);
|
|
13970
14126
|
const missingFiles = changedFiles.filter((f) => !reviewedFiles.has(f));
|
|
13971
14127
|
if (missingFiles.length > 0) {
|
|
@@ -13975,40 +14131,120 @@ var PrePrReviewValidator = class {
|
|
|
13975
14131
|
${missingFiles.map((f) => `- ${f}`).join("\n")}`
|
|
13976
14132
|
);
|
|
13977
14133
|
}
|
|
13978
|
-
return
|
|
14134
|
+
return {
|
|
14135
|
+
evidence: normalizedEvidence,
|
|
14136
|
+
scope
|
|
14137
|
+
};
|
|
13979
14138
|
}
|
|
13980
|
-
async
|
|
13981
|
-
const
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
14139
|
+
async collectReviewScope(cwd) {
|
|
14140
|
+
const baseRef = await this.resolveBaseRef(cwd);
|
|
14141
|
+
const mergeBase = await this.resolveMergeBase(cwd, baseRef);
|
|
14142
|
+
const mainDiff = await this.getMainChangedFiles(cwd, mergeBase);
|
|
14143
|
+
const worktreeDiff = await this.getWorktreeChangedFiles(cwd);
|
|
14144
|
+
if (!mainDiff.ok && !worktreeDiff.ok) {
|
|
14145
|
+
throw createCliError(
|
|
14146
|
+
"VALIDATION_FAILED",
|
|
14147
|
+
"Unable to determine changed files from git diff. Ensure this is a git repository with accessible history."
|
|
14148
|
+
);
|
|
13985
14149
|
}
|
|
13986
|
-
|
|
14150
|
+
return {
|
|
14151
|
+
baseRef,
|
|
14152
|
+
mergeBase,
|
|
14153
|
+
mainRange: mainDiff.rangeLabel,
|
|
14154
|
+
mainChangedFiles: mainDiff.files,
|
|
14155
|
+
worktreeChangedFiles: worktreeDiff.files
|
|
14156
|
+
};
|
|
14157
|
+
}
|
|
14158
|
+
async resolveBaseRef(cwd) {
|
|
13987
14159
|
try {
|
|
13988
|
-
const
|
|
14160
|
+
const result = await this.ctx.cmd.runAsync(
|
|
13989
14161
|
"git",
|
|
13990
|
-
["
|
|
14162
|
+
["rev-parse", "--abbrev-ref", "origin/HEAD"],
|
|
13991
14163
|
{ cwd }
|
|
13992
14164
|
);
|
|
13993
|
-
if (
|
|
13994
|
-
|
|
14165
|
+
if (result.code === 0) {
|
|
14166
|
+
const value = result.stdout.trim();
|
|
14167
|
+
if (value && value !== "origin/HEAD") {
|
|
14168
|
+
return value;
|
|
14169
|
+
}
|
|
13995
14170
|
}
|
|
13996
14171
|
} catch {
|
|
13997
14172
|
}
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14173
|
+
return "origin/main";
|
|
14174
|
+
}
|
|
14175
|
+
async resolveMergeBase(cwd, baseRef) {
|
|
14176
|
+
const candidates = uniquePaths([
|
|
14177
|
+
baseRef,
|
|
14178
|
+
baseRef.replace(/^origin\//, ""),
|
|
14179
|
+
"origin/main",
|
|
14180
|
+
"main"
|
|
14181
|
+
]);
|
|
14182
|
+
for (const candidate of candidates) {
|
|
14183
|
+
if (!candidate) continue;
|
|
14184
|
+
try {
|
|
14185
|
+
const result = await this.ctx.cmd.runAsync(
|
|
14186
|
+
"git",
|
|
14187
|
+
["merge-base", "HEAD", candidate],
|
|
14188
|
+
{ cwd }
|
|
14189
|
+
);
|
|
14190
|
+
if (result.code !== 0) continue;
|
|
14191
|
+
const mergeBase = normalizeGitPath3(result.stdout);
|
|
14192
|
+
if (mergeBase) return mergeBase;
|
|
14193
|
+
} catch {
|
|
14194
|
+
}
|
|
14007
14195
|
}
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14196
|
+
return null;
|
|
14197
|
+
}
|
|
14198
|
+
async getMainChangedFiles(cwd, mergeBase) {
|
|
14199
|
+
const ranges = uniquePaths([
|
|
14200
|
+
mergeBase ? `${mergeBase}..HEAD` : "",
|
|
14201
|
+
"HEAD~1..HEAD",
|
|
14202
|
+
"HEAD^..HEAD"
|
|
14203
|
+
]);
|
|
14204
|
+
for (const range of ranges) {
|
|
14205
|
+
if (!range) continue;
|
|
14206
|
+
try {
|
|
14207
|
+
const result = await this.ctx.cmd.runAsync(
|
|
14208
|
+
"git",
|
|
14209
|
+
["diff", "--name-only", range],
|
|
14210
|
+
{ cwd }
|
|
14211
|
+
);
|
|
14212
|
+
if (result.code !== 0) continue;
|
|
14213
|
+
return {
|
|
14214
|
+
ok: true,
|
|
14215
|
+
rangeLabel: range,
|
|
14216
|
+
files: uniquePaths(parseGitPathList(result.stdout))
|
|
14217
|
+
};
|
|
14218
|
+
} catch {
|
|
14219
|
+
}
|
|
14220
|
+
}
|
|
14221
|
+
return {
|
|
14222
|
+
ok: false,
|
|
14223
|
+
rangeLabel: mergeBase ? `${mergeBase}..HEAD` : "HEAD~1..HEAD",
|
|
14224
|
+
files: []
|
|
14225
|
+
};
|
|
14226
|
+
}
|
|
14227
|
+
async getWorktreeChangedFiles(cwd) {
|
|
14228
|
+
let hasSuccessfulCommand = false;
|
|
14229
|
+
const files = [];
|
|
14230
|
+
const commands = [
|
|
14231
|
+
["diff", "--name-only"],
|
|
14232
|
+
["diff", "--name-only", "--cached"],
|
|
14233
|
+
["ls-files", "--others", "--exclude-standard"]
|
|
14234
|
+
];
|
|
14235
|
+
for (const args of commands) {
|
|
14236
|
+
try {
|
|
14237
|
+
const result = await this.ctx.cmd.runAsync("git", args, { cwd });
|
|
14238
|
+
if (result.code !== 0) continue;
|
|
14239
|
+
hasSuccessfulCommand = true;
|
|
14240
|
+
files.push(...parseGitPathList(result.stdout));
|
|
14241
|
+
} catch {
|
|
14242
|
+
}
|
|
14243
|
+
}
|
|
14244
|
+
return {
|
|
14245
|
+
ok: hasSuccessfulCommand,
|
|
14246
|
+
files: uniquePaths(files)
|
|
14247
|
+
};
|
|
14012
14248
|
}
|
|
14013
14249
|
};
|
|
14014
14250
|
|
|
@@ -14106,6 +14342,8 @@ function buildReportContent(input) {
|
|
|
14106
14342
|
- Maintainability: ${f.review.maintainability}`;
|
|
14107
14343
|
}).join("\n");
|
|
14108
14344
|
}
|
|
14345
|
+
const mainScopeFiles = input.scope.mainChangedFiles.length > 0 ? input.scope.mainChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
|
|
14346
|
+
const worktreeScopeFiles = input.scope.worktreeChangedFiles.length > 0 ? input.scope.worktreeChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
|
|
14109
14347
|
return `## Pre-PR Review Log (${input.date})
|
|
14110
14348
|
|
|
14111
14349
|
- **Feature**: ${input.folderName}
|
|
@@ -14119,12 +14357,30 @@ ${commandsRun}
|
|
|
14119
14357
|
- **Residual Risks**:
|
|
14120
14358
|
- ${input.evidence.residualRisks}
|
|
14121
14359
|
|
|
14360
|
+
- **Review Scope**:
|
|
14361
|
+
- **Main Base Ref**: ${input.scope.baseRef}
|
|
14362
|
+
- **Main Merge Base**: ${input.scope.mergeBase ?? "unresolved"}
|
|
14363
|
+
- **Main Range**: ${input.scope.mainRange}
|
|
14364
|
+
- **Main Changed Files**:
|
|
14365
|
+
${mainScopeFiles}
|
|
14366
|
+
- **Worktree Changed Files**:
|
|
14367
|
+
${worktreeScopeFiles}
|
|
14368
|
+
|
|
14122
14369
|
- **Findings (Changed Files)**:
|
|
14123
14370
|
${filesSection}
|
|
14124
14371
|
|
|
14125
14372
|
- **Trace**: pre-pr-review command executed and synced with tasks.md
|
|
14126
14373
|
`;
|
|
14127
14374
|
}
|
|
14375
|
+
function createFallbackReviewScope() {
|
|
14376
|
+
return {
|
|
14377
|
+
baseRef: "origin/main",
|
|
14378
|
+
mergeBase: null,
|
|
14379
|
+
mainRange: "HEAD~1..HEAD",
|
|
14380
|
+
mainChangedFiles: [],
|
|
14381
|
+
worktreeChangedFiles: []
|
|
14382
|
+
};
|
|
14383
|
+
}
|
|
14128
14384
|
function appendDecisionLog(content, entry) {
|
|
14129
14385
|
const normalized = content.trimEnd();
|
|
14130
14386
|
if (!normalized) return `${entry.trim()}
|
|
@@ -14216,12 +14472,15 @@ async function runPrePrReview(featureName, options) {
|
|
|
14216
14472
|
}
|
|
14217
14473
|
const note = options.note?.trim() || (decision === "approve" ? "baseline review completed" : decision === "changes_requested" ? "follow-up changes are required before PR creation" : "blocked until prerequisite risk is resolved");
|
|
14218
14474
|
let evidenceObj;
|
|
14475
|
+
let reviewScope = createFallbackReviewScope();
|
|
14219
14476
|
if (options.evidence) {
|
|
14220
14477
|
const validator = new PrePrReviewValidator(ctx);
|
|
14221
|
-
|
|
14478
|
+
const validationResult = await validator.validateEvidenceWithScope(
|
|
14222
14479
|
options.evidence,
|
|
14223
14480
|
process.cwd()
|
|
14224
14481
|
);
|
|
14482
|
+
evidenceObj = validationResult.evidence;
|
|
14483
|
+
reviewScope = validationResult.scope;
|
|
14225
14484
|
} else if (policy.evidenceMode === "path_required") {
|
|
14226
14485
|
throw createCliError(
|
|
14227
14486
|
"INVALID_ARGUMENT",
|
|
@@ -14229,6 +14488,12 @@ async function runPrePrReview(featureName, options) {
|
|
|
14229
14488
|
);
|
|
14230
14489
|
} else {
|
|
14231
14490
|
evidenceObj = DEFAULT_EVIDENCE_FOR_ANY_MODE;
|
|
14491
|
+
const validator = new PrePrReviewValidator(ctx);
|
|
14492
|
+
try {
|
|
14493
|
+
reviewScope = await validator.collectReviewScope(process.cwd());
|
|
14494
|
+
} catch {
|
|
14495
|
+
reviewScope = createFallbackReviewScope();
|
|
14496
|
+
}
|
|
14232
14497
|
}
|
|
14233
14498
|
const decisionsPath = path12.join(feature.path, "decisions.md");
|
|
14234
14499
|
const decisionLogEntry = buildReportContent({
|
|
@@ -14238,7 +14503,8 @@ async function runPrePrReview(featureName, options) {
|
|
|
14238
14503
|
note,
|
|
14239
14504
|
fallback: policy.fallback,
|
|
14240
14505
|
skills: policy.skills,
|
|
14241
|
-
evidence: evidenceObj
|
|
14506
|
+
evidence: evidenceObj,
|
|
14507
|
+
scope: reviewScope
|
|
14242
14508
|
});
|
|
14243
14509
|
const decisionsContent = await fs.pathExists(decisionsPath) ? await fs.readFile(decisionsPath, "utf-8") : "";
|
|
14244
14510
|
const nextDecisions = appendDecisionLog(decisionsContent, decisionLogEntry);
|
|
@@ -14285,6 +14551,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
14285
14551
|
decisionsPath: normalizePathForDoc(decisionsPath),
|
|
14286
14552
|
evidencePath,
|
|
14287
14553
|
decision,
|
|
14554
|
+
reviewScope,
|
|
14288
14555
|
tasksUpdated: nextTasks !== tasksContent
|
|
14289
14556
|
},
|
|
14290
14557
|
null,
|