lee-spec-kit 0.6.33 → 0.6.35
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 +18 -2
- package/README.md +18 -2
- package/dist/index.js +657 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/agents/skills/create-pr.md +6 -5
- package/templates/en/common/agents/skills/split-feature.md +95 -0
- package/templates/ko/common/agents/skills/create-pr.md +6 -5
- package/templates/ko/common/agents/skills/split-feature.md +95 -0
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import chalk8 from 'chalk';
|
|
|
8
8
|
import { spawn, spawnSync, execFileSync, execSync } from 'child_process';
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import { createHash, randomUUID } from 'crypto';
|
|
11
|
-
import
|
|
11
|
+
import fs9 from 'fs';
|
|
12
12
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
13
13
|
|
|
14
14
|
var getFilename = () => fileURLToPath(import.meta.url);
|
|
@@ -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",
|
|
@@ -595,6 +599,8 @@ var koMessages = {
|
|
|
595
599
|
reviewFixCommitGuidance: "PR \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uB9AC\uBDF0 \uBC18\uC601 \uD30C\uC77C\uB9CC \uC2A4\uD14C\uC774\uC9D5\uD55C \uB4A4 `fix(review): <review-fix-summary>` \uD615\uC2DD\uC73C\uB85C \uCEE4\uBC0B\uD558\uC138\uC694. `<review-fix-summary>`\uC5D0\uB294 \uC774\uBC88 \uCEE4\uBC0B\uC5D0\uC11C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uD56D\uBAA9 \uC694\uC57D\uC744 \uC791\uC131\uD558\uC138\uC694. (\uD0DC\uC2A4\uD06C \uC81C\uBAA9 \uC7AC\uC0AC\uC6A9 \uAE08\uC9C0)",
|
|
596
600
|
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
597
601
|
createBranch: 'cd "{projectGitCwd}" && mkdir -p .worktrees && (git worktree add ".worktrees/feat-{issueNumber}-{slug}" "feat/{issueNumber}-{slug}" || git worktree add -b "feat/{issueNumber}-{slug}" ".worktrees/feat-{issueNumber}-{slug}") && WT="{projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}" && for f in .env .env.local .env.development .env.development.local .env.test .env.test.local .env.production .env.production.local; do [ -f "{projectGitCwd}/$f" ] && [ ! -e "$WT/$f" ] && cp "{projectGitCwd}/$f" "$WT/$f" || true; done && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
|
|
602
|
+
moveToExistingWorktree: '\uD574\uB2F9 feature\uC6A9 worktree\uAC00 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 `cd "{worktreePath}"`\uB85C \uC774\uB3D9\uD55C \uB4A4 context\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.',
|
|
603
|
+
worktreeRequiredFromMainBranch: "`workflow.requireWorktree`\uAC00 \uD65C\uC131\uD654\uB418\uC5B4 \uC788\uC9C0\uB9CC \uD604\uC7AC feature \uBE0C\uB79C\uCE58\uAC00 \uBA54\uC778 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uCCB4\uD06C\uC544\uC6C3\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uBA54\uC778 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uAE30\uBCF8 \uBE0C\uB79C\uCE58\uB85C \uB418\uB3CC\uB9B0 \uB4A4 `.worktrees/feat-{issueNumber}-{slug}`\uB97C \uC0DD\uC131/\uC7AC\uC0AC\uC6A9\uD558\uACE0 \uADF8 worktree\uC5D0\uC11C \uACC4\uC18D \uC9C4\uD589\uD558\uC138\uC694.",
|
|
598
604
|
worktreeCleanupCommand: 'cd "{projectGitCwd}" && WT="{worktreePath}" && ROOT="$(pwd)" && case "$WT" in "$ROOT"/.worktrees/*) if git worktree list --porcelain | grep -Fxq "worktree $WT"; then git worktree remove --force "$WT" || true; fi; [ -d "$WT" ] && rm -rf "$WT" || true ;; *) echo "skip unsafe worktree path: $WT" ;; esac && git worktree prune && CURRENT_BRANCH=$(git branch --show-current) && DEFAULT_BRANCH=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | cut -d/ -f2-) && TARGET_BRANCH="${DEFAULT_BRANCH:-$CURRENT_BRANCH}" && if [ -n "$TARGET_BRANCH" ]; then git checkout "$TARGET_BRANCH" >/dev/null 2>&1 || true; fi && if git rev-parse --abbrev-ref --symbolic-full-name "@{u}" >/dev/null 2>&1 && [ -z "$(git status --porcelain)" ]; then git pull --ff-only || true; fi',
|
|
599
605
|
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. \uBA85\uC2DC\uC801\uC778 \uCD5C\uC885 \uC2B9\uC778\uB3C4 \uBC18\uC601\uD558\uC138\uC694.',
|
|
600
606
|
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 \uBA85\uC2DC\uC801\uC778 \uCD5C\uC885 \uC2B9\uC778\uC744 \uBC18\uC601\uD558\uC138\uC694.",
|
|
@@ -609,9 +615,10 @@ var koMessages = {
|
|
|
609
615
|
taskCommitGateReasonMismatchLastDone: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B\uC774 \uC9C1\uC804 \uC644\uB8CC \uD0DC\uC2A4\uD06C\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
610
616
|
prLegacyAsk: "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD15C\uD50C\uB9BF\uC744 \uCD5C\uC2E0 \uD3EC\uB9F7\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694? (\uD655\uC778 \uD544\uC694)",
|
|
611
617
|
prePrReviewFieldMissing: "tasks.md\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uC804 \uB9AC\uBDF0**: Pending | Done` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
612
|
-
prePrReviewRun: "\uCF54\uB4DC \uB9AC\uBDF0 \uC5D0\uC774\uC804\uD2B8\uB97C \uC2E4\uD589\uD574 `review-trace.json`\uC744 \uC0DD\uC131\uD55C \uB4A4, `pre-pr-review --evidence review-trace.json`\uC73C\uB85C \uB9AC\uBDF0 \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
613
|
-
prePrReviewEvidenceMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC2E4\uC81C \uD30C\uC77C \uACBD\uB85C\uC640 `Pre-PR Review Log`(\uB610\uB294 `PR \uC804 \uB9AC\uBDF0 \uB85C\uADF8`)\uC5D0 placeholder\uAC00 \uC544\uB2CC `Summary`/`Decision`/`Findings`(\uB610\uB294 \uBA85\uC2DC\uC801 `0 findings`)/`Residual Risks`/`Tests Run
|
|
618
|
+
prePrReviewRun: "\uCF54\uB4DC \uB9AC\uBDF0 \uC5D0\uC774\uC804\uD2B8\uB97C \uC2E4\uD589\uD574 \uBE44\uC5B4\uC788\uC9C0 \uC54A\uC740 `commandsExecuted`\uB97C \uD3EC\uD568\uD55C `review-trace.json`\uC744 \uC0DD\uC131\uD55C \uB4A4, `pre-pr-review --evidence review-trace.json`\uC73C\uB85C \uB9AC\uBDF0 \uACB0\uACFC\uB97C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
619
|
+
prePrReviewEvidenceMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC2E4\uC81C \uD30C\uC77C \uACBD\uB85C\uC640 `Pre-PR Review Log`(\uB610\uB294 `PR \uC804 \uB9AC\uBDF0 \uB85C\uADF8`)\uC5D0 placeholder\uAC00 \uC544\uB2CC `Summary`/`Decision`/`Findings`(\uB610\uB294 \uBA85\uC2DC\uC801 `0 findings`)/`Residual Risks`/`Tests Run`/\uC2E4\uD589 \uBA85\uB839(`commandsExecuted`)\uC744 \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
614
620
|
prePrReviewDecisionMissing: "tasks.md\uC758 `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 \uBE44\uC5B4\uC788\uAC70\uB098 \uACB0\uC815 \uD615\uC2DD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `\uACB0\uC815: ...`(\uB610\uB294 `decision: ...`) \uD615\uC2DD\uC73C\uB85C \uAE30\uB85D\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
621
|
+
prePrReviewDecisionReconfirm: "\uD604\uC7AC `PR \uC804 \uB9AC\uBDF0 Decision`\uC774 `{decision}`\uC785\uB2C8\uB2E4. \uC9C0\uC801\uC0AC\uD56D\uC744 \uBC18\uC601\uD55C \uB4A4 \uC774\uC804 \uC0C1\uD0DC \uC7AC\uC0AC\uC6A9\uC744 \uB9C9\uAE30 \uC704\uD574 \uBA85\uC2DC\uC801\uC73C\uB85C Decision\uC744 \uC9C0\uC815\uD574 \uC7AC\uC2E4\uD589\uD558\uC138\uC694: `{command}` (\uD655\uC778 \uD544\uC694)",
|
|
615
622
|
prReviewEvidenceFieldMissing: "tasks.md\uC5D0 `PR \uB9AC\uBDF0 Evidence` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uB9AC\uBDF0 Evidence**: -` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC \uC9C4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
616
623
|
prReviewEvidenceMissing: "tasks.md\uC758 `PR \uB9AC\uBDF0 Evidence`\uAC00 \uBE44\uC5B4\uC788\uAC70\uB098 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. `\uC694\uC57D: ...`(\uB610\uB294 `summary: ...`) \uD615\uC2DD\uC73C\uB85C \uAE30\uB85D\uD558\uAC70\uB098 `PR Review Log`(\uB610\uB294 `PR \uB9AC\uBDF0 \uB85C\uADF8`)\uC758 `Summary`/`Decision`\uC774 \uC788\uB294 \uD30C\uC77C \uACBD\uB85C\uB97C \uC9C0\uC815\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
617
624
|
prReviewDecisionFieldMissing: "tasks.md\uC5D0 `PR \uB9AC\uBDF0 Decision` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **PR \uB9AC\uBDF0 Decision**: -` \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0 \uB2E4\uC2DC \uC9C4\uD589\uD558\uC138\uC694. (\uD655\uC778 \uD544\uC694)",
|
|
@@ -636,6 +643,9 @@ var koMessages = {
|
|
|
636
643
|
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
644
|
prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
|
|
638
645
|
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.",
|
|
646
|
+
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.",
|
|
647
|
+
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.",
|
|
648
|
+
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
649
|
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
650
|
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
651
|
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."
|
|
@@ -644,10 +654,13 @@ var koMessages = {
|
|
|
644
654
|
// src/utils/locales/ko/warnings.ts
|
|
645
655
|
var koWarnings = {
|
|
646
656
|
projectBranchUnavailable: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.)",
|
|
657
|
+
projectExpectedBranchOnMainWorkspace: "feature \uBE0C\uB79C\uCE58\uAC00 \uBA54\uC778 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uCCB4\uD06C\uC544\uC6C3\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uAC00\uB2A5\uD558\uBA74 `.worktrees/*` \uACBD\uB85C\uC5D0\uC11C \uC791\uC5C5\uD558\uC138\uC694.",
|
|
658
|
+
workflowWorktreeRequired: "`workflow.requireWorktree=true` \uC124\uC815\uC73C\uB85C \uC778\uD574 \uD0DC\uC2A4\uD06C \uC2E4\uD589\uC740 `.worktrees/*` \uACBD\uB85C\uC5D0\uC11C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.",
|
|
647
659
|
docsGitUnavailable: "docs \uB808\uD3EC\uC758 git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uB808\uD3EC \uC704\uCE58 / git init \uD655\uC778)",
|
|
648
660
|
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
661
|
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
662
|
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)",
|
|
663
|
+
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
664
|
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
665
|
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
666
|
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 +1054,10 @@ var enContext = {
|
|
|
1041
1054
|
"context.actionDetail.codeReviewRemoteBlocked": "Resolve remote PR blockers before merge",
|
|
1042
1055
|
"context.actionDetail.codeReviewMergeAfterOk": "Merge PR after explicit OK",
|
|
1043
1056
|
"context.actionDetail.codeReviewRequestReview": "Request review and keep PR Status as Review",
|
|
1057
|
+
"context.actionDetail.featureScopeSplit": "Review whether this feature should be split into smaller issue units",
|
|
1058
|
+
"context.actionDetail.featureScopeSplitKeep": "Keep current issue scope and continue (after split-guide check)",
|
|
1059
|
+
"context.actionDetail.featureScopeSplitTwo": "Split into 2 linked issues using coupling/file-overlap/test/deploy criteria",
|
|
1060
|
+
"context.actionDetail.featureScopeSplitFour": "Split into 4 linked issues (criteria-based) and merge PRs in dependency order",
|
|
1044
1061
|
"context.actionDetail.worktreeCleanup": "Clean up the completed feature worktree",
|
|
1045
1062
|
"context.actionDetail.prMetadataMigrate": "Update tasks.md PR fields to the latest template format",
|
|
1046
1063
|
"context.actionDetail.prMetadataMigratePrFields": "Update tasks.md with PR/PR Status fields",
|
|
@@ -1124,6 +1141,8 @@ var enMessages = {
|
|
|
1124
1141
|
reviewFixCommitGuidance: "Commit PR review fixes. Stage only review-fix files, then commit with `fix(review): <review-fix-summary>`. `<review-fix-summary>` must describe review comments resolved in this commit (do not reuse task titles).",
|
|
1125
1142
|
standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
|
|
1126
1143
|
createBranch: 'cd "{projectGitCwd}" && mkdir -p .worktrees && (git worktree add ".worktrees/feat-{issueNumber}-{slug}" "feat/{issueNumber}-{slug}" || git worktree add -b "feat/{issueNumber}-{slug}" ".worktrees/feat-{issueNumber}-{slug}") && WT="{projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}" && for f in .env .env.local .env.development .env.development.local .env.test .env.test.local .env.production .env.production.local; do [ -f "{projectGitCwd}/$f" ] && [ ! -e "$WT/$f" ] && cp "{projectGitCwd}/$f" "$WT/$f" || true; done && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
|
|
1144
|
+
moveToExistingWorktree: 'A matching feature worktree already exists. Move first: `cd "{worktreePath}"`, then re-run context.',
|
|
1145
|
+
worktreeRequiredFromMainBranch: "`workflow.requireWorktree` is enabled, but this feature branch is checked out in the main workspace. Switch the main workspace to a base branch first, then create/reuse `.worktrees/feat-{issueNumber}-{slug}` and continue from that worktree.",
|
|
1127
1146
|
worktreeCleanupCommand: 'cd "{projectGitCwd}" && WT="{worktreePath}" && ROOT="$(pwd)" && case "$WT" in "$ROOT"/.worktrees/*) if git worktree list --porcelain | grep -Fxq "worktree $WT"; then git worktree remove --force "$WT" || true; fi; [ -d "$WT" ] && rm -rf "$WT" || true ;; *) echo "skip unsafe worktree path: $WT" ;; esac && git worktree prune && CURRENT_BRANCH=$(git branch --show-current) && DEFAULT_BRANCH=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | cut -d/ -f2-) && TARGET_BRANCH="${DEFAULT_BRANCH:-$CURRENT_BRANCH}" && if [ -n "$TARGET_BRANCH" ]; then git checkout "$TARGET_BRANCH" >/dev/null 2>&1 || true; fi && if git rev-parse --abbrev-ref --symbolic-full-name "@{u}" >/dev/null 2>&1 && [ -z "$(git status --porcelain)" ]; then git pull --ff-only || true; fi',
|
|
1128
1147
|
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 explicit final approval as well.',
|
|
1129
1148
|
tasksAllDoneButChecklist: "Proceed with remaining completion checklist items. Current progress: ({checked}/{total}) Mark items as [x] only after user confirmation and real verification. Record explicit final approval as well.",
|
|
@@ -1138,9 +1157,10 @@ var enMessages = {
|
|
|
1138
1157
|
taskCommitGateReasonMismatchLastDone: "The latest project code commit does not match the last completed task",
|
|
1139
1158
|
prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
|
|
1140
1159
|
prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
|
|
1141
|
-
prePrReviewRun: "Run the code review agent
|
|
1142
|
-
prePrReviewEvidenceMissing: "tasks.md `Pre-PR Evidence` is empty/invalid. Point to a real file and include a `Pre-PR Review Log` section with non-placeholder `Summary`, `Decision`, `Findings` (or explicit `0 findings`), `Residual Risks`,
|
|
1160
|
+
prePrReviewRun: "Run the code review agent and generate `review-trace.json` with non-empty `commandsExecuted`, then execute `pre-pr-review --evidence review-trace.json` to record findings. (CHECK required)",
|
|
1161
|
+
prePrReviewEvidenceMissing: "tasks.md `Pre-PR Evidence` is empty/invalid. Point to a real file and include a `Pre-PR Review Log` section with non-placeholder `Summary`, `Decision`, `Findings` (or explicit `0 findings`), `Residual Risks`, `Tests Run`, and real executed commands (`commandsExecuted`). (CHECK required)",
|
|
1143
1162
|
prePrReviewDecisionMissing: "tasks.md `Pre-PR Decision` is empty/placeholder or missing decision format. Record it as `decision: ...` (or `\uACB0\uC815: ...`). (CHECK required)",
|
|
1163
|
+
prePrReviewDecisionReconfirm: "Current `Pre-PR Decision` is `{decision}`. After fixing findings, rerun pre-PR review with an explicit decision to avoid replaying the prior state: `{command}` (CHECK required)",
|
|
1144
1164
|
prReviewEvidenceFieldMissing: "tasks.md is missing the `PR Review Evidence` field. Add `- **PR Review Evidence**: -` and continue. (CHECK required)",
|
|
1145
1165
|
prReviewEvidenceMissing: "tasks.md `PR Review Evidence` is empty/invalid. Use `summary: ...` (or `\uC694\uC57D: ...`), or point to a file containing `PR Review Log` with non-placeholder `Summary` and `Decision`. (CHECK required)",
|
|
1146
1166
|
prReviewDecisionFieldMissing: "tasks.md is missing the `PR Review Decision` field. Add `- **PR Review Decision**: -` and continue. (CHECK required)",
|
|
@@ -1165,6 +1185,9 @@ var enMessages = {
|
|
|
1165
1185
|
prReviewMerge: "When ready to merge, get explicit approval (label) and run the merge option. (On success, PR Status is synced to Approved.)",
|
|
1166
1186
|
prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
|
|
1167
1187
|
prRequestReview: "Request review and set/keep PR Status as Review.",
|
|
1188
|
+
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.",
|
|
1189
|
+
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.",
|
|
1190
|
+
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
1191
|
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
1192
|
featureDone: "Workflow requirements and all tasks/completion criteria are satisfied. This feature is done.",
|
|
1170
1193
|
fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
|
|
@@ -1173,10 +1196,13 @@ var enMessages = {
|
|
|
1173
1196
|
// src/utils/locales/en/warnings.ts
|
|
1174
1197
|
var enWarnings = {
|
|
1175
1198
|
projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
|
|
1199
|
+
projectExpectedBranchOnMainWorkspace: "Feature branch is checked out in the main workspace. Prefer working from a `.worktrees/*` path.",
|
|
1200
|
+
workflowWorktreeRequired: "With `workflow.requireWorktree=true`, task execution is allowed only from `.worktrees/*` paths.",
|
|
1176
1201
|
docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
|
|
1177
1202
|
docsPathIgnored: "Current feature docs path is ignored by git: {path} (docs commit detection may be limited).",
|
|
1178
1203
|
docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Check commit message rules against the git-workflow guide.",
|
|
1179
1204
|
projectUncommittedChanges: "Project code changes are not committed. (Additional code commit needed.)",
|
|
1205
|
+
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
1206
|
legacyTasksDocStatusField: "Legacy tasks.md format detected. Add a `Doc Status` field (Draft/Review/Approved) to enable tasks approval.",
|
|
1181
1207
|
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
|
|
1182
1208
|
legacyTasksPrePrReviewField: "Legacy tasks.md format detected. Add `Pre-PR Review` before PR steps. (`- **Pre-PR Review**: Pending | Done`)",
|
|
@@ -2444,6 +2470,7 @@ async function runInit(options) {
|
|
|
2444
2470
|
docsRepo,
|
|
2445
2471
|
workflow: {
|
|
2446
2472
|
mode: workflowMode,
|
|
2473
|
+
requireWorktree: false,
|
|
2447
2474
|
codeDirtyScope: "auto",
|
|
2448
2475
|
taskCommitGate: "warn",
|
|
2449
2476
|
auto: {
|
|
@@ -2453,7 +2480,9 @@ async function runInit(options) {
|
|
|
2453
2480
|
skills: ["code-review-excellence"],
|
|
2454
2481
|
fallback: "builtin-checklist",
|
|
2455
2482
|
evidenceMode: "path_required",
|
|
2456
|
-
decisionEnum: ["approve", "changes_requested", "blocked"]
|
|
2483
|
+
decisionEnum: ["approve", "changes_requested", "blocked"],
|
|
2484
|
+
enforceExecutionEvidence: true,
|
|
2485
|
+
executionCommandPrefixes: []
|
|
2457
2486
|
}
|
|
2458
2487
|
},
|
|
2459
2488
|
pr: {
|
|
@@ -3163,6 +3192,7 @@ var ACTION_CATEGORIES = [
|
|
|
3163
3192
|
"pre_pr_review",
|
|
3164
3193
|
"pr_status_update",
|
|
3165
3194
|
"code_review",
|
|
3195
|
+
"feature_scope_split",
|
|
3166
3196
|
"worktree_cleanup",
|
|
3167
3197
|
"user_request_replan",
|
|
3168
3198
|
"feature_done",
|
|
@@ -3182,6 +3212,7 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3182
3212
|
mode,
|
|
3183
3213
|
requireIssue: false,
|
|
3184
3214
|
requireBranch: false,
|
|
3215
|
+
requireWorktree: false,
|
|
3185
3216
|
requirePr: false,
|
|
3186
3217
|
requireReview: false,
|
|
3187
3218
|
requireMerge: false
|
|
@@ -3189,6 +3220,7 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3189
3220
|
mode,
|
|
3190
3221
|
requireIssue: true,
|
|
3191
3222
|
requireBranch: true,
|
|
3223
|
+
requireWorktree: false,
|
|
3192
3224
|
requirePr: true,
|
|
3193
3225
|
requireReview: true,
|
|
3194
3226
|
requireMerge: true
|
|
@@ -3199,6 +3231,9 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3199
3231
|
if (typeof workflow?.requireBranch === "boolean") {
|
|
3200
3232
|
policy.requireBranch = workflow.requireBranch;
|
|
3201
3233
|
}
|
|
3234
|
+
if (typeof workflow?.requireWorktree === "boolean") {
|
|
3235
|
+
policy.requireWorktree = workflow.requireWorktree;
|
|
3236
|
+
}
|
|
3202
3237
|
if (typeof workflow?.requirePr === "boolean") {
|
|
3203
3238
|
policy.requirePr = workflow.requirePr;
|
|
3204
3239
|
}
|
|
@@ -3211,6 +3246,9 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3211
3246
|
if (!policy.requireIssue) {
|
|
3212
3247
|
policy.requireBranch = false;
|
|
3213
3248
|
}
|
|
3249
|
+
if (!policy.requireBranch) {
|
|
3250
|
+
policy.requireWorktree = false;
|
|
3251
|
+
}
|
|
3214
3252
|
if (!policy.requirePr) {
|
|
3215
3253
|
policy.requireReview = false;
|
|
3216
3254
|
policy.requireMerge = false;
|
|
@@ -3267,6 +3305,16 @@ function normalizeDecisionEnumList(input) {
|
|
|
3267
3305
|
}
|
|
3268
3306
|
return [...deduped];
|
|
3269
3307
|
}
|
|
3308
|
+
function normalizeCommandPrefixList(input) {
|
|
3309
|
+
if (!Array.isArray(input)) return [];
|
|
3310
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
3311
|
+
for (const raw of input) {
|
|
3312
|
+
const value = String(raw || "").trim();
|
|
3313
|
+
if (!value) continue;
|
|
3314
|
+
deduped.add(value);
|
|
3315
|
+
}
|
|
3316
|
+
return [...deduped];
|
|
3317
|
+
}
|
|
3270
3318
|
function resolvePrePrReviewPolicy(workflow) {
|
|
3271
3319
|
const workflowPolicy = resolveWorkflowPolicy(workflow);
|
|
3272
3320
|
const configured = workflow?.prePrReview;
|
|
@@ -3274,17 +3322,45 @@ function resolvePrePrReviewPolicy(workflow) {
|
|
|
3274
3322
|
const configuredDecisionEnum = normalizeDecisionEnumList(
|
|
3275
3323
|
configured?.decisionEnum
|
|
3276
3324
|
);
|
|
3325
|
+
const configuredCommandPrefixes = normalizeCommandPrefixList(
|
|
3326
|
+
configured?.executionCommandPrefixes
|
|
3327
|
+
);
|
|
3277
3328
|
const configuredEnabled = typeof configured?.enabled === "boolean" ? configured.enabled : workflowPolicy.requirePr;
|
|
3329
|
+
const configuredExecutionEvidence = typeof configured?.enforceExecutionEvidence === "boolean" ? configured.enforceExecutionEvidence : true;
|
|
3278
3330
|
return {
|
|
3279
3331
|
enabled: workflowPolicy.requirePr ? configuredEnabled : false,
|
|
3280
3332
|
skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
|
|
3281
3333
|
fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
|
|
3282
3334
|
evidenceMode: configured?.evidenceMode === "any" ? "any" : "path_required",
|
|
3283
|
-
decisionEnum: configuredDecisionEnum.length > 0 ? configuredDecisionEnum : DEFAULT_PRE_PR_DECISION_ENUM
|
|
3335
|
+
decisionEnum: configuredDecisionEnum.length > 0 ? configuredDecisionEnum : DEFAULT_PRE_PR_DECISION_ENUM,
|
|
3336
|
+
enforceExecutionEvidence: configuredExecutionEvidence,
|
|
3337
|
+
executionCommandPrefixes: configuredCommandPrefixes.length > 0 ? configuredCommandPrefixes : []
|
|
3284
3338
|
};
|
|
3285
3339
|
}
|
|
3286
3340
|
|
|
3287
3341
|
// src/utils/agent-orchestration.ts
|
|
3342
|
+
function getPrePrReviewPrompt(lang, skills, fallbackText) {
|
|
3343
|
+
if (lang === "ko") {
|
|
3344
|
+
return `PR \uC0DD\uC131 \uC804 \uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0\uB97C \uC9C4\uD589\uD558\uC138\uC694.
|
|
3345
|
+
1. \uC790\uB3D9\uD654\uB41C \uC624\uB958 \uBD84\uC11D\uAE30(\uC608: vitest, biome check, pnpm audit)\uB97C \uC2E4\uD589\uD558\uC138\uC694.
|
|
3346
|
+
2. \uB9AC\uBDF0 \uBC94\uC704\uB97C \uBD84\uB9AC\uD574 \uD655\uC778\uD558\uC138\uC694.
|
|
3347
|
+
- main \uAE30\uC900: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
|
|
3348
|
+
- worktree \uAE30\uC900: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
|
|
3349
|
+
3. \uD655\uC778\uB41C \uAC01 \uD30C\uC77C\uC5D0 \uB300\uD574 risk, security, perf, maintainability \uD3C9\uAC00\uC640 \uAD6C\uCCB4\uC801\uC778 fileLine \uC704\uCE58\uAC00 \uD3EC\uD568\uB41C 'review-trace.json' \uC99D\uAC70 \uD30C\uC77C\uC744 \uC0DD\uC131\uD558\uC138\uC694. \uC794\uC5EC \uC704\uD5D8(residualRisks)\uACFC \uC2E4\uD589\uD55C \uBA85\uB839\uC5B4(commandsExecuted)\uB3C4 \uD3EC\uD568\uD558\uC138\uC694.
|
|
3350
|
+
4. \uAE30\uBCF8 \uBCA0\uC774\uC2A4\uB77C\uC778\uC740 '${fallbackText}'\uC774\uBA70, 'create-pr' \uBB38\uC11C\uC758 'Pre-PR \uAE30\uBCF8 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8' \uC139\uC158\uC744 \uC218\uD589\uD558\uC138\uC694.
|
|
3351
|
+
5. \uC6B0\uC120\uC21C\uC704 \uC2A4\uD0AC: ${skills.length > 0 ? skills.join(", ") : "\uC5C6\uC74C"} \uB85C \uC2EC\uD654 \uAC80\uD1A0\uB97C \uC9C4\uD589\uD558\uC138\uC694.
|
|
3352
|
+
\uC644\uB8CC \uD6C4 'pnpm lee pre-pr-review <feature> --evidence review-trace.json --decision approve' \uB97C \uC2E4\uD589\uD558\uC138\uC694.`;
|
|
3353
|
+
}
|
|
3354
|
+
return `Conduct a pre-PR code review.
|
|
3355
|
+
1. Run automated analyzers (e.g., vitest, biome check, pnpm audit).
|
|
3356
|
+
2. Split and check the review scope.
|
|
3357
|
+
- Main scope: 'git diff --name-only $(git merge-base HEAD origin/main)..HEAD'
|
|
3358
|
+
- Worktree scope: 'git diff --name-only', 'git diff --name-only --cached', 'git ls-files --others --exclude-standard'
|
|
3359
|
+
3. Generate a 'review-trace.json' file for all changed files, including evaluations for risk, security, perf, maintainability, and specific fileLine locators. Also include residualRisks and commandsExecuted array.
|
|
3360
|
+
4. The baseline is '${fallbackText}'. Always perform the 'Pre-PR Core Checklist' section of the 'create-pr' document.
|
|
3361
|
+
5. Priority skills: ${skills.length > 0 ? skills.join(", ") : "None"} for deeper technical review.
|
|
3362
|
+
After completion, execute 'pnpm lee pre-pr-review <feature> --evidence review-trace.json --decision approve'.`;
|
|
3363
|
+
}
|
|
3288
3364
|
function getCodeReviewPrompt(lang) {
|
|
3289
3365
|
if (lang === "ko") {
|
|
3290
3366
|
return `\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD655\uC778/\uBD84\uC11D\uD55C \uB4A4 \uD544\uC694\uD55C \uC218\uC815\uC744 \uC9C4\uD589\uD558\uC138\uC694. PR \uC0C1\uD0DC\uB294 Review\uB97C \uC720\uC9C0\uD558\uACE0 'PR \uB9AC\uBDF0 Evidence/Decision'\uC744 \uCD5C\uC2E0\uC73C\uB85C \uAE30\uB85D\uD558\uC138\uC694. \uC6D0\uACA9 \uBC18\uC601(push)\uC740 \uBA85\uC2DC\uC801\uC778 \uBA38\uC9C0 \uC2B9\uC778(\uB77C\uBCA8) \uD6C4, \uB85C\uCEEC \uBE0C\uB79C\uCE58\uAC00 upstream\uBCF4\uB2E4 \uC55E\uC120 \uACBD\uC6B0\uC5D0\uB9CC \uC9C4\uD589\uD558\uC138\uC694.`;
|
|
@@ -3371,6 +3447,17 @@ function buildSelfCliCommand(args) {
|
|
|
3371
3447
|
const base = [process.execPath, entry, "--no-banner", ...args];
|
|
3372
3448
|
return base.map((arg) => toShellArg(arg)).join(" ");
|
|
3373
3449
|
}
|
|
3450
|
+
function buildPrePrReviewCommandArgs(feature, evidencePath, decision) {
|
|
3451
|
+
const commandArgs = ["pre-pr-review", feature.folderName];
|
|
3452
|
+
if (feature.type && feature.type !== "single") {
|
|
3453
|
+
commandArgs.push("--component", feature.type);
|
|
3454
|
+
}
|
|
3455
|
+
commandArgs.push("--evidence", evidencePath);
|
|
3456
|
+
if (decision) {
|
|
3457
|
+
commandArgs.push("--decision", decision);
|
|
3458
|
+
}
|
|
3459
|
+
return commandArgs;
|
|
3460
|
+
}
|
|
3374
3461
|
function resolvePrePrReviewEvidencePath(feature) {
|
|
3375
3462
|
const docsRoot = feature.git.docsGitCwd;
|
|
3376
3463
|
const candidates = [];
|
|
@@ -3392,7 +3479,7 @@ function resolvePrePrReviewEvidencePath(feature) {
|
|
|
3392
3479
|
const abs = path12.resolve(candidate);
|
|
3393
3480
|
if (seen.has(abs)) continue;
|
|
3394
3481
|
seen.add(abs);
|
|
3395
|
-
if (!
|
|
3482
|
+
if (!fs9.existsSync(abs)) continue;
|
|
3396
3483
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
3397
3484
|
const rel = path12.relative(docsRoot, abs).replace(/\\/g, "/");
|
|
3398
3485
|
if (rel && !rel.startsWith("../")) {
|
|
@@ -3849,6 +3936,60 @@ function getStepDefinitions(ctx) {
|
|
|
3849
3936
|
current: {
|
|
3850
3937
|
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && (f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)) && isTasksDocApproved(f) && (!workflowPolicy.requireBranch || f.git.onExpectedBranch || f.tasks.done === f.tasks.total),
|
|
3851
3938
|
actions: (f) => {
|
|
3939
|
+
if (workflowPolicy.requireWorktree && f.tasks.done < f.tasks.total && f.issueNumber && !f.git.projectInManagedWorktree) {
|
|
3940
|
+
if (f.git.expectedWorktreePath) {
|
|
3941
|
+
return [
|
|
3942
|
+
{
|
|
3943
|
+
type: "instruction",
|
|
3944
|
+
category: "branch_create",
|
|
3945
|
+
requiresUserCheck: true,
|
|
3946
|
+
uiDetailKey: "context.actionDetail.branchCreate",
|
|
3947
|
+
message: tr(lang, "messages", "moveToExistingWorktree", {
|
|
3948
|
+
worktreePath: f.git.expectedWorktreePath
|
|
3949
|
+
})
|
|
3950
|
+
}
|
|
3951
|
+
];
|
|
3952
|
+
}
|
|
3953
|
+
if (!f.git.projectGitCwd) {
|
|
3954
|
+
return [
|
|
3955
|
+
{
|
|
3956
|
+
type: "instruction",
|
|
3957
|
+
category: "branch_create",
|
|
3958
|
+
requiresUserCheck: true,
|
|
3959
|
+
message: tr(lang, "messages", "standaloneNeedsProjectRoot")
|
|
3960
|
+
}
|
|
3961
|
+
];
|
|
3962
|
+
}
|
|
3963
|
+
if (f.git.onExpectedBranch) {
|
|
3964
|
+
return [
|
|
3965
|
+
{
|
|
3966
|
+
type: "instruction",
|
|
3967
|
+
category: "branch_create",
|
|
3968
|
+
requiresUserCheck: true,
|
|
3969
|
+
uiDetailKey: "context.actionDetail.branchCreate",
|
|
3970
|
+
message: tr(lang, "messages", "worktreeRequiredFromMainBranch", {
|
|
3971
|
+
projectGitCwd: f.git.projectGitCwd,
|
|
3972
|
+
issueNumber: f.issueNumber,
|
|
3973
|
+
slug: f.slug
|
|
3974
|
+
})
|
|
3975
|
+
}
|
|
3976
|
+
];
|
|
3977
|
+
}
|
|
3978
|
+
return [
|
|
3979
|
+
{
|
|
3980
|
+
type: "command",
|
|
3981
|
+
category: "branch_create",
|
|
3982
|
+
requiresUserCheck: true,
|
|
3983
|
+
scope: "project",
|
|
3984
|
+
cwd: f.git.projectGitCwd,
|
|
3985
|
+
cmd: tr(lang, "messages", "createBranch", {
|
|
3986
|
+
projectGitCwd: f.git.projectGitCwd,
|
|
3987
|
+
issueNumber: f.issueNumber,
|
|
3988
|
+
slug: f.slug
|
|
3989
|
+
})
|
|
3990
|
+
}
|
|
3991
|
+
];
|
|
3992
|
+
}
|
|
3852
3993
|
if (f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f)) {
|
|
3853
3994
|
if (f.git.docsHasUncommittedChanges) {
|
|
3854
3995
|
return [
|
|
@@ -4204,15 +4345,30 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4204
4345
|
type: "instruction",
|
|
4205
4346
|
category: "pre_pr_review",
|
|
4206
4347
|
requiresUserCheck: true,
|
|
4207
|
-
message:
|
|
4348
|
+
message: getPrePrReviewPrompt(
|
|
4349
|
+
lang,
|
|
4350
|
+
prePrReviewPolicy.skills,
|
|
4351
|
+
prePrReviewPolicy.fallback
|
|
4352
|
+
)
|
|
4208
4353
|
}
|
|
4209
4354
|
];
|
|
4210
4355
|
}
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4356
|
+
if (f.prePrReview.decisionOutcome && f.prePrReview.decisionOutcome !== "approve") {
|
|
4357
|
+
return [
|
|
4358
|
+
{
|
|
4359
|
+
type: "instruction",
|
|
4360
|
+
category: "pre_pr_review",
|
|
4361
|
+
requiresUserCheck: true,
|
|
4362
|
+
message: tr(lang, "messages", "prePrReviewDecisionReconfirm", {
|
|
4363
|
+
decision: f.prePrReview.decisionOutcome,
|
|
4364
|
+
command: buildSelfCliCommand(
|
|
4365
|
+
buildPrePrReviewCommandArgs(f, evidencePath, "approve")
|
|
4366
|
+
)
|
|
4367
|
+
})
|
|
4368
|
+
}
|
|
4369
|
+
];
|
|
4214
4370
|
}
|
|
4215
|
-
commandArgs
|
|
4371
|
+
const commandArgs = buildPrePrReviewCommandArgs(f, evidencePath);
|
|
4216
4372
|
return [
|
|
4217
4373
|
{
|
|
4218
4374
|
type: "command",
|
|
@@ -4575,6 +4731,77 @@ function applyTaskExecutePhaseCheck(action, requiresUserCheck, policy, explicitl
|
|
|
4575
4731
|
if (explicitlyRequired) return requiresUserCheck;
|
|
4576
4732
|
return false;
|
|
4577
4733
|
}
|
|
4734
|
+
function withFeatureScopeSplitOptions(actions, feature, lang) {
|
|
4735
|
+
if (!feature.scopeSplit.suggested) return actions;
|
|
4736
|
+
if (feature.tasks.total === 0 || feature.tasks.done >= feature.tasks.total) {
|
|
4737
|
+
return actions;
|
|
4738
|
+
}
|
|
4739
|
+
if (actions.some((action) => action.category === "feature_scope_split")) {
|
|
4740
|
+
return actions;
|
|
4741
|
+
}
|
|
4742
|
+
const recommendedIssues = feature.scopeSplit.recommendation === "split_4" ? 4 : 2;
|
|
4743
|
+
const recommendationLabel = feature.scopeSplit.recommendation === "split_4" ? "split_4" : feature.scopeSplit.recommendation === "split_2" ? "split_2" : "none";
|
|
4744
|
+
const vars = {
|
|
4745
|
+
taskCount: feature.scopeSplit.taskCount,
|
|
4746
|
+
decisionsLineCount: feature.scopeSplit.decisionsLineCount,
|
|
4747
|
+
taskThreshold: feature.scopeSplit.suggestTaskCountThreshold,
|
|
4748
|
+
decisionsThreshold: feature.scopeSplit.suggestDecisionsLineCountThreshold,
|
|
4749
|
+
recommendFourTaskThreshold: feature.scopeSplit.recommendSplitFourTaskCountThreshold,
|
|
4750
|
+
recommendFourDecisionsThreshold: feature.scopeSplit.recommendSplitFourDecisionsLineCountThreshold,
|
|
4751
|
+
recommendedIssues,
|
|
4752
|
+
recommendationLabel,
|
|
4753
|
+
guideCommand: "npx lee-spec-kit docs get split-feature --json"
|
|
4754
|
+
};
|
|
4755
|
+
return [
|
|
4756
|
+
...actions,
|
|
4757
|
+
{
|
|
4758
|
+
type: "instruction",
|
|
4759
|
+
category: "feature_scope_split",
|
|
4760
|
+
requiresUserCheck: true,
|
|
4761
|
+
uiDetailKey: "context.actionDetail.featureScopeSplitKeep",
|
|
4762
|
+
message: tr(lang, "messages", "featureScopeSplitKeep", vars)
|
|
4763
|
+
},
|
|
4764
|
+
{
|
|
4765
|
+
type: "instruction",
|
|
4766
|
+
category: "feature_scope_split",
|
|
4767
|
+
requiresUserCheck: true,
|
|
4768
|
+
uiDetailKey: "context.actionDetail.featureScopeSplitTwo",
|
|
4769
|
+
message: tr(lang, "messages", "featureScopeSplitTwo", vars)
|
|
4770
|
+
},
|
|
4771
|
+
{
|
|
4772
|
+
type: "instruction",
|
|
4773
|
+
category: "feature_scope_split",
|
|
4774
|
+
requiresUserCheck: true,
|
|
4775
|
+
uiDetailKey: "context.actionDetail.featureScopeSplitFour",
|
|
4776
|
+
message: tr(lang, "messages", "featureScopeSplitFour", vars)
|
|
4777
|
+
}
|
|
4778
|
+
];
|
|
4779
|
+
}
|
|
4780
|
+
function withExistingWorktreeMoveOption(actions, feature, lang) {
|
|
4781
|
+
if (feature.tasks.total === 0 || feature.tasks.done >= feature.tasks.total) {
|
|
4782
|
+
return actions;
|
|
4783
|
+
}
|
|
4784
|
+
if (feature.git.projectInManagedWorktree) return actions;
|
|
4785
|
+
if (!feature.git.onExpectedBranch) return actions;
|
|
4786
|
+
if (!feature.git.expectedWorktreePath) return actions;
|
|
4787
|
+
if (actions.some(
|
|
4788
|
+
(action) => action.category === "branch_create" && action.type === "instruction" && action.message.includes(feature.git.expectedWorktreePath || "")
|
|
4789
|
+
)) {
|
|
4790
|
+
return actions;
|
|
4791
|
+
}
|
|
4792
|
+
return [
|
|
4793
|
+
...actions,
|
|
4794
|
+
{
|
|
4795
|
+
type: "instruction",
|
|
4796
|
+
category: "branch_create",
|
|
4797
|
+
requiresUserCheck: true,
|
|
4798
|
+
uiDetailKey: "context.actionDetail.branchCreate",
|
|
4799
|
+
message: tr(lang, "messages", "moveToExistingWorktree", {
|
|
4800
|
+
worktreePath: feature.git.expectedWorktreePath
|
|
4801
|
+
})
|
|
4802
|
+
}
|
|
4803
|
+
];
|
|
4804
|
+
}
|
|
4578
4805
|
function withUserRequestReplanOption(actions, lang) {
|
|
4579
4806
|
if (actions.some((action) => action.category === "user_request_replan")) {
|
|
4580
4807
|
return actions;
|
|
@@ -4594,8 +4821,18 @@ function resolveFeatureProgress(feature, stepDefinitions, lang, approval) {
|
|
|
4594
4821
|
for (const definition of ordered) {
|
|
4595
4822
|
if (!definition.current) continue;
|
|
4596
4823
|
if (definition.current.when(feature)) {
|
|
4597
|
-
const
|
|
4824
|
+
const actionsWithScopeSplit = withFeatureScopeSplitOptions(
|
|
4598
4825
|
definition.current.actions(feature),
|
|
4826
|
+
feature,
|
|
4827
|
+
lang
|
|
4828
|
+
);
|
|
4829
|
+
const actionsWithWorktreeMove = withExistingWorktreeMoveOption(
|
|
4830
|
+
actionsWithScopeSplit,
|
|
4831
|
+
feature,
|
|
4832
|
+
lang
|
|
4833
|
+
);
|
|
4834
|
+
const currentActions = withUserRequestReplanOption(
|
|
4835
|
+
actionsWithWorktreeMove,
|
|
4599
4836
|
lang
|
|
4600
4837
|
);
|
|
4601
4838
|
const actions = applyApprovalPolicy(
|
|
@@ -4737,9 +4974,22 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
|
|
|
4737
4974
|
}
|
|
4738
4975
|
}
|
|
4739
4976
|
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
4977
|
+
var WORKTREE_MARKER = `${path12.sep}.worktrees${path12.sep}`;
|
|
4740
4978
|
function resetContextGitCaches() {
|
|
4741
4979
|
GIT_WORKTREE_CACHE.clear();
|
|
4742
4980
|
}
|
|
4981
|
+
function isManagedWorktreePath(cwd) {
|
|
4982
|
+
if (!cwd) return false;
|
|
4983
|
+
const normalized = path12.resolve(cwd);
|
|
4984
|
+
return normalized.includes(WORKTREE_MARKER);
|
|
4985
|
+
}
|
|
4986
|
+
function resolveProjectRootFromGitCwd(cwd) {
|
|
4987
|
+
const normalized = path12.resolve(cwd);
|
|
4988
|
+
const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
|
|
4989
|
+
if (markerIndex <= 0) return normalized;
|
|
4990
|
+
const projectRoot = normalized.slice(0, markerIndex);
|
|
4991
|
+
return projectRoot || normalized;
|
|
4992
|
+
}
|
|
4743
4993
|
function getGitTopLevel(ctx, cwd) {
|
|
4744
4994
|
try {
|
|
4745
4995
|
return ctx.cmd.execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
@@ -4843,6 +5093,17 @@ function isExpectedFeatureBranch(branchName, issueNumber, slug, folderName) {
|
|
|
4843
5093
|
const rest = match[1];
|
|
4844
5094
|
return rest === slug || rest === folderName;
|
|
4845
5095
|
}
|
|
5096
|
+
var FEATURE_SCOPE_SPLIT_TASK_THRESHOLD = 40;
|
|
5097
|
+
var FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD = 1200;
|
|
5098
|
+
var FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD = 80;
|
|
5099
|
+
var FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD = 2500;
|
|
5100
|
+
function countDocumentLines(content) {
|
|
5101
|
+
if (!content) return 0;
|
|
5102
|
+
const lines = content.split(/\r?\n/);
|
|
5103
|
+
if (lines.length === 0) return 0;
|
|
5104
|
+
if (lines[lines.length - 1] === "") return lines.length - 1;
|
|
5105
|
+
return lines.length;
|
|
5106
|
+
}
|
|
4846
5107
|
function escapeRegExp(value) {
|
|
4847
5108
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4848
5109
|
}
|
|
@@ -5033,7 +5294,13 @@ function isExplicitZeroFindingsEntry(value) {
|
|
|
5033
5294
|
if (!trimmed) return false;
|
|
5034
5295
|
return /^0+\s*findings?\b/.test(trimmed) || /^no\s+findings?\b/.test(trimmed) || /^findings?\s*[::]\s*0+\b/.test(trimmed) || /^지적\s*사항?\s*[::]?\s*0+\b/.test(trimmed) || /^지적\s*없음\b/.test(trimmed);
|
|
5035
5296
|
}
|
|
5036
|
-
function
|
|
5297
|
+
function isNoCommandsPlaceholder(value) {
|
|
5298
|
+
const trimmed = value.trim().toLowerCase();
|
|
5299
|
+
if (!trimmed) return true;
|
|
5300
|
+
return /^none(?:\s+specified)?\b/.test(trimmed) || /^no\s+commands?\b/.test(trimmed) || /^n\/a\b/.test(trimmed) || /^없음\b/.test(trimmed) || /^명령(?:어)?\s*없음\b/.test(trimmed);
|
|
5301
|
+
}
|
|
5302
|
+
function hasPrePrReviewLogQuality(content, options) {
|
|
5303
|
+
const requireCommandsExecuted = !!options?.requireCommandsExecuted;
|
|
5037
5304
|
const sections = splitReviewLogSections(content, PRE_PR_REVIEW_LOG_HEADER);
|
|
5038
5305
|
for (const section of sections) {
|
|
5039
5306
|
const summaryEntries = collectStructuredReviewEntries(section, [
|
|
@@ -5074,16 +5341,28 @@ function hasPrePrReviewLogQuality(content) {
|
|
|
5074
5341
|
"\uD14C\uC2A4\uD2B8 \uC2E4\uD589"
|
|
5075
5342
|
]);
|
|
5076
5343
|
if (!hasValidReviewLogEntries(testsRunEntries)) continue;
|
|
5344
|
+
if (requireCommandsExecuted) {
|
|
5345
|
+
const commandsEntries = collectStructuredReviewEntries(section, [
|
|
5346
|
+
"Commands Executed",
|
|
5347
|
+
"Executed Commands",
|
|
5348
|
+
"\uC2E4\uD589 \uBA85\uB839\uC5B4",
|
|
5349
|
+
"\uC2E4\uD589 \uBA85\uB839"
|
|
5350
|
+
]);
|
|
5351
|
+
const hasCommandsExecuted = commandsEntries.map((entry) => entry.trim()).some(
|
|
5352
|
+
(entry) => entry.length > 0 && !isReviewDraftPlaceholder(entry) && !isPlaceholderReviewEvidence(entry) && !isNoCommandsPlaceholder(entry)
|
|
5353
|
+
);
|
|
5354
|
+
if (!hasCommandsExecuted) continue;
|
|
5355
|
+
}
|
|
5077
5356
|
return true;
|
|
5078
5357
|
}
|
|
5079
5358
|
return false;
|
|
5080
5359
|
}
|
|
5081
5360
|
var PRE_PR_REVIEW_LOG_HEADER = /^##\s+(?:Pre-PR Review Log|PR 전 리뷰 로그)\b.*$/gim;
|
|
5082
5361
|
var PR_REVIEW_LOG_HEADER = /^##\s+(?:PR Review Log|PR 리뷰 로그)\b.*$/gim;
|
|
5083
|
-
async function hasPrePrReviewLogEvidence(ctx, candidatePath) {
|
|
5362
|
+
async function hasPrePrReviewLogEvidence(ctx, candidatePath, options) {
|
|
5084
5363
|
try {
|
|
5085
5364
|
const content = await ctx.fs.readFile(candidatePath, "utf-8");
|
|
5086
|
-
return hasPrePrReviewLogQuality(content);
|
|
5365
|
+
return hasPrePrReviewLogQuality(content, options);
|
|
5087
5366
|
} catch {
|
|
5088
5367
|
return false;
|
|
5089
5368
|
}
|
|
@@ -5130,12 +5409,19 @@ async function isPrePrEvidenceProvided(ctx, rawValue, policy, context) {
|
|
|
5130
5409
|
rawValue,
|
|
5131
5410
|
context
|
|
5132
5411
|
);
|
|
5412
|
+
if (policy.enforceExecutionEvidence && !existingEvidencePath) {
|
|
5413
|
+
return false;
|
|
5414
|
+
}
|
|
5133
5415
|
if (policy.evidenceMode !== "path_required") {
|
|
5134
5416
|
if (!existingEvidencePath) return true;
|
|
5135
|
-
return hasPrePrReviewLogEvidence(ctx, existingEvidencePath
|
|
5417
|
+
return hasPrePrReviewLogEvidence(ctx, existingEvidencePath, {
|
|
5418
|
+
requireCommandsExecuted: policy.enforceExecutionEvidence
|
|
5419
|
+
});
|
|
5136
5420
|
}
|
|
5137
5421
|
if (!existingEvidencePath) return false;
|
|
5138
|
-
return hasPrePrReviewLogEvidence(ctx, existingEvidencePath
|
|
5422
|
+
return hasPrePrReviewLogEvidence(ctx, existingEvidencePath, {
|
|
5423
|
+
requireCommandsExecuted: policy.enforceExecutionEvidence
|
|
5424
|
+
});
|
|
5139
5425
|
}
|
|
5140
5426
|
async function isPrReviewEvidenceProvided(ctx, rawValue, context) {
|
|
5141
5427
|
if (isPlaceholderReviewEvidence(rawValue)) return false;
|
|
@@ -5203,6 +5489,31 @@ function resetContextParseCaches() {
|
|
|
5203
5489
|
PR_REMOTE_STATUS_CACHE.clear();
|
|
5204
5490
|
FEATURE_WORKTREE_CACHE.clear();
|
|
5205
5491
|
}
|
|
5492
|
+
function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderName) {
|
|
5493
|
+
const projectRoot = resolveProjectRootFromGitCwd(projectGitCwd);
|
|
5494
|
+
const names = [`feat-${issueNumber}-${slug}`, `feat-${issueNumber}-${folderName}`];
|
|
5495
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5496
|
+
const out = [];
|
|
5497
|
+
for (const name of names) {
|
|
5498
|
+
const candidate = path12.resolve(projectRoot, ".worktrees", name);
|
|
5499
|
+
if (seen.has(candidate)) continue;
|
|
5500
|
+
seen.add(candidate);
|
|
5501
|
+
out.push(candidate);
|
|
5502
|
+
}
|
|
5503
|
+
return out;
|
|
5504
|
+
}
|
|
5505
|
+
function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, folderName) {
|
|
5506
|
+
for (const candidate of getExpectedWorktreeCandidates(
|
|
5507
|
+
projectGitCwd,
|
|
5508
|
+
issueNumber,
|
|
5509
|
+
slug,
|
|
5510
|
+
folderName
|
|
5511
|
+
)) {
|
|
5512
|
+
if (!fs9.existsSync(candidate)) continue;
|
|
5513
|
+
return candidate;
|
|
5514
|
+
}
|
|
5515
|
+
return void 0;
|
|
5516
|
+
}
|
|
5206
5517
|
function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folderName) {
|
|
5207
5518
|
const expectedBranches = [
|
|
5208
5519
|
`feat/${issueNumber}-${slug}`,
|
|
@@ -5221,6 +5532,21 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
|
|
|
5221
5532
|
branch: getCurrentBranch(ctx, foundPath) || branchName
|
|
5222
5533
|
};
|
|
5223
5534
|
}
|
|
5535
|
+
const expectedBranchesSet = new Set(expectedBranches);
|
|
5536
|
+
for (const candidate of getExpectedWorktreeCandidates(
|
|
5537
|
+
projectGitCwd,
|
|
5538
|
+
issueNumber,
|
|
5539
|
+
slug,
|
|
5540
|
+
folderName
|
|
5541
|
+
)) {
|
|
5542
|
+
if (!fs9.existsSync(candidate)) continue;
|
|
5543
|
+
const branchName = getCurrentBranch(ctx, candidate);
|
|
5544
|
+
if (!expectedBranchesSet.has(branchName)) continue;
|
|
5545
|
+
return {
|
|
5546
|
+
cwd: candidate,
|
|
5547
|
+
branch: branchName
|
|
5548
|
+
};
|
|
5549
|
+
}
|
|
5224
5550
|
return void 0;
|
|
5225
5551
|
}
|
|
5226
5552
|
function toUpperToken(value) {
|
|
@@ -5466,6 +5792,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5466
5792
|
const specPath = path12.join(featurePath, "spec.md");
|
|
5467
5793
|
const planPath = path12.join(featurePath, "plan.md");
|
|
5468
5794
|
const tasksPath = path12.join(featurePath, "tasks.md");
|
|
5795
|
+
const decisionsPath = path12.join(featurePath, "decisions.md");
|
|
5469
5796
|
const issueDocPath = path12.join(featurePath, "issue.md");
|
|
5470
5797
|
const prDocPath = path12.join(featurePath, "pr.md");
|
|
5471
5798
|
let specStatus;
|
|
@@ -5486,25 +5813,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5486
5813
|
let effectiveProjectBranch = context.projectBranch;
|
|
5487
5814
|
let effectiveProjectBranchAvailable = context.projectBranchAvailable;
|
|
5488
5815
|
if (effectiveProjectGitCwd && issueNumber) {
|
|
5489
|
-
const
|
|
5490
|
-
|
|
5816
|
+
const worktree = resolveFeatureWorktreePath(
|
|
5817
|
+
ctx,
|
|
5818
|
+
effectiveProjectGitCwd,
|
|
5491
5819
|
issueNumber,
|
|
5492
5820
|
slug,
|
|
5493
5821
|
folderName
|
|
5494
5822
|
);
|
|
5495
|
-
if (
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
issueNumber,
|
|
5500
|
-
slug,
|
|
5501
|
-
folderName
|
|
5502
|
-
);
|
|
5503
|
-
if (worktree) {
|
|
5504
|
-
effectiveProjectGitCwd = worktree.cwd;
|
|
5505
|
-
effectiveProjectBranch = worktree.branch;
|
|
5506
|
-
effectiveProjectBranchAvailable = true;
|
|
5507
|
-
}
|
|
5823
|
+
if (worktree) {
|
|
5824
|
+
effectiveProjectGitCwd = worktree.cwd;
|
|
5825
|
+
effectiveProjectBranch = worktree.branch;
|
|
5826
|
+
effectiveProjectBranchAvailable = true;
|
|
5508
5827
|
}
|
|
5509
5828
|
}
|
|
5510
5829
|
let planStatus;
|
|
@@ -5514,6 +5833,12 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5514
5833
|
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
5515
5834
|
planStatus = parseDocStatus(statusValue);
|
|
5516
5835
|
}
|
|
5836
|
+
const decisionsExists = await ctx.fs.pathExists(decisionsPath);
|
|
5837
|
+
let decisionsLineCount = 0;
|
|
5838
|
+
if (decisionsExists) {
|
|
5839
|
+
const content = await ctx.fs.readFile(decisionsPath, "utf-8");
|
|
5840
|
+
decisionsLineCount = countDocumentLines(content);
|
|
5841
|
+
}
|
|
5517
5842
|
const tasksExists = await ctx.fs.pathExists(tasksPath);
|
|
5518
5843
|
const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
|
|
5519
5844
|
let activeTask;
|
|
@@ -5650,25 +5975,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5650
5975
|
prReviewDecisionProvided = !isPlaceholderReviewEvidence(prReviewDecisionValue) && hasStructuredReviewDecision(prReviewDecisionValue);
|
|
5651
5976
|
}
|
|
5652
5977
|
if (effectiveProjectGitCwd && issueNumber) {
|
|
5653
|
-
const
|
|
5654
|
-
|
|
5978
|
+
const worktree = resolveFeatureWorktreePath(
|
|
5979
|
+
ctx,
|
|
5980
|
+
effectiveProjectGitCwd,
|
|
5655
5981
|
issueNumber,
|
|
5656
5982
|
slug,
|
|
5657
5983
|
folderName
|
|
5658
5984
|
);
|
|
5659
|
-
if (
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
issueNumber,
|
|
5664
|
-
slug,
|
|
5665
|
-
folderName
|
|
5666
|
-
);
|
|
5667
|
-
if (worktree) {
|
|
5668
|
-
effectiveProjectGitCwd = worktree.cwd;
|
|
5669
|
-
effectiveProjectBranch = worktree.branch;
|
|
5670
|
-
effectiveProjectBranchAvailable = true;
|
|
5671
|
-
}
|
|
5985
|
+
if (worktree) {
|
|
5986
|
+
effectiveProjectGitCwd = worktree.cwd;
|
|
5987
|
+
effectiveProjectBranch = worktree.branch;
|
|
5988
|
+
effectiveProjectBranchAvailable = true;
|
|
5672
5989
|
}
|
|
5673
5990
|
}
|
|
5674
5991
|
const issueDocExists = await ctx.fs.pathExists(issueDocPath);
|
|
@@ -5701,6 +6018,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5701
6018
|
if (workflowPolicy.requireMerge && prStatus === "Review" && prLink && effectiveProjectGitCwd) {
|
|
5702
6019
|
prRemote = resolvePrRemoteStatus(ctx, prLink, effectiveProjectGitCwd) || void 0;
|
|
5703
6020
|
}
|
|
6021
|
+
const scopeSplitReasons = [];
|
|
6022
|
+
if (tasksSummary.total >= FEATURE_SCOPE_SPLIT_TASK_THRESHOLD) {
|
|
6023
|
+
scopeSplitReasons.push("task_count");
|
|
6024
|
+
}
|
|
6025
|
+
if (decisionsLineCount >= FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD) {
|
|
6026
|
+
scopeSplitReasons.push("decisions_lines");
|
|
6027
|
+
}
|
|
6028
|
+
const scopeSplitSuggested = scopeSplitReasons.length > 0;
|
|
6029
|
+
const scopeSplitRecommendFour = tasksSummary.total >= FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD || decisionsLineCount >= FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD;
|
|
6030
|
+
const scopeSplitRecommendation = !scopeSplitSuggested ? "none" : scopeSplitRecommendFour ? "split_4" : "split_2";
|
|
5704
6031
|
const warnings = [];
|
|
5705
6032
|
if (effectiveProjectBranchAvailable === false) {
|
|
5706
6033
|
warnings.push(tr(lang, "warnings", "projectBranchUnavailable"));
|
|
@@ -5711,6 +6038,18 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5711
6038
|
slug,
|
|
5712
6039
|
folderName
|
|
5713
6040
|
);
|
|
6041
|
+
const projectInManagedWorktree = isManagedWorktreePath(effectiveProjectGitCwd);
|
|
6042
|
+
const expectedWorktreePath = effectiveProjectGitCwd && issueNumber ? resolveExistingExpectedWorktreePath(
|
|
6043
|
+
effectiveProjectGitCwd,
|
|
6044
|
+
issueNumber,
|
|
6045
|
+
slug,
|
|
6046
|
+
folderName
|
|
6047
|
+
) : void 0;
|
|
6048
|
+
if (workflowPolicy.requireBranch && tasksSummary.total > tasksSummary.done && onExpectedBranch && !projectInManagedWorktree) {
|
|
6049
|
+
warnings.push(tr(lang, "warnings", "projectExpectedBranchOnMainWorkspace"));
|
|
6050
|
+
} else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
|
|
6051
|
+
warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
|
|
6052
|
+
}
|
|
5714
6053
|
const relativeFeaturePathFromDocs = path12.relative(
|
|
5715
6054
|
context.docsDir,
|
|
5716
6055
|
featurePath
|
|
@@ -5835,6 +6174,19 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5835
6174
|
if (tasksExists && !tasksDocStatusFieldExists) {
|
|
5836
6175
|
warnings.push(tr(lang, "warnings", "legacyTasksDocStatusField"));
|
|
5837
6176
|
}
|
|
6177
|
+
if (scopeSplitSuggested && tasksSummary.total > tasksSummary.done) {
|
|
6178
|
+
warnings.push(
|
|
6179
|
+
tr(lang, "warnings", "featureScopeSplitSuggested", {
|
|
6180
|
+
taskCount: tasksSummary.total,
|
|
6181
|
+
decisionsLineCount,
|
|
6182
|
+
taskThreshold: FEATURE_SCOPE_SPLIT_TASK_THRESHOLD,
|
|
6183
|
+
decisionsThreshold: FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD,
|
|
6184
|
+
recommendFourTaskThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD,
|
|
6185
|
+
recommendFourDecisionsThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD,
|
|
6186
|
+
recommendedIssues: scopeSplitRecommendation === "split_4" ? 4 : 2
|
|
6187
|
+
})
|
|
6188
|
+
);
|
|
6189
|
+
}
|
|
5838
6190
|
if (docsEverCommitted && docsHasUncommittedChanges) {
|
|
5839
6191
|
warnings.push(tr(lang, "warnings", "docsUncommittedChanges"));
|
|
5840
6192
|
}
|
|
@@ -5946,6 +6298,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5946
6298
|
planStatus,
|
|
5947
6299
|
tasksDocStatus,
|
|
5948
6300
|
tasks: tasksSummary,
|
|
6301
|
+
scopeSplit: {
|
|
6302
|
+
suggested: scopeSplitSuggested,
|
|
6303
|
+
reasons: scopeSplitReasons,
|
|
6304
|
+
recommendation: scopeSplitRecommendation,
|
|
6305
|
+
taskCount: tasksSummary.total,
|
|
6306
|
+
decisionsLineCount,
|
|
6307
|
+
suggestTaskCountThreshold: FEATURE_SCOPE_SPLIT_TASK_THRESHOLD,
|
|
6308
|
+
suggestDecisionsLineCountThreshold: FEATURE_SCOPE_SPLIT_DECISIONS_LINE_THRESHOLD,
|
|
6309
|
+
recommendSplitFourTaskCountThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_TASK_THRESHOLD,
|
|
6310
|
+
recommendSplitFourDecisionsLineCountThreshold: FEATURE_SCOPE_SPLIT_RECOMMEND_FOUR_DECISIONS_LINE_THRESHOLD
|
|
6311
|
+
},
|
|
5949
6312
|
activeTask,
|
|
5950
6313
|
lastDoneTask,
|
|
5951
6314
|
nextTodoTask,
|
|
@@ -5972,6 +6335,8 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5972
6335
|
docsGitCwd: context.docsGitCwd,
|
|
5973
6336
|
projectGitCwd: effectiveProjectGitCwd,
|
|
5974
6337
|
onExpectedBranch,
|
|
6338
|
+
projectInManagedWorktree,
|
|
6339
|
+
expectedWorktreePath,
|
|
5975
6340
|
docsEverCommitted,
|
|
5976
6341
|
docsHasUncommittedChanges,
|
|
5977
6342
|
projectHasUncommittedChanges,
|
|
@@ -6545,6 +6910,16 @@ function normalizeSkillList2(raw) {
|
|
|
6545
6910
|
}
|
|
6546
6911
|
return [...deduped];
|
|
6547
6912
|
}
|
|
6913
|
+
function normalizeStringList(raw) {
|
|
6914
|
+
if (!Array.isArray(raw)) return [];
|
|
6915
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
6916
|
+
for (const item of raw) {
|
|
6917
|
+
const value = String(item || "").trim();
|
|
6918
|
+
if (!value) continue;
|
|
6919
|
+
deduped.add(value);
|
|
6920
|
+
}
|
|
6921
|
+
return [...deduped];
|
|
6922
|
+
}
|
|
6548
6923
|
function normalizeDecisionEnumList2(raw) {
|
|
6549
6924
|
if (!Array.isArray(raw)) return [];
|
|
6550
6925
|
const deduped = /* @__PURE__ */ new Set();
|
|
@@ -6578,6 +6953,7 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
6578
6953
|
}
|
|
6579
6954
|
const workflow = raw.workflow;
|
|
6580
6955
|
setIfMissing(workflow, "mode", "github", "workflow.mode");
|
|
6956
|
+
setIfMissing(workflow, "requireWorktree", false, "workflow.requireWorktree");
|
|
6581
6957
|
setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
|
|
6582
6958
|
setIfMissing(workflow, "taskCommitGate", "warn", "workflow.taskCommitGate");
|
|
6583
6959
|
if (!isPlainObject(workflow.auto)) {
|
|
@@ -6632,6 +7008,24 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
6632
7008
|
changedPaths.push("workflow.prePrReview.decisionEnum");
|
|
6633
7009
|
}
|
|
6634
7010
|
}
|
|
7011
|
+
setIfMissing(
|
|
7012
|
+
prePrReview,
|
|
7013
|
+
"enforceExecutionEvidence",
|
|
7014
|
+
true,
|
|
7015
|
+
"workflow.prePrReview.enforceExecutionEvidence"
|
|
7016
|
+
);
|
|
7017
|
+
if (prePrReview.executionCommandPrefixes === void 0) {
|
|
7018
|
+
prePrReview.executionCommandPrefixes = [];
|
|
7019
|
+
changedPaths.push("workflow.prePrReview.executionCommandPrefixes");
|
|
7020
|
+
} else {
|
|
7021
|
+
const normalizedExecutionCommandPrefixes = normalizeStringList(
|
|
7022
|
+
prePrReview.executionCommandPrefixes
|
|
7023
|
+
);
|
|
7024
|
+
if (JSON.stringify(normalizedExecutionCommandPrefixes) !== JSON.stringify(prePrReview.executionCommandPrefixes)) {
|
|
7025
|
+
prePrReview.executionCommandPrefixes = normalizedExecutionCommandPrefixes;
|
|
7026
|
+
changedPaths.push("workflow.prePrReview.executionCommandPrefixes");
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
6635
7029
|
if (!isPlainObject(raw.pr)) {
|
|
6636
7030
|
raw.pr = {};
|
|
6637
7031
|
changedPaths.push("pr");
|
|
@@ -6966,12 +7360,18 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
6966
7360
|
id: "create-pr",
|
|
6967
7361
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
6968
7362
|
relativePath: (_, lang) => path12.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
7363
|
+
},
|
|
7364
|
+
{
|
|
7365
|
+
id: "split-feature",
|
|
7366
|
+
title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
|
|
7367
|
+
relativePath: (_, lang) => path12.join(lang, "common", "agents", "skills", "split-feature.md")
|
|
6969
7368
|
}
|
|
6970
7369
|
];
|
|
6971
7370
|
var DOC_FOLLOWUPS = {
|
|
6972
7371
|
agents: [
|
|
6973
7372
|
"create-feature",
|
|
6974
7373
|
"execute-task",
|
|
7374
|
+
"split-feature",
|
|
6975
7375
|
"git-workflow",
|
|
6976
7376
|
"create-issue",
|
|
6977
7377
|
"issue-doc",
|
|
@@ -6982,9 +7382,10 @@ var DOC_FOLLOWUPS = {
|
|
|
6982
7382
|
"issue-doc": [],
|
|
6983
7383
|
"pr-doc": [],
|
|
6984
7384
|
"create-feature": ["execute-task"],
|
|
6985
|
-
"execute-task": ["git-workflow"],
|
|
7385
|
+
"execute-task": ["git-workflow", "split-feature"],
|
|
6986
7386
|
"create-issue": ["issue-doc"],
|
|
6987
|
-
"create-pr": ["pr-doc"]
|
|
7387
|
+
"create-pr": ["pr-doc"],
|
|
7388
|
+
"split-feature": []
|
|
6988
7389
|
};
|
|
6989
7390
|
var CATEGORY_DOC_MAP = {
|
|
6990
7391
|
spec_write: ["agents"],
|
|
@@ -7002,6 +7403,7 @@ var CATEGORY_DOC_MAP = {
|
|
|
7002
7403
|
pr_create: ["create-pr", "pr-doc", "git-workflow"],
|
|
7003
7404
|
pr_status_update: ["create-pr"],
|
|
7004
7405
|
code_review: ["create-pr"],
|
|
7406
|
+
feature_scope_split: ["split-feature", "execute-task"],
|
|
7005
7407
|
worktree_cleanup: ["git-workflow"],
|
|
7006
7408
|
user_request_replan: ["agents", "execute-task"]
|
|
7007
7409
|
};
|
|
@@ -7019,6 +7421,9 @@ function normalizeBuiltinDocId(input) {
|
|
|
7019
7421
|
if (normalized === "execute-task") return "execute-task";
|
|
7020
7422
|
if (normalized === "create-issue") return "create-issue";
|
|
7021
7423
|
if (normalized === "create-pr") return "create-pr";
|
|
7424
|
+
if (normalized === "split-feature" || normalized === "feature-split") {
|
|
7425
|
+
return "split-feature";
|
|
7426
|
+
}
|
|
7022
7427
|
if (normalized === "agents") return "agents";
|
|
7023
7428
|
return null;
|
|
7024
7429
|
}
|
|
@@ -7107,6 +7512,7 @@ var ACTION_DETAIL_KEY_BY_CATEGORY = {
|
|
|
7107
7512
|
pr_create: "context.actionDetail.prCreate",
|
|
7108
7513
|
pr_status_update: "context.actionDetail.prStatusUpdate",
|
|
7109
7514
|
code_review: "context.actionDetail.codeReview",
|
|
7515
|
+
feature_scope_split: "context.actionDetail.featureScopeSplit",
|
|
7110
7516
|
worktree_cleanup: "context.actionDetail.worktreeCleanup",
|
|
7111
7517
|
pr_metadata_migrate: "context.actionDetail.prMetadataMigrate",
|
|
7112
7518
|
user_request_replan: "context.actionDetail.userRequestReplan",
|
|
@@ -13877,6 +14283,22 @@ function normalizeCommandsExecuted(value) {
|
|
|
13877
14283
|
if (!Array.isArray(value)) return [];
|
|
13878
14284
|
return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
13879
14285
|
}
|
|
14286
|
+
function normalizeGitPath3(value) {
|
|
14287
|
+
return value.trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
14288
|
+
}
|
|
14289
|
+
function parseGitPathList(stdout) {
|
|
14290
|
+
return stdout.split("\n").map((entry) => normalizeGitPath3(entry)).filter(Boolean);
|
|
14291
|
+
}
|
|
14292
|
+
function uniquePaths(values) {
|
|
14293
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14294
|
+
const out = [];
|
|
14295
|
+
for (const value of values) {
|
|
14296
|
+
if (!value || seen.has(value)) continue;
|
|
14297
|
+
seen.add(value);
|
|
14298
|
+
out.push(value);
|
|
14299
|
+
}
|
|
14300
|
+
return out;
|
|
14301
|
+
}
|
|
13880
14302
|
function normalizeEvidenceFiles(value) {
|
|
13881
14303
|
if (!Array.isArray(value)) {
|
|
13882
14304
|
throw createCliError(
|
|
@@ -13920,6 +14342,10 @@ var PrePrReviewValidator = class {
|
|
|
13920
14342
|
this.ctx = ctx;
|
|
13921
14343
|
}
|
|
13922
14344
|
async validateEvidence(evidencePath, projectRoot) {
|
|
14345
|
+
const result = await this.validateEvidenceWithScope(evidencePath, projectRoot);
|
|
14346
|
+
return result.evidence;
|
|
14347
|
+
}
|
|
14348
|
+
async validateEvidenceWithScope(evidencePath, projectRoot) {
|
|
13923
14349
|
const fullPath = path12.resolve(evidencePath);
|
|
13924
14350
|
if (!await fs.pathExists(fullPath)) {
|
|
13925
14351
|
throw createCliError(
|
|
@@ -13958,14 +14384,18 @@ var PrePrReviewValidator = class {
|
|
|
13958
14384
|
);
|
|
13959
14385
|
}
|
|
13960
14386
|
}
|
|
13961
|
-
const
|
|
14387
|
+
const scope = await this.collectReviewScope(projectRoot);
|
|
14388
|
+
const changedFiles = uniquePaths([
|
|
14389
|
+
...scope.mainChangedFiles,
|
|
14390
|
+
...scope.worktreeChangedFiles
|
|
14391
|
+
]);
|
|
13962
14392
|
const reviewedFiles = new Set(
|
|
13963
14393
|
normalizedEvidence.files.map(
|
|
13964
14394
|
(f) => path12.relative(
|
|
13965
14395
|
projectRoot,
|
|
13966
14396
|
path12.resolve(projectRoot, f.path)
|
|
13967
14397
|
)
|
|
13968
|
-
)
|
|
14398
|
+
).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
|
|
13969
14399
|
);
|
|
13970
14400
|
const missingFiles = changedFiles.filter((f) => !reviewedFiles.has(f));
|
|
13971
14401
|
if (missingFiles.length > 0) {
|
|
@@ -13975,40 +14405,120 @@ var PrePrReviewValidator = class {
|
|
|
13975
14405
|
${missingFiles.map((f) => `- ${f}`).join("\n")}`
|
|
13976
14406
|
);
|
|
13977
14407
|
}
|
|
13978
|
-
return
|
|
14408
|
+
return {
|
|
14409
|
+
evidence: normalizedEvidence,
|
|
14410
|
+
scope
|
|
14411
|
+
};
|
|
13979
14412
|
}
|
|
13980
|
-
async
|
|
13981
|
-
const
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
14413
|
+
async collectReviewScope(cwd) {
|
|
14414
|
+
const baseRef = await this.resolveBaseRef(cwd);
|
|
14415
|
+
const mergeBase = await this.resolveMergeBase(cwd, baseRef);
|
|
14416
|
+
const mainDiff = await this.getMainChangedFiles(cwd, mergeBase);
|
|
14417
|
+
const worktreeDiff = await this.getWorktreeChangedFiles(cwd);
|
|
14418
|
+
if (!mainDiff.ok && !worktreeDiff.ok) {
|
|
14419
|
+
throw createCliError(
|
|
14420
|
+
"VALIDATION_FAILED",
|
|
14421
|
+
"Unable to determine changed files from git diff. Ensure this is a git repository with accessible history."
|
|
14422
|
+
);
|
|
13985
14423
|
}
|
|
13986
|
-
|
|
14424
|
+
return {
|
|
14425
|
+
baseRef,
|
|
14426
|
+
mergeBase,
|
|
14427
|
+
mainRange: mainDiff.rangeLabel,
|
|
14428
|
+
mainChangedFiles: mainDiff.files,
|
|
14429
|
+
worktreeChangedFiles: worktreeDiff.files
|
|
14430
|
+
};
|
|
14431
|
+
}
|
|
14432
|
+
async resolveBaseRef(cwd) {
|
|
13987
14433
|
try {
|
|
13988
|
-
const
|
|
14434
|
+
const result = await this.ctx.cmd.runAsync(
|
|
13989
14435
|
"git",
|
|
13990
|
-
["
|
|
14436
|
+
["rev-parse", "--abbrev-ref", "origin/HEAD"],
|
|
13991
14437
|
{ cwd }
|
|
13992
14438
|
);
|
|
13993
|
-
if (
|
|
13994
|
-
|
|
14439
|
+
if (result.code === 0) {
|
|
14440
|
+
const value = result.stdout.trim();
|
|
14441
|
+
if (value && value !== "origin/HEAD") {
|
|
14442
|
+
return value;
|
|
14443
|
+
}
|
|
13995
14444
|
}
|
|
13996
14445
|
} catch {
|
|
13997
14446
|
}
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14447
|
+
return "origin/main";
|
|
14448
|
+
}
|
|
14449
|
+
async resolveMergeBase(cwd, baseRef) {
|
|
14450
|
+
const candidates = uniquePaths([
|
|
14451
|
+
baseRef,
|
|
14452
|
+
baseRef.replace(/^origin\//, ""),
|
|
14453
|
+
"origin/main",
|
|
14454
|
+
"main"
|
|
14455
|
+
]);
|
|
14456
|
+
for (const candidate of candidates) {
|
|
14457
|
+
if (!candidate) continue;
|
|
14458
|
+
try {
|
|
14459
|
+
const result = await this.ctx.cmd.runAsync(
|
|
14460
|
+
"git",
|
|
14461
|
+
["merge-base", "HEAD", candidate],
|
|
14462
|
+
{ cwd }
|
|
14463
|
+
);
|
|
14464
|
+
if (result.code !== 0) continue;
|
|
14465
|
+
const mergeBase = normalizeGitPath3(result.stdout);
|
|
14466
|
+
if (mergeBase) return mergeBase;
|
|
14467
|
+
} catch {
|
|
14468
|
+
}
|
|
14007
14469
|
}
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14470
|
+
return null;
|
|
14471
|
+
}
|
|
14472
|
+
async getMainChangedFiles(cwd, mergeBase) {
|
|
14473
|
+
const ranges = uniquePaths([
|
|
14474
|
+
mergeBase ? `${mergeBase}..HEAD` : "",
|
|
14475
|
+
"HEAD~1..HEAD",
|
|
14476
|
+
"HEAD^..HEAD"
|
|
14477
|
+
]);
|
|
14478
|
+
for (const range of ranges) {
|
|
14479
|
+
if (!range) continue;
|
|
14480
|
+
try {
|
|
14481
|
+
const result = await this.ctx.cmd.runAsync(
|
|
14482
|
+
"git",
|
|
14483
|
+
["diff", "--name-only", range],
|
|
14484
|
+
{ cwd }
|
|
14485
|
+
);
|
|
14486
|
+
if (result.code !== 0) continue;
|
|
14487
|
+
return {
|
|
14488
|
+
ok: true,
|
|
14489
|
+
rangeLabel: range,
|
|
14490
|
+
files: uniquePaths(parseGitPathList(result.stdout))
|
|
14491
|
+
};
|
|
14492
|
+
} catch {
|
|
14493
|
+
}
|
|
14494
|
+
}
|
|
14495
|
+
return {
|
|
14496
|
+
ok: false,
|
|
14497
|
+
rangeLabel: mergeBase ? `${mergeBase}..HEAD` : "HEAD~1..HEAD",
|
|
14498
|
+
files: []
|
|
14499
|
+
};
|
|
14500
|
+
}
|
|
14501
|
+
async getWorktreeChangedFiles(cwd) {
|
|
14502
|
+
let hasSuccessfulCommand = false;
|
|
14503
|
+
const files = [];
|
|
14504
|
+
const commands = [
|
|
14505
|
+
["diff", "--name-only"],
|
|
14506
|
+
["diff", "--name-only", "--cached"],
|
|
14507
|
+
["ls-files", "--others", "--exclude-standard"]
|
|
14508
|
+
];
|
|
14509
|
+
for (const args of commands) {
|
|
14510
|
+
try {
|
|
14511
|
+
const result = await this.ctx.cmd.runAsync("git", args, { cwd });
|
|
14512
|
+
if (result.code !== 0) continue;
|
|
14513
|
+
hasSuccessfulCommand = true;
|
|
14514
|
+
files.push(...parseGitPathList(result.stdout));
|
|
14515
|
+
} catch {
|
|
14516
|
+
}
|
|
14517
|
+
}
|
|
14518
|
+
return {
|
|
14519
|
+
ok: hasSuccessfulCommand,
|
|
14520
|
+
files: uniquePaths(files)
|
|
14521
|
+
};
|
|
14012
14522
|
}
|
|
14013
14523
|
};
|
|
14014
14524
|
|
|
@@ -14075,6 +14585,9 @@ function upsertSpecLine(content, keys, preferredKey, value, anchorKeys) {
|
|
|
14075
14585
|
function normalizePathForDoc(value) {
|
|
14076
14586
|
return value.replace(/\\/g, "/");
|
|
14077
14587
|
}
|
|
14588
|
+
function normalizeShellLikeCommand(value) {
|
|
14589
|
+
return value.trim().replace(/\s+/g, " ");
|
|
14590
|
+
}
|
|
14078
14591
|
function getPreferredKeys(lang) {
|
|
14079
14592
|
if (lang === "ko") {
|
|
14080
14593
|
return {
|
|
@@ -14106,6 +14619,8 @@ function buildReportContent(input) {
|
|
|
14106
14619
|
- Maintainability: ${f.review.maintainability}`;
|
|
14107
14620
|
}).join("\n");
|
|
14108
14621
|
}
|
|
14622
|
+
const mainScopeFiles = input.scope.mainChangedFiles.length > 0 ? input.scope.mainChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
|
|
14623
|
+
const worktreeScopeFiles = input.scope.worktreeChangedFiles.length > 0 ? input.scope.worktreeChangedFiles.map((entry) => ` - ${entry}`).join("\n") : " - (none)";
|
|
14109
14624
|
return `## Pre-PR Review Log (${input.date})
|
|
14110
14625
|
|
|
14111
14626
|
- **Feature**: ${input.folderName}
|
|
@@ -14119,12 +14634,30 @@ ${commandsRun}
|
|
|
14119
14634
|
- **Residual Risks**:
|
|
14120
14635
|
- ${input.evidence.residualRisks}
|
|
14121
14636
|
|
|
14637
|
+
- **Review Scope**:
|
|
14638
|
+
- **Main Base Ref**: ${input.scope.baseRef}
|
|
14639
|
+
- **Main Merge Base**: ${input.scope.mergeBase ?? "unresolved"}
|
|
14640
|
+
- **Main Range**: ${input.scope.mainRange}
|
|
14641
|
+
- **Main Changed Files**:
|
|
14642
|
+
${mainScopeFiles}
|
|
14643
|
+
- **Worktree Changed Files**:
|
|
14644
|
+
${worktreeScopeFiles}
|
|
14645
|
+
|
|
14122
14646
|
- **Findings (Changed Files)**:
|
|
14123
14647
|
${filesSection}
|
|
14124
14648
|
|
|
14125
14649
|
- **Trace**: pre-pr-review command executed and synced with tasks.md
|
|
14126
14650
|
`;
|
|
14127
14651
|
}
|
|
14652
|
+
function createFallbackReviewScope() {
|
|
14653
|
+
return {
|
|
14654
|
+
baseRef: "origin/main",
|
|
14655
|
+
mergeBase: null,
|
|
14656
|
+
mainRange: "HEAD~1..HEAD",
|
|
14657
|
+
mainChangedFiles: [],
|
|
14658
|
+
worktreeChangedFiles: []
|
|
14659
|
+
};
|
|
14660
|
+
}
|
|
14128
14661
|
function appendDecisionLog(content, entry) {
|
|
14129
14662
|
const normalized = content.trimEnd();
|
|
14130
14663
|
if (!normalized) return `${entry.trim()}
|
|
@@ -14207,6 +14740,12 @@ async function runPrePrReview(featureName, options) {
|
|
|
14207
14740
|
"`--decision` must be one of: approve, changes_requested, blocked."
|
|
14208
14741
|
);
|
|
14209
14742
|
}
|
|
14743
|
+
if (!explicitDecision && feature.prePrReview.decisionOutcome && feature.prePrReview.decisionOutcome !== "approve") {
|
|
14744
|
+
throw createCliError(
|
|
14745
|
+
"INVALID_ARGUMENT",
|
|
14746
|
+
`Existing Pre-PR decision is "${feature.prePrReview.decisionOutcome}". Re-run with explicit --decision to avoid replaying the previous non-approve decision.`
|
|
14747
|
+
);
|
|
14748
|
+
}
|
|
14210
14749
|
const decision = explicitDecision || feature.prePrReview.decisionOutcome || "approve";
|
|
14211
14750
|
if (!policy.decisionEnum.includes(decision)) {
|
|
14212
14751
|
throw createCliError(
|
|
@@ -14216,12 +14755,21 @@ async function runPrePrReview(featureName, options) {
|
|
|
14216
14755
|
}
|
|
14217
14756
|
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
14757
|
let evidenceObj;
|
|
14758
|
+
let reviewScope = createFallbackReviewScope();
|
|
14759
|
+
if (policy.enforceExecutionEvidence && !options.evidence) {
|
|
14760
|
+
throw createCliError(
|
|
14761
|
+
"INVALID_ARGUMENT",
|
|
14762
|
+
"`--evidence <path>` is required when workflow.prePrReview.enforceExecutionEvidence=true."
|
|
14763
|
+
);
|
|
14764
|
+
}
|
|
14219
14765
|
if (options.evidence) {
|
|
14220
14766
|
const validator = new PrePrReviewValidator(ctx);
|
|
14221
|
-
|
|
14767
|
+
const validationResult = await validator.validateEvidenceWithScope(
|
|
14222
14768
|
options.evidence,
|
|
14223
14769
|
process.cwd()
|
|
14224
14770
|
);
|
|
14771
|
+
evidenceObj = validationResult.evidence;
|
|
14772
|
+
reviewScope = validationResult.scope;
|
|
14225
14773
|
} else if (policy.evidenceMode === "path_required") {
|
|
14226
14774
|
throw createCliError(
|
|
14227
14775
|
"INVALID_ARGUMENT",
|
|
@@ -14229,6 +14777,34 @@ async function runPrePrReview(featureName, options) {
|
|
|
14229
14777
|
);
|
|
14230
14778
|
} else {
|
|
14231
14779
|
evidenceObj = DEFAULT_EVIDENCE_FOR_ANY_MODE;
|
|
14780
|
+
const validator = new PrePrReviewValidator(ctx);
|
|
14781
|
+
try {
|
|
14782
|
+
reviewScope = await validator.collectReviewScope(process.cwd());
|
|
14783
|
+
} catch {
|
|
14784
|
+
reviewScope = createFallbackReviewScope();
|
|
14785
|
+
}
|
|
14786
|
+
}
|
|
14787
|
+
if (policy.enforceExecutionEvidence) {
|
|
14788
|
+
const normalizedCommands = evidenceObj.commandsExecuted.map((entry) => normalizeShellLikeCommand(entry)).filter(Boolean);
|
|
14789
|
+
if (normalizedCommands.length === 0) {
|
|
14790
|
+
throw createCliError(
|
|
14791
|
+
"VALIDATION_FAILED",
|
|
14792
|
+
"Evidence must include non-empty commandsExecuted entries when workflow.prePrReview.enforceExecutionEvidence=true."
|
|
14793
|
+
);
|
|
14794
|
+
}
|
|
14795
|
+
if (policy.executionCommandPrefixes.length > 0) {
|
|
14796
|
+
const hasMatchedPrefix = normalizedCommands.some(
|
|
14797
|
+
(cmd) => policy.executionCommandPrefixes.some(
|
|
14798
|
+
(prefix) => cmd.toLowerCase().startsWith(normalizeShellLikeCommand(prefix).toLowerCase())
|
|
14799
|
+
)
|
|
14800
|
+
);
|
|
14801
|
+
if (!hasMatchedPrefix) {
|
|
14802
|
+
throw createCliError(
|
|
14803
|
+
"VALIDATION_FAILED",
|
|
14804
|
+
`Evidence commandsExecuted must include at least one command starting with workflow.prePrReview.executionCommandPrefixes: ${policy.executionCommandPrefixes.join(", ")}`
|
|
14805
|
+
);
|
|
14806
|
+
}
|
|
14807
|
+
}
|
|
14232
14808
|
}
|
|
14233
14809
|
const decisionsPath = path12.join(feature.path, "decisions.md");
|
|
14234
14810
|
const decisionLogEntry = buildReportContent({
|
|
@@ -14238,7 +14814,8 @@ async function runPrePrReview(featureName, options) {
|
|
|
14238
14814
|
note,
|
|
14239
14815
|
fallback: policy.fallback,
|
|
14240
14816
|
skills: policy.skills,
|
|
14241
|
-
evidence: evidenceObj
|
|
14817
|
+
evidence: evidenceObj,
|
|
14818
|
+
scope: reviewScope
|
|
14242
14819
|
});
|
|
14243
14820
|
const decisionsContent = await fs.pathExists(decisionsPath) ? await fs.readFile(decisionsPath, "utf-8") : "";
|
|
14244
14821
|
const nextDecisions = appendDecisionLog(decisionsContent, decisionLogEntry);
|
|
@@ -14285,6 +14862,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
14285
14862
|
decisionsPath: normalizePathForDoc(decisionsPath),
|
|
14286
14863
|
evidencePath,
|
|
14287
14864
|
decision,
|
|
14865
|
+
reviewScope,
|
|
14288
14866
|
tasksUpdated: nextTasks !== tasksContent
|
|
14289
14867
|
},
|
|
14290
14868
|
null,
|