lee-spec-kit 0.6.34 → 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 +360 -49
- 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/ko/common/agents/skills/create-pr.md +6 -5
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);
|
|
@@ -599,6 +599,8 @@ var koMessages = {
|
|
|
599
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)",
|
|
600
600
|
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
601
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.",
|
|
602
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',
|
|
603
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.',
|
|
604
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.",
|
|
@@ -613,9 +615,10 @@ var koMessages = {
|
|
|
613
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",
|
|
614
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)",
|
|
615
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)",
|
|
616
|
-
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)",
|
|
617
|
-
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)",
|
|
618
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)",
|
|
619
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)",
|
|
620
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)",
|
|
621
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)",
|
|
@@ -651,6 +654,8 @@ var koMessages = {
|
|
|
651
654
|
// src/utils/locales/ko/warnings.ts
|
|
652
655
|
var koWarnings = {
|
|
653
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.",
|
|
654
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)",
|
|
655
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.)",
|
|
656
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.",
|
|
@@ -1136,6 +1141,8 @@ var enMessages = {
|
|
|
1136
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).",
|
|
1137
1142
|
standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
|
|
1138
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.",
|
|
1139
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',
|
|
1140
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.',
|
|
1141
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.",
|
|
@@ -1150,9 +1157,10 @@ var enMessages = {
|
|
|
1150
1157
|
taskCommitGateReasonMismatchLastDone: "The latest project code commit does not match the last completed task",
|
|
1151
1158
|
prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
|
|
1152
1159
|
prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
|
|
1153
|
-
prePrReviewRun: "Run the code review agent
|
|
1154
|
-
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)",
|
|
1155
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)",
|
|
1156
1164
|
prReviewEvidenceFieldMissing: "tasks.md is missing the `PR Review Evidence` field. Add `- **PR Review Evidence**: -` and continue. (CHECK required)",
|
|
1157
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)",
|
|
1158
1166
|
prReviewDecisionFieldMissing: "tasks.md is missing the `PR Review Decision` field. Add `- **PR Review Decision**: -` and continue. (CHECK required)",
|
|
@@ -1188,6 +1196,8 @@ var enMessages = {
|
|
|
1188
1196
|
// src/utils/locales/en/warnings.ts
|
|
1189
1197
|
var enWarnings = {
|
|
1190
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.",
|
|
1191
1201
|
docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
|
|
1192
1202
|
docsPathIgnored: "Current feature docs path is ignored by git: {path} (docs commit detection may be limited).",
|
|
1193
1203
|
docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Check commit message rules against the git-workflow guide.",
|
|
@@ -2460,6 +2470,7 @@ async function runInit(options) {
|
|
|
2460
2470
|
docsRepo,
|
|
2461
2471
|
workflow: {
|
|
2462
2472
|
mode: workflowMode,
|
|
2473
|
+
requireWorktree: false,
|
|
2463
2474
|
codeDirtyScope: "auto",
|
|
2464
2475
|
taskCommitGate: "warn",
|
|
2465
2476
|
auto: {
|
|
@@ -2469,7 +2480,9 @@ async function runInit(options) {
|
|
|
2469
2480
|
skills: ["code-review-excellence"],
|
|
2470
2481
|
fallback: "builtin-checklist",
|
|
2471
2482
|
evidenceMode: "path_required",
|
|
2472
|
-
decisionEnum: ["approve", "changes_requested", "blocked"]
|
|
2483
|
+
decisionEnum: ["approve", "changes_requested", "blocked"],
|
|
2484
|
+
enforceExecutionEvidence: true,
|
|
2485
|
+
executionCommandPrefixes: []
|
|
2473
2486
|
}
|
|
2474
2487
|
},
|
|
2475
2488
|
pr: {
|
|
@@ -3199,6 +3212,7 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3199
3212
|
mode,
|
|
3200
3213
|
requireIssue: false,
|
|
3201
3214
|
requireBranch: false,
|
|
3215
|
+
requireWorktree: false,
|
|
3202
3216
|
requirePr: false,
|
|
3203
3217
|
requireReview: false,
|
|
3204
3218
|
requireMerge: false
|
|
@@ -3206,6 +3220,7 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3206
3220
|
mode,
|
|
3207
3221
|
requireIssue: true,
|
|
3208
3222
|
requireBranch: true,
|
|
3223
|
+
requireWorktree: false,
|
|
3209
3224
|
requirePr: true,
|
|
3210
3225
|
requireReview: true,
|
|
3211
3226
|
requireMerge: true
|
|
@@ -3216,6 +3231,9 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3216
3231
|
if (typeof workflow?.requireBranch === "boolean") {
|
|
3217
3232
|
policy.requireBranch = workflow.requireBranch;
|
|
3218
3233
|
}
|
|
3234
|
+
if (typeof workflow?.requireWorktree === "boolean") {
|
|
3235
|
+
policy.requireWorktree = workflow.requireWorktree;
|
|
3236
|
+
}
|
|
3219
3237
|
if (typeof workflow?.requirePr === "boolean") {
|
|
3220
3238
|
policy.requirePr = workflow.requirePr;
|
|
3221
3239
|
}
|
|
@@ -3228,6 +3246,9 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3228
3246
|
if (!policy.requireIssue) {
|
|
3229
3247
|
policy.requireBranch = false;
|
|
3230
3248
|
}
|
|
3249
|
+
if (!policy.requireBranch) {
|
|
3250
|
+
policy.requireWorktree = false;
|
|
3251
|
+
}
|
|
3231
3252
|
if (!policy.requirePr) {
|
|
3232
3253
|
policy.requireReview = false;
|
|
3233
3254
|
policy.requireMerge = false;
|
|
@@ -3284,6 +3305,16 @@ function normalizeDecisionEnumList(input) {
|
|
|
3284
3305
|
}
|
|
3285
3306
|
return [...deduped];
|
|
3286
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
|
+
}
|
|
3287
3318
|
function resolvePrePrReviewPolicy(workflow) {
|
|
3288
3319
|
const workflowPolicy = resolveWorkflowPolicy(workflow);
|
|
3289
3320
|
const configured = workflow?.prePrReview;
|
|
@@ -3291,17 +3322,45 @@ function resolvePrePrReviewPolicy(workflow) {
|
|
|
3291
3322
|
const configuredDecisionEnum = normalizeDecisionEnumList(
|
|
3292
3323
|
configured?.decisionEnum
|
|
3293
3324
|
);
|
|
3325
|
+
const configuredCommandPrefixes = normalizeCommandPrefixList(
|
|
3326
|
+
configured?.executionCommandPrefixes
|
|
3327
|
+
);
|
|
3294
3328
|
const configuredEnabled = typeof configured?.enabled === "boolean" ? configured.enabled : workflowPolicy.requirePr;
|
|
3329
|
+
const configuredExecutionEvidence = typeof configured?.enforceExecutionEvidence === "boolean" ? configured.enforceExecutionEvidence : true;
|
|
3295
3330
|
return {
|
|
3296
3331
|
enabled: workflowPolicy.requirePr ? configuredEnabled : false,
|
|
3297
3332
|
skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
|
|
3298
3333
|
fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
|
|
3299
3334
|
evidenceMode: configured?.evidenceMode === "any" ? "any" : "path_required",
|
|
3300
|
-
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 : []
|
|
3301
3338
|
};
|
|
3302
3339
|
}
|
|
3303
3340
|
|
|
3304
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
|
+
}
|
|
3305
3364
|
function getCodeReviewPrompt(lang) {
|
|
3306
3365
|
if (lang === "ko") {
|
|
3307
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.`;
|
|
@@ -3388,6 +3447,17 @@ function buildSelfCliCommand(args) {
|
|
|
3388
3447
|
const base = [process.execPath, entry, "--no-banner", ...args];
|
|
3389
3448
|
return base.map((arg) => toShellArg(arg)).join(" ");
|
|
3390
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
|
+
}
|
|
3391
3461
|
function resolvePrePrReviewEvidencePath(feature) {
|
|
3392
3462
|
const docsRoot = feature.git.docsGitCwd;
|
|
3393
3463
|
const candidates = [];
|
|
@@ -3409,7 +3479,7 @@ function resolvePrePrReviewEvidencePath(feature) {
|
|
|
3409
3479
|
const abs = path12.resolve(candidate);
|
|
3410
3480
|
if (seen.has(abs)) continue;
|
|
3411
3481
|
seen.add(abs);
|
|
3412
|
-
if (!
|
|
3482
|
+
if (!fs9.existsSync(abs)) continue;
|
|
3413
3483
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
3414
3484
|
const rel = path12.relative(docsRoot, abs).replace(/\\/g, "/");
|
|
3415
3485
|
if (rel && !rel.startsWith("../")) {
|
|
@@ -3866,6 +3936,60 @@ function getStepDefinitions(ctx) {
|
|
|
3866
3936
|
current: {
|
|
3867
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),
|
|
3868
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
|
+
}
|
|
3869
3993
|
if (f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f)) {
|
|
3870
3994
|
if (f.git.docsHasUncommittedChanges) {
|
|
3871
3995
|
return [
|
|
@@ -4221,15 +4345,30 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
4221
4345
|
type: "instruction",
|
|
4222
4346
|
category: "pre_pr_review",
|
|
4223
4347
|
requiresUserCheck: true,
|
|
4224
|
-
message:
|
|
4348
|
+
message: getPrePrReviewPrompt(
|
|
4349
|
+
lang,
|
|
4350
|
+
prePrReviewPolicy.skills,
|
|
4351
|
+
prePrReviewPolicy.fallback
|
|
4352
|
+
)
|
|
4225
4353
|
}
|
|
4226
4354
|
];
|
|
4227
4355
|
}
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
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
|
+
];
|
|
4231
4370
|
}
|
|
4232
|
-
commandArgs
|
|
4371
|
+
const commandArgs = buildPrePrReviewCommandArgs(f, evidencePath);
|
|
4233
4372
|
return [
|
|
4234
4373
|
{
|
|
4235
4374
|
type: "command",
|
|
@@ -4638,6 +4777,31 @@ function withFeatureScopeSplitOptions(actions, feature, lang) {
|
|
|
4638
4777
|
}
|
|
4639
4778
|
];
|
|
4640
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
|
+
}
|
|
4641
4805
|
function withUserRequestReplanOption(actions, lang) {
|
|
4642
4806
|
if (actions.some((action) => action.category === "user_request_replan")) {
|
|
4643
4807
|
return actions;
|
|
@@ -4662,8 +4826,13 @@ function resolveFeatureProgress(feature, stepDefinitions, lang, approval) {
|
|
|
4662
4826
|
feature,
|
|
4663
4827
|
lang
|
|
4664
4828
|
);
|
|
4665
|
-
const
|
|
4829
|
+
const actionsWithWorktreeMove = withExistingWorktreeMoveOption(
|
|
4666
4830
|
actionsWithScopeSplit,
|
|
4831
|
+
feature,
|
|
4832
|
+
lang
|
|
4833
|
+
);
|
|
4834
|
+
const currentActions = withUserRequestReplanOption(
|
|
4835
|
+
actionsWithWorktreeMove,
|
|
4667
4836
|
lang
|
|
4668
4837
|
);
|
|
4669
4838
|
const actions = applyApprovalPolicy(
|
|
@@ -4805,9 +4974,22 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
|
|
|
4805
4974
|
}
|
|
4806
4975
|
}
|
|
4807
4976
|
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
4977
|
+
var WORKTREE_MARKER = `${path12.sep}.worktrees${path12.sep}`;
|
|
4808
4978
|
function resetContextGitCaches() {
|
|
4809
4979
|
GIT_WORKTREE_CACHE.clear();
|
|
4810
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
|
+
}
|
|
4811
4993
|
function getGitTopLevel(ctx, cwd) {
|
|
4812
4994
|
try {
|
|
4813
4995
|
return ctx.cmd.execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
@@ -5112,7 +5294,13 @@ function isExplicitZeroFindingsEntry(value) {
|
|
|
5112
5294
|
if (!trimmed) return false;
|
|
5113
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);
|
|
5114
5296
|
}
|
|
5115
|
-
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;
|
|
5116
5304
|
const sections = splitReviewLogSections(content, PRE_PR_REVIEW_LOG_HEADER);
|
|
5117
5305
|
for (const section of sections) {
|
|
5118
5306
|
const summaryEntries = collectStructuredReviewEntries(section, [
|
|
@@ -5153,16 +5341,28 @@ function hasPrePrReviewLogQuality(content) {
|
|
|
5153
5341
|
"\uD14C\uC2A4\uD2B8 \uC2E4\uD589"
|
|
5154
5342
|
]);
|
|
5155
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
|
+
}
|
|
5156
5356
|
return true;
|
|
5157
5357
|
}
|
|
5158
5358
|
return false;
|
|
5159
5359
|
}
|
|
5160
5360
|
var PRE_PR_REVIEW_LOG_HEADER = /^##\s+(?:Pre-PR Review Log|PR 전 리뷰 로그)\b.*$/gim;
|
|
5161
5361
|
var PR_REVIEW_LOG_HEADER = /^##\s+(?:PR Review Log|PR 리뷰 로그)\b.*$/gim;
|
|
5162
|
-
async function hasPrePrReviewLogEvidence(ctx, candidatePath) {
|
|
5362
|
+
async function hasPrePrReviewLogEvidence(ctx, candidatePath, options) {
|
|
5163
5363
|
try {
|
|
5164
5364
|
const content = await ctx.fs.readFile(candidatePath, "utf-8");
|
|
5165
|
-
return hasPrePrReviewLogQuality(content);
|
|
5365
|
+
return hasPrePrReviewLogQuality(content, options);
|
|
5166
5366
|
} catch {
|
|
5167
5367
|
return false;
|
|
5168
5368
|
}
|
|
@@ -5209,12 +5409,19 @@ async function isPrePrEvidenceProvided(ctx, rawValue, policy, context) {
|
|
|
5209
5409
|
rawValue,
|
|
5210
5410
|
context
|
|
5211
5411
|
);
|
|
5412
|
+
if (policy.enforceExecutionEvidence && !existingEvidencePath) {
|
|
5413
|
+
return false;
|
|
5414
|
+
}
|
|
5212
5415
|
if (policy.evidenceMode !== "path_required") {
|
|
5213
5416
|
if (!existingEvidencePath) return true;
|
|
5214
|
-
return hasPrePrReviewLogEvidence(ctx, existingEvidencePath
|
|
5417
|
+
return hasPrePrReviewLogEvidence(ctx, existingEvidencePath, {
|
|
5418
|
+
requireCommandsExecuted: policy.enforceExecutionEvidence
|
|
5419
|
+
});
|
|
5215
5420
|
}
|
|
5216
5421
|
if (!existingEvidencePath) return false;
|
|
5217
|
-
return hasPrePrReviewLogEvidence(ctx, existingEvidencePath
|
|
5422
|
+
return hasPrePrReviewLogEvidence(ctx, existingEvidencePath, {
|
|
5423
|
+
requireCommandsExecuted: policy.enforceExecutionEvidence
|
|
5424
|
+
});
|
|
5218
5425
|
}
|
|
5219
5426
|
async function isPrReviewEvidenceProvided(ctx, rawValue, context) {
|
|
5220
5427
|
if (isPlaceholderReviewEvidence(rawValue)) return false;
|
|
@@ -5282,6 +5489,31 @@ function resetContextParseCaches() {
|
|
|
5282
5489
|
PR_REMOTE_STATUS_CACHE.clear();
|
|
5283
5490
|
FEATURE_WORKTREE_CACHE.clear();
|
|
5284
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
|
+
}
|
|
5285
5517
|
function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folderName) {
|
|
5286
5518
|
const expectedBranches = [
|
|
5287
5519
|
`feat/${issueNumber}-${slug}`,
|
|
@@ -5300,6 +5532,21 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
|
|
|
5300
5532
|
branch: getCurrentBranch(ctx, foundPath) || branchName
|
|
5301
5533
|
};
|
|
5302
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
|
+
}
|
|
5303
5550
|
return void 0;
|
|
5304
5551
|
}
|
|
5305
5552
|
function toUpperToken(value) {
|
|
@@ -5566,25 +5813,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5566
5813
|
let effectiveProjectBranch = context.projectBranch;
|
|
5567
5814
|
let effectiveProjectBranchAvailable = context.projectBranchAvailable;
|
|
5568
5815
|
if (effectiveProjectGitCwd && issueNumber) {
|
|
5569
|
-
const
|
|
5570
|
-
|
|
5816
|
+
const worktree = resolveFeatureWorktreePath(
|
|
5817
|
+
ctx,
|
|
5818
|
+
effectiveProjectGitCwd,
|
|
5571
5819
|
issueNumber,
|
|
5572
5820
|
slug,
|
|
5573
5821
|
folderName
|
|
5574
5822
|
);
|
|
5575
|
-
if (
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
issueNumber,
|
|
5580
|
-
slug,
|
|
5581
|
-
folderName
|
|
5582
|
-
);
|
|
5583
|
-
if (worktree) {
|
|
5584
|
-
effectiveProjectGitCwd = worktree.cwd;
|
|
5585
|
-
effectiveProjectBranch = worktree.branch;
|
|
5586
|
-
effectiveProjectBranchAvailable = true;
|
|
5587
|
-
}
|
|
5823
|
+
if (worktree) {
|
|
5824
|
+
effectiveProjectGitCwd = worktree.cwd;
|
|
5825
|
+
effectiveProjectBranch = worktree.branch;
|
|
5826
|
+
effectiveProjectBranchAvailable = true;
|
|
5588
5827
|
}
|
|
5589
5828
|
}
|
|
5590
5829
|
let planStatus;
|
|
@@ -5736,25 +5975,17 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5736
5975
|
prReviewDecisionProvided = !isPlaceholderReviewEvidence(prReviewDecisionValue) && hasStructuredReviewDecision(prReviewDecisionValue);
|
|
5737
5976
|
}
|
|
5738
5977
|
if (effectiveProjectGitCwd && issueNumber) {
|
|
5739
|
-
const
|
|
5740
|
-
|
|
5978
|
+
const worktree = resolveFeatureWorktreePath(
|
|
5979
|
+
ctx,
|
|
5980
|
+
effectiveProjectGitCwd,
|
|
5741
5981
|
issueNumber,
|
|
5742
5982
|
slug,
|
|
5743
5983
|
folderName
|
|
5744
5984
|
);
|
|
5745
|
-
if (
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
issueNumber,
|
|
5750
|
-
slug,
|
|
5751
|
-
folderName
|
|
5752
|
-
);
|
|
5753
|
-
if (worktree) {
|
|
5754
|
-
effectiveProjectGitCwd = worktree.cwd;
|
|
5755
|
-
effectiveProjectBranch = worktree.branch;
|
|
5756
|
-
effectiveProjectBranchAvailable = true;
|
|
5757
|
-
}
|
|
5985
|
+
if (worktree) {
|
|
5986
|
+
effectiveProjectGitCwd = worktree.cwd;
|
|
5987
|
+
effectiveProjectBranch = worktree.branch;
|
|
5988
|
+
effectiveProjectBranchAvailable = true;
|
|
5758
5989
|
}
|
|
5759
5990
|
}
|
|
5760
5991
|
const issueDocExists = await ctx.fs.pathExists(issueDocPath);
|
|
@@ -5807,6 +6038,18 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
5807
6038
|
slug,
|
|
5808
6039
|
folderName
|
|
5809
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
|
+
}
|
|
5810
6053
|
const relativeFeaturePathFromDocs = path12.relative(
|
|
5811
6054
|
context.docsDir,
|
|
5812
6055
|
featurePath
|
|
@@ -6092,6 +6335,8 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6092
6335
|
docsGitCwd: context.docsGitCwd,
|
|
6093
6336
|
projectGitCwd: effectiveProjectGitCwd,
|
|
6094
6337
|
onExpectedBranch,
|
|
6338
|
+
projectInManagedWorktree,
|
|
6339
|
+
expectedWorktreePath,
|
|
6095
6340
|
docsEverCommitted,
|
|
6096
6341
|
docsHasUncommittedChanges,
|
|
6097
6342
|
projectHasUncommittedChanges,
|
|
@@ -6665,6 +6910,16 @@ function normalizeSkillList2(raw) {
|
|
|
6665
6910
|
}
|
|
6666
6911
|
return [...deduped];
|
|
6667
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
|
+
}
|
|
6668
6923
|
function normalizeDecisionEnumList2(raw) {
|
|
6669
6924
|
if (!Array.isArray(raw)) return [];
|
|
6670
6925
|
const deduped = /* @__PURE__ */ new Set();
|
|
@@ -6698,6 +6953,7 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
6698
6953
|
}
|
|
6699
6954
|
const workflow = raw.workflow;
|
|
6700
6955
|
setIfMissing(workflow, "mode", "github", "workflow.mode");
|
|
6956
|
+
setIfMissing(workflow, "requireWorktree", false, "workflow.requireWorktree");
|
|
6701
6957
|
setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
|
|
6702
6958
|
setIfMissing(workflow, "taskCommitGate", "warn", "workflow.taskCommitGate");
|
|
6703
6959
|
if (!isPlainObject(workflow.auto)) {
|
|
@@ -6752,6 +7008,24 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
6752
7008
|
changedPaths.push("workflow.prePrReview.decisionEnum");
|
|
6753
7009
|
}
|
|
6754
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
|
+
}
|
|
6755
7029
|
if (!isPlainObject(raw.pr)) {
|
|
6756
7030
|
raw.pr = {};
|
|
6757
7031
|
changedPaths.push("pr");
|
|
@@ -14311,6 +14585,9 @@ function upsertSpecLine(content, keys, preferredKey, value, anchorKeys) {
|
|
|
14311
14585
|
function normalizePathForDoc(value) {
|
|
14312
14586
|
return value.replace(/\\/g, "/");
|
|
14313
14587
|
}
|
|
14588
|
+
function normalizeShellLikeCommand(value) {
|
|
14589
|
+
return value.trim().replace(/\s+/g, " ");
|
|
14590
|
+
}
|
|
14314
14591
|
function getPreferredKeys(lang) {
|
|
14315
14592
|
if (lang === "ko") {
|
|
14316
14593
|
return {
|
|
@@ -14463,6 +14740,12 @@ async function runPrePrReview(featureName, options) {
|
|
|
14463
14740
|
"`--decision` must be one of: approve, changes_requested, blocked."
|
|
14464
14741
|
);
|
|
14465
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
|
+
}
|
|
14466
14749
|
const decision = explicitDecision || feature.prePrReview.decisionOutcome || "approve";
|
|
14467
14750
|
if (!policy.decisionEnum.includes(decision)) {
|
|
14468
14751
|
throw createCliError(
|
|
@@ -14473,6 +14756,12 @@ async function runPrePrReview(featureName, options) {
|
|
|
14473
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");
|
|
14474
14757
|
let evidenceObj;
|
|
14475
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
|
+
}
|
|
14476
14765
|
if (options.evidence) {
|
|
14477
14766
|
const validator = new PrePrReviewValidator(ctx);
|
|
14478
14767
|
const validationResult = await validator.validateEvidenceWithScope(
|
|
@@ -14495,6 +14784,28 @@ async function runPrePrReview(featureName, options) {
|
|
|
14495
14784
|
reviewScope = createFallbackReviewScope();
|
|
14496
14785
|
}
|
|
14497
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
|
+
}
|
|
14808
|
+
}
|
|
14498
14809
|
const decisionsPath = path12.join(feature.path, "decisions.md");
|
|
14499
14810
|
const decisionLogEntry = buildReportContent({
|
|
14500
14811
|
folderName: feature.folderName,
|