lee-spec-kit 0.6.17 → 0.6.19
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 +3 -3
- package/README.md +4 -4
- package/dist/index.js +509 -202
- package/package.json +8 -3
- package/templates/en/common/agents/git-workflow.md +8 -1
- package/templates/en/common/features/feature-base/issue.md +2 -1
- package/templates/en/common/features/feature-base/plan.md +2 -1
- package/templates/en/common/features/feature-base/pr.md +2 -1
- package/templates/en/common/features/feature-base/spec.md +2 -1
- package/templates/en/common/features/feature-base/tasks.md +4 -2
- package/templates/ko/common/agents/git-workflow.md +8 -1
- package/templates/ko/common/features/feature-base/issue.md +2 -1
- package/templates/ko/common/features/feature-base/plan.md +2 -1
- package/templates/ko/common/features/feature-base/pr.md +2 -1
- package/templates/ko/common/features/feature-base/spec.md +2 -1
- package/templates/ko/common/features/feature-base/tasks.md +4 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import path20 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { program } from 'commander';
|
|
5
5
|
import fs15 from 'fs-extra';
|
|
@@ -11,7 +11,7 @@ import os from 'os';
|
|
|
11
11
|
import { createHash, randomUUID } from 'crypto';
|
|
12
12
|
|
|
13
13
|
var getFilename = () => fileURLToPath(import.meta.url);
|
|
14
|
-
var getDirname = () =>
|
|
14
|
+
var getDirname = () => path20.dirname(getFilename());
|
|
15
15
|
var __dirname$1 = /* @__PURE__ */ getDirname();
|
|
16
16
|
async function copyTemplates(src, dest) {
|
|
17
17
|
await fs15.copy(src, dest, {
|
|
@@ -42,10 +42,10 @@ async function replaceInFiles(dir, replacements) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
45
|
-
var __dirname2 =
|
|
45
|
+
var __dirname2 = path20.dirname(__filename2);
|
|
46
46
|
function getTemplatesDir() {
|
|
47
|
-
const rootDir =
|
|
48
|
-
return
|
|
47
|
+
const rootDir = path20.resolve(__dirname2, "..");
|
|
48
|
+
return path20.join(rootDir, "templates");
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// src/utils/i18n.ts
|
|
@@ -118,7 +118,7 @@ var I18N = {
|
|
|
118
118
|
"doctor.issue.planStatusUnset": "plan.md\uC758 Status(\uC0C1\uD0DC)\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uD15C\uD50C\uB9BF \uADF8\uB300\uB85C\uC77C \uC218 \uC788\uC74C)",
|
|
119
119
|
"doctor.issue.tasksEmpty": "tasks.md\uC5D0 \uD0DC\uC2A4\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
120
120
|
"doctor.issue.tasksDocStatusUnset": "tasks.md\uC758 \uBB38\uC11C \uC0C1\uD0DC(Doc Status)\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (Draft/Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694.)",
|
|
121
|
-
"doctor.issue.tasksDocStatusMissing": "tasks.md\uC5D0 \uBB38\uC11C \uC0C1\uD0DC(Doc Status) \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **\uBB38\uC11C \uC0C1\uD0DC**: Draft | Review | Approved`\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
121
|
+
"doctor.issue.tasksDocStatusMissing": "tasks.md\uC5D0 \uBB38\uC11C \uC0C1\uD0DC(Doc Status) \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `- **\uBB38\uC11C \uC0C1\uD0DC**: -`\uC640 `\uAC12: Draft | Review | Approved`\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
122
122
|
"doctor.issue.duplicateFeatureId": "\uC911\uBCF5 Feature ID \uAC10\uC9C0: {id} ({count}\uAC1C)",
|
|
123
123
|
"doctor.issue.missingFeatureId": "Feature \uD3F4\uB354\uBA85\uC774 F001-... \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. (ID\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC74C)",
|
|
124
124
|
"context.noActiveFeatures": "\u26A0\uFE0F \uC9C4\uD589 \uC911\uC778 Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
@@ -276,7 +276,7 @@ var I18N = {
|
|
|
276
276
|
"github.operationPrMerge": "GitHub PR merge",
|
|
277
277
|
"github.createIssueFailed": "GitHub issue \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
|
|
278
278
|
"github.createPrFailed": "GitHub PR \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
|
|
279
|
-
"github.mergeRequiresPr": "`--merge`\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 `--create
|
|
279
|
+
"github.mergeRequiresPr": "`--merge`\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 `--create`, `--pr <url|number>`, \uB610\uB294 tasks.md\uC758 PR \uB9C1\uD06C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
280
280
|
"github.checkoutBaseAfterMergeFailed": "merge \uD6C4 {base} \uBE0C\uB79C\uCE58 checkout\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
|
|
281
281
|
"github.pullBaseAfterMergeFailed": "merge \uD6C4 {base} \uBE0C\uB79C\uCE58 \uCD5C\uC2E0\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
|
|
282
282
|
"github.issueDefaultTitle": "{slug} ({summary})",
|
|
@@ -433,7 +433,7 @@ var I18N = {
|
|
|
433
433
|
reviewFixCommitIssueGuidance: 'PR \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD574\uACB0\uD55C \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D\uC744 \uC694\uC57D\uD574\uC57C \uD558\uBA70, \uD0DC\uC2A4\uD06C \uC81C\uBAA9\uC744 \uC7AC\uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694. \uC608: `cd "{projectGitCwd}" && (git diff --cached --quiet && echo "\uC2A4\uD14C\uC774\uC9D5\uB41C \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB9AC\uBDF0 \uC218\uC815 \uBC18\uC601 \uD30C\uC77C\uB9CC git add [files] \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694." && exit 1 || git commit -m "fix(#{issueNumber}): <review-fix-summary>")` (`<review-fix-summary>`\uB294 \uC774\uBC88 \uCEE4\uBC0B\uC5D0\uC11C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uD56D\uBAA9\uC73C\uB85C \uC9C1\uC811 \uC791\uC131)',
|
|
434
434
|
reviewFixCommitGuidance: 'PR \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD574\uACB0\uD55C \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D\uC744 \uC694\uC57D\uD574\uC57C \uD558\uBA70, \uD0DC\uC2A4\uD06C \uC81C\uBAA9\uC744 \uC7AC\uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694. \uC608: `cd "{projectGitCwd}" && (git diff --cached --quiet && echo "\uC2A4\uD14C\uC774\uC9D5\uB41C \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB9AC\uBDF0 \uC218\uC815 \uBC18\uC601 \uD30C\uC77C\uB9CC git add [files] \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694." && exit 1 || git commit -m "fix(review): <review-fix-summary>")` (`<review-fix-summary>`\uB294 \uC774\uBC88 \uCEE4\uBC0B\uC5D0\uC11C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uD56D\uBAA9\uC73C\uB85C \uC9C1\uC811 \uC791\uC131)',
|
|
435
435
|
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
436
|
-
createBranch: 'cd "{projectGitCwd}" && git
|
|
436
|
+
createBranch: 'cd "{projectGitCwd}" && mkdir -p .worktrees && (git worktree add ".worktrees/feat-{issueNumber}-{slug}" "feat/{issueNumber}-{slug}" || git worktree add -b "feat/{issueNumber}-{slug}" ".worktrees/feat-{issueNumber}-{slug}") && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
|
|
437
437
|
tasksAllDoneButNoChecklist: '\uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uB97C \uC791\uC131\uD558\uC138\uC694. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC5D0 \uAC80\uC99D \uD56D\uBAA9\uC744 \uCD94\uAC00\uD558\uACE0, \uC0AC\uC6A9\uC790\uC640 \uD655\uC778 \uD6C4 \uCDA9\uC871 \uD56D\uBAA9\uC744 [x]\uB85C \uCCB4\uD06C\uD558\uC138\uC694. \uCD5C\uC885 \uC2B9\uC778(OK)\uB3C4 \uBC18\uC601\uD558\uC138\uC694.',
|
|
438
438
|
tasksAllDoneButChecklist: "\uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uC758 \uB0A8\uC740 \uD56D\uBAA9\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uD604\uC7AC \uC9C4\uD589: ({checked}/{total}) \uC0AC\uC6A9\uC790\uC640 \uD655\uC778 \uD6C4 \uCDA9\uC871 \uD56D\uBAA9\uC744 [x]\uB85C \uCCB4\uD06C\uD558\uACE0 \uCD5C\uC885 \uC2B9\uC778(OK)\uC744 \uBC18\uC601\uD558\uC138\uC694.",
|
|
439
439
|
finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD569\uB2C8\uB2E4: "{title}" ({done}/{total}) \uACB0\uACFC/\uAC80\uC99D \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DONE \uCC98\uB9AC',
|
|
@@ -443,7 +443,7 @@ var I18N = {
|
|
|
443
443
|
taskCommitGateWarnProceed: "\u26A0\uFE0F \uD0DC\uC2A4\uD06C \uCEE4\uBC0B \uB2E8\uC704 \uC810\uAC80 \uACBD\uACE0: {reason}. \uD604\uC7AC\uB294 \uC9C4\uD589 \uAC00\uB2A5\uD558\uC9C0\uB9CC `1 \uD0DC\uC2A4\uD06C = 1 \uCEE4\uBC0B`\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
|
|
444
444
|
taskCommitGateReasonNoTasksCommit: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
445
445
|
taskCommitGateReasonTasksFileUnavailable: "\uCD5C\uADFC \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B \uC774\uB825\uC744 \uD310\uB3C5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
446
|
-
taskCommitGateReasonDoneCount: "\uCD5C\
|
|
446
|
+
taskCommitGateReasonDoneCount: "\uCD5C\uC2E0 tasks.md \uCEE4\uBC0B\uC5D0\uC11C DONE \uC804\uD658\uC774 {count}\uAC74 \uAC10\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
447
447
|
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",
|
|
448
448
|
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)",
|
|
449
449
|
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)",
|
|
@@ -467,16 +467,21 @@ var I18N = {
|
|
|
467
467
|
prCreateExecute: "\uD655\uC815\uB41C PR \uBCF8\uBB38\uC73C\uB85C PR\uC744 \uC0DD\uC131\uD558\uACE0, \uC0DD\uC131\uB41C PR \uB9C1\uD06C\uB97C tasks.md\uC758 PR \uD544\uB4DC\uC5D0 \uAE30\uB85D\uD558\uC138\uC694.",
|
|
468
468
|
prCreateRequiredSequence: "PR \uC0DD\uC131\uC740 \uD544\uC218 2\uB2E8\uACC4\uC785\uB2C8\uB2E4: (1) PR \uBCF8\uBB38 \uD15C\uD50C\uB9BF \uC0DD\uC131/\uBCF4\uC644 + \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK), (2) PR \uC0DD\uC131 + tasks.md PR \uB9C1\uD06C \uAE30\uB85D. \uC704 \uC21C\uC11C\uB97C \uBAA8\uB450 \uC644\uB8CC\uD558\uC138\uC694.",
|
|
469
469
|
prFillStatus: "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC124\uC815\uD558\uC138\uC694. (PR \uC0DD\uC131/\uB9AC\uBDF0 \uB2E8\uACC4\uC5D0\uC11C\uB294 Review\uB97C \uC720\uC9C0\uD569\uB2C8\uB2E4.)",
|
|
470
|
+
prReviewMergedSyncStatus: "\uC6D0\uACA9 PR\uC774 \uC774\uBBF8 \uBA38\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. (PR/\uB9AC\uBDF0 \uADFC\uAC70 \uD544\uB4DC\uB3C4 \uCD5C\uC2E0 \uC0C1\uD0DC\uB85C \uD655\uC778)",
|
|
470
471
|
prResolveReview: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD574\uACB0\uD558\uB294 \uB3D9\uC548 PR \uC0C1\uD0DC\uB294 Review\uB85C \uC720\uC9C0\uD558\uC138\uC694. \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD0DC\uC2A4\uD06C\uBA85\uC774 \uC544\uB2C8\uB77C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D \uC694\uC57D\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694. \uBA38\uC9C0 \uC900\uBE44\uAC00 \uB418\uBA74 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 `npx lee-spec-kit github pr {featureRef} --merge --confirm OK`\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uC131\uACF5 \uC2DC PR \uC0C1\uD0DC\uAC00 Approved\uB85C \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4.)",
|
|
471
472
|
prReviewResolve: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uBA3C\uC800 \uD655\uC778/\uBD84\uC11D\uD55C \uB4A4 \uD544\uC694\uD55C \uC218\uC815 \uC791\uC5C5\uC744 \uC9C4\uD589\uD558\uC138\uC694. \uC9C4\uD589 \uC911\uC5D0\uB294 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC720\uC9C0\uD558\uC138\uC694. \uB9AC\uBDF0 \uC218\uC815 \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB294 \uD0DC\uC2A4\uD06C\uBA85\uC774 \uC544\uB2C8\uB77C \uC2E4\uC81C\uB85C \uD574\uACB0\uD55C \uB9AC\uBDF0 \uC9C0\uC801\uC0AC\uD56D \uC694\uC57D\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694. `PR \uB9AC\uBDF0 Findings/Evidence`\uB97C \uCD5C\uC2E0\uC73C\uB85C \uAE30\uB85D\uD558\uACE0, \uCEE4\uBC0B \uD6C4 \uC6D0\uACA9 \uBC18\uC601(push)\uB3C4 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK)\uC744 \uBC1B\uC740 \uB4A4 \uC9C4\uD589\uD558\uC138\uC694.",
|
|
472
|
-
prReviewPush: '
|
|
473
|
+
prReviewPush: 'cd "{projectGitCwd}" && git push',
|
|
473
474
|
prReviewRemoteBlocked: "\uC6D0\uACA9 PR \uC0C1\uD0DC\uB97C \uD655\uC778\uD55C \uACB0\uACFC \uC544\uC9C1 \uBA38\uC9C0 \uC900\uBE44\uAC00 \uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: {reasons}. \uB9AC\uBDF0 \uCF54\uBA58\uD2B8/\uCCB4\uD06C \uC0C1\uD0DC\uB97C \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.",
|
|
474
475
|
prReviewRemoteReasonChangesRequested: "\uB9AC\uBDF0 \uC2B9\uC778 \uC0C1\uD0DC\uAC00 \uBCC0\uACBD \uC694\uCCAD \uB610\uB294 \uCD94\uAC00 \uB9AC\uBDF0 \uD544\uC694 \uC0C1\uD0DC\uC785\uB2C8\uB2E4",
|
|
476
|
+
prReviewRemoteReasonClosed: "PR\uC774 \uBA38\uC9C0\uB418\uC9C0 \uC54A\uC740 \uCC44 \uB2EB\uD600 \uC788\uC2B5\uB2C8\uB2E4 (reopen \uB610\uB294 \uC0C8 PR \uD544\uC694)",
|
|
475
477
|
prReviewRemoteReasonChecksFailing: "\uC2E4\uD328\uD55C \uCCB4\uD06C\uAC00 {count}\uAC74 \uC788\uC2B5\uB2C8\uB2E4",
|
|
476
478
|
prReviewRemoteReasonChecksPending: "\uB300\uAE30 \uC911\uC778 \uCCB4\uD06C\uAC00 {count}\uAC74 \uC788\uC2B5\uB2C8\uB2E4",
|
|
477
479
|
prReviewRemoteReasonMergeBlocked: "\uBA38\uC9C0 \uC0C1\uD0DC\uAC00 `{status}`\uB85C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4",
|
|
480
|
+
prReviewRemoteReasonUnavailable: "\uC6D0\uACA9 PR \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (gh \uC778\uC99D/\uB124\uD2B8\uC6CC\uD06C/\uAD8C\uD55C \uD655\uC778 \uD544\uC694)",
|
|
478
481
|
prReviewMerge: "\uBA38\uC9C0 \uC900\uBE44\uAC00 \uB418\uBA74 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 `npx lee-spec-kit github pr {featureRef} --merge --confirm OK`\uB97C \uC2E4\uD589\uD558\uC138\uC694. (\uC131\uACF5 \uC2DC PR \uC0C1\uD0DC\uAC00 Approved\uB85C \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4.)",
|
|
482
|
+
prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
|
|
479
483
|
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.",
|
|
484
|
+
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.",
|
|
480
485
|
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.",
|
|
481
486
|
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."
|
|
482
487
|
},
|
|
@@ -570,7 +575,7 @@ var I18N = {
|
|
|
570
575
|
"doctor.issue.planStatusUnset": "plan.md Status is not set. (May still be a template)",
|
|
571
576
|
"doctor.issue.tasksEmpty": "tasks.md has no tasks.",
|
|
572
577
|
"doctor.issue.tasksDocStatusUnset": "tasks.md Doc Status is not set. (Set it to Draft, Review, or Approved.)",
|
|
573
|
-
"doctor.issue.tasksDocStatusMissing": "tasks.md is missing the Doc Status field. Add `- **Doc Status**: Draft | Review | Approved`.",
|
|
578
|
+
"doctor.issue.tasksDocStatusMissing": "tasks.md is missing the Doc Status field. Add `- **Doc Status**: -` and `Values: Draft | Review | Approved`.",
|
|
574
579
|
"doctor.issue.duplicateFeatureId": "Duplicate Feature ID detected: {id} ({count})",
|
|
575
580
|
"doctor.issue.missingFeatureId": "Feature folder name is not in F001-... format. (Cannot extract ID)",
|
|
576
581
|
"context.noActiveFeatures": "\u26A0\uFE0F No active features found.",
|
|
@@ -728,7 +733,7 @@ var I18N = {
|
|
|
728
733
|
"github.operationPrMerge": "GitHub PR merge",
|
|
729
734
|
"github.createIssueFailed": "Failed to create GitHub issue",
|
|
730
735
|
"github.createPrFailed": "Failed to create GitHub PR",
|
|
731
|
-
"github.mergeRequiresPr": "`--merge` requires `--create
|
|
736
|
+
"github.mergeRequiresPr": "`--merge` requires `--create`, `--pr <url|number>`, or a PR link in tasks.md.",
|
|
732
737
|
"github.checkoutBaseAfterMergeFailed": "Failed to checkout {base} after merge",
|
|
733
738
|
"github.pullBaseAfterMergeFailed": "Failed to update {base} after merge",
|
|
734
739
|
"github.issueDefaultTitle": "{slug} ({summary})",
|
|
@@ -885,7 +890,7 @@ var I18N = {
|
|
|
885
890
|
reviewFixCommitIssueGuidance: 'Commit PR review fixes. The commit message must summarize review comments resolved in this commit, not reuse a task title. Example: `cd "{projectGitCwd}" && (git diff --cached --quiet && echo "No staged files. Stage only files that implement review fixes with git add [files], then run again." && exit 1 || git commit -m "fix(#{issueNumber}): <review-fix-summary>")` (replace `<review-fix-summary>` with what this commit actually resolves).',
|
|
886
891
|
reviewFixCommitGuidance: 'Commit PR review fixes. The commit message must summarize review comments resolved in this commit, not reuse a task title. Example: `cd "{projectGitCwd}" && (git diff --cached --quiet && echo "No staged files. Stage only files that implement review fixes with git add [files], then run again." && exit 1 || git commit -m "fix(review): <review-fix-summary>")` (replace `<review-fix-summary>` with what this commit actually resolves).',
|
|
887
892
|
standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
|
|
888
|
-
createBranch: 'cd "{projectGitCwd}" && git
|
|
893
|
+
createBranch: 'cd "{projectGitCwd}" && mkdir -p .worktrees && (git worktree add ".worktrees/feat-{issueNumber}-{slug}" "feat/{issueNumber}-{slug}" || git worktree add -b "feat/{issueNumber}-{slug}" ".worktrees/feat-{issueNumber}-{slug}") && echo "worktree: {projectGitCwd}/.worktrees/feat-{issueNumber}-{slug}"',
|
|
889
894
|
tasksAllDoneButNoChecklist: 'Create the completion checklist. Add verification items to the tasks.md "Completion Criteria" section, then mark satisfied items as [x] after user confirmation. Record final approval (OK) as well.',
|
|
890
895
|
tasksAllDoneButChecklist: "Proceed with remaining completion checklist items. Current progress: ({checked}/{total}) Mark items as [x] only after user confirmation and real verification. Record final approval (OK) as well.",
|
|
891
896
|
finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) Share outcome/verification + get OK before marking DONE',
|
|
@@ -895,7 +900,7 @@ var I18N = {
|
|
|
895
900
|
taskCommitGateWarnProceed: "\u26A0\uFE0F Task commit boundary warning: {reason}. You may continue, but `1 task = 1 commit` is recommended.",
|
|
896
901
|
taskCommitGateReasonNoTasksCommit: "No recent project code commit was found",
|
|
897
902
|
taskCommitGateReasonTasksFileUnavailable: "Cannot read recent project code commit history",
|
|
898
|
-
taskCommitGateReasonDoneCount: "
|
|
903
|
+
taskCommitGateReasonDoneCount: "DONE transitions detected in the latest tasks.md commit ({count})",
|
|
899
904
|
taskCommitGateReasonMismatchLastDone: "The latest project code commit does not match the last completed task",
|
|
900
905
|
prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
|
|
901
906
|
prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
|
|
@@ -919,16 +924,21 @@ var I18N = {
|
|
|
919
924
|
prCreateExecute: "Create the PR with the finalized body, then record the created PR link in tasks.md.",
|
|
920
925
|
prCreateRequiredSequence: "PR creation is a required 2-step sequence: (1) generate/refine PR body template + explicit user OK, (2) create PR + record PR link in tasks.md. Complete both in order.",
|
|
921
926
|
prFillStatus: "Set PR Status in tasks.md to Review. (Keep Review during PR creation/review stages.)",
|
|
927
|
+
prReviewMergedSyncStatus: "The remote PR is already merged. Update PR Status in tasks.md to Approved. (Also verify PR/review evidence fields are up to date.)",
|
|
922
928
|
prResolveReview: "Keep PR Status as Review while addressing comments. For review-fix commits, use commit messages that summarize resolved review feedback (not task titles). Once ready to merge, get explicit user OK and run `npx lee-spec-kit github pr {featureRef} --merge --confirm OK`. (On success, PR Status is synced to Approved.)",
|
|
923
929
|
prReviewResolve: "First review/analyze the PR comments, then apply the required fixes. Keep PR Status as Review while addressing comments. For review-fix commits, use commit messages that summarize resolved review feedback (not task titles). Keep `PR Review Findings/Evidence` updated, then run push only after explicit user OK.",
|
|
924
|
-
prReviewPush: '
|
|
930
|
+
prReviewPush: 'cd "{projectGitCwd}" && git push',
|
|
925
931
|
prReviewRemoteBlocked: "Remote PR checks indicate this PR is not ready to merge yet: {reasons}. Resolve review comments/check statuses, then re-check.",
|
|
926
932
|
prReviewRemoteReasonChangesRequested: "review decision is changes requested or additional review required",
|
|
933
|
+
prReviewRemoteReasonClosed: "PR is closed without merge (reopen or create a new PR)",
|
|
927
934
|
prReviewRemoteReasonChecksFailing: "{count} failing check(s) detected",
|
|
928
935
|
prReviewRemoteReasonChecksPending: "{count} pending check(s) detected",
|
|
929
936
|
prReviewRemoteReasonMergeBlocked: "merge state is blocked (`{status}`)",
|
|
937
|
+
prReviewRemoteReasonUnavailable: "remote PR status could not be verified (check gh auth/network/permissions)",
|
|
930
938
|
prReviewMerge: "When ready to merge, get explicit user OK and run `npx lee-spec-kit github pr {featureRef} --merge --confirm OK`. (On success, PR Status is synced to Approved.)",
|
|
939
|
+
prReviewMergeCommand: "npx lee-spec-kit github pr {featureRef} --merge --confirm OK",
|
|
931
940
|
prRequestReview: "Request review and set/keep PR Status as Review.",
|
|
941
|
+
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.",
|
|
932
942
|
featureDone: "Workflow requirements and all tasks/completion criteria are satisfied. This feature is done.",
|
|
933
943
|
fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
|
|
934
944
|
},
|
|
@@ -1349,10 +1359,10 @@ function sleep(ms) {
|
|
|
1349
1359
|
return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
|
|
1350
1360
|
}
|
|
1351
1361
|
function toScopeKey(value) {
|
|
1352
|
-
return createHash("sha1").update(
|
|
1362
|
+
return createHash("sha1").update(path20.resolve(value)).digest("hex").slice(0, 16);
|
|
1353
1363
|
}
|
|
1354
1364
|
function getTempRuntimeDir(scopePath) {
|
|
1355
|
-
return
|
|
1365
|
+
return path20.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
|
|
1356
1366
|
}
|
|
1357
1367
|
function resolveGitRuntimeDir(cwd) {
|
|
1358
1368
|
try {
|
|
@@ -1366,38 +1376,38 @@ function resolveGitRuntimeDir(cwd) {
|
|
|
1366
1376
|
}
|
|
1367
1377
|
).trim();
|
|
1368
1378
|
if (!out) return null;
|
|
1369
|
-
return
|
|
1379
|
+
return path20.isAbsolute(out) ? out : path20.resolve(cwd, out);
|
|
1370
1380
|
} catch {
|
|
1371
1381
|
return null;
|
|
1372
1382
|
}
|
|
1373
1383
|
}
|
|
1374
1384
|
function getRuntimeStateDir(cwd) {
|
|
1375
|
-
const resolved =
|
|
1385
|
+
const resolved = path20.resolve(cwd);
|
|
1376
1386
|
return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
|
|
1377
1387
|
}
|
|
1378
1388
|
function getDocsLockPath(docsDir) {
|
|
1379
|
-
return
|
|
1389
|
+
return path20.join(
|
|
1380
1390
|
getRuntimeStateDir(docsDir),
|
|
1381
1391
|
"locks",
|
|
1382
1392
|
`docs-${toScopeKey(docsDir)}.lock`
|
|
1383
1393
|
);
|
|
1384
1394
|
}
|
|
1385
1395
|
function getInitLockPath(targetDir) {
|
|
1386
|
-
return
|
|
1387
|
-
getRuntimeStateDir(
|
|
1396
|
+
return path20.join(
|
|
1397
|
+
getRuntimeStateDir(path20.dirname(path20.resolve(targetDir))),
|
|
1388
1398
|
"locks",
|
|
1389
1399
|
`init-${toScopeKey(targetDir)}.lock`
|
|
1390
1400
|
);
|
|
1391
1401
|
}
|
|
1392
1402
|
function getApprovalTicketStorePath(docsDir) {
|
|
1393
|
-
return
|
|
1403
|
+
return path20.join(
|
|
1394
1404
|
getRuntimeStateDir(docsDir),
|
|
1395
1405
|
"tickets",
|
|
1396
1406
|
`approval-${toScopeKey(docsDir)}.json`
|
|
1397
1407
|
);
|
|
1398
1408
|
}
|
|
1399
1409
|
function getProjectExecutionLockPath(cwd) {
|
|
1400
|
-
return
|
|
1410
|
+
return path20.join(getRuntimeStateDir(cwd), "locks", "project.lock");
|
|
1401
1411
|
}
|
|
1402
1412
|
async function isStaleLock(lockPath, staleMs) {
|
|
1403
1413
|
try {
|
|
@@ -1438,7 +1448,7 @@ function isProcessAlive(pid) {
|
|
|
1438
1448
|
}
|
|
1439
1449
|
}
|
|
1440
1450
|
async function tryAcquire(lockPath, owner) {
|
|
1441
|
-
await fs15.ensureDir(
|
|
1451
|
+
await fs15.ensureDir(path20.dirname(lockPath));
|
|
1442
1452
|
try {
|
|
1443
1453
|
const fd = await fs15.open(lockPath, "wx");
|
|
1444
1454
|
const payload = JSON.stringify(
|
|
@@ -1515,30 +1525,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
|
|
|
1515
1525
|
"pr-template.md"
|
|
1516
1526
|
];
|
|
1517
1527
|
var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
|
|
1518
|
-
var ENGINE_MANAGED_FEATURE_PATH =
|
|
1528
|
+
var ENGINE_MANAGED_FEATURE_PATH = path20.join(
|
|
1519
1529
|
"features",
|
|
1520
1530
|
"feature-base"
|
|
1521
1531
|
);
|
|
1522
1532
|
async function pruneEngineManagedDocs(docsDir) {
|
|
1523
1533
|
const removed = [];
|
|
1524
1534
|
for (const file of ENGINE_MANAGED_AGENT_FILES) {
|
|
1525
|
-
const target =
|
|
1535
|
+
const target = path20.join(docsDir, "agents", file);
|
|
1526
1536
|
if (await fs15.pathExists(target)) {
|
|
1527
1537
|
await fs15.remove(target);
|
|
1528
|
-
removed.push(
|
|
1538
|
+
removed.push(path20.relative(docsDir, target));
|
|
1529
1539
|
}
|
|
1530
1540
|
}
|
|
1531
1541
|
for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
|
|
1532
|
-
const target =
|
|
1542
|
+
const target = path20.join(docsDir, "agents", dir);
|
|
1533
1543
|
if (await fs15.pathExists(target)) {
|
|
1534
1544
|
await fs15.remove(target);
|
|
1535
|
-
removed.push(
|
|
1545
|
+
removed.push(path20.relative(docsDir, target));
|
|
1536
1546
|
}
|
|
1537
1547
|
}
|
|
1538
|
-
const featureBasePath =
|
|
1548
|
+
const featureBasePath = path20.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
|
|
1539
1549
|
if (await fs15.pathExists(featureBasePath)) {
|
|
1540
1550
|
await fs15.remove(featureBasePath);
|
|
1541
|
-
removed.push(
|
|
1551
|
+
removed.push(path20.relative(docsDir, featureBasePath));
|
|
1542
1552
|
}
|
|
1543
1553
|
return removed;
|
|
1544
1554
|
}
|
|
@@ -1679,7 +1689,7 @@ ${tr(lang2, "cli", "common.canceled")}`)
|
|
|
1679
1689
|
}
|
|
1680
1690
|
async function runInit(options) {
|
|
1681
1691
|
const cwd = process.cwd();
|
|
1682
|
-
const defaultName =
|
|
1692
|
+
const defaultName = path20.basename(cwd);
|
|
1683
1693
|
let projectName = options.name || defaultName;
|
|
1684
1694
|
let projectType = options.type;
|
|
1685
1695
|
let components = parseComponentsOption(options.components);
|
|
@@ -1692,7 +1702,7 @@ async function runInit(options) {
|
|
|
1692
1702
|
const componentProjectRoots = parseComponentProjectRootsOption(
|
|
1693
1703
|
options.componentProjectRoots
|
|
1694
1704
|
);
|
|
1695
|
-
const targetDir =
|
|
1705
|
+
const targetDir = path20.resolve(cwd, options.dir || "./docs");
|
|
1696
1706
|
const skipPrompts = !!options.yes || !!options.nonInteractive;
|
|
1697
1707
|
if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
|
|
1698
1708
|
throw createCliError(
|
|
@@ -2081,7 +2091,7 @@ async function runInit(options) {
|
|
|
2081
2091
|
);
|
|
2082
2092
|
console.log();
|
|
2083
2093
|
const templatesDir = getTemplatesDir();
|
|
2084
|
-
const commonPath =
|
|
2094
|
+
const commonPath = path20.join(templatesDir, lang, "common");
|
|
2085
2095
|
if (!await fs15.pathExists(commonPath)) {
|
|
2086
2096
|
throw new Error(
|
|
2087
2097
|
tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
|
|
@@ -2089,11 +2099,11 @@ async function runInit(options) {
|
|
|
2089
2099
|
}
|
|
2090
2100
|
await copyTemplates(commonPath, targetDir);
|
|
2091
2101
|
if (projectType === "multi") {
|
|
2092
|
-
const featuresRoot =
|
|
2102
|
+
const featuresRoot = path20.join(targetDir, "features");
|
|
2093
2103
|
for (const component of components) {
|
|
2094
|
-
const componentDir =
|
|
2104
|
+
const componentDir = path20.join(featuresRoot, component);
|
|
2095
2105
|
await fs15.ensureDir(componentDir);
|
|
2096
|
-
const readmePath =
|
|
2106
|
+
const readmePath = path20.join(componentDir, "README.md");
|
|
2097
2107
|
if (!await fs15.pathExists(readmePath)) {
|
|
2098
2108
|
await fs15.writeFile(
|
|
2099
2109
|
readmePath,
|
|
@@ -2122,7 +2132,7 @@ async function runInit(options) {
|
|
|
2122
2132
|
workflow: {
|
|
2123
2133
|
mode: workflowMode,
|
|
2124
2134
|
codeDirtyScope: "auto",
|
|
2125
|
-
taskCommitGate: "
|
|
2135
|
+
taskCommitGate: "warn",
|
|
2126
2136
|
prePrReview: {
|
|
2127
2137
|
skills: ["code-review-excellence"],
|
|
2128
2138
|
minorPolicy: "warn"
|
|
@@ -2146,7 +2156,7 @@ async function runInit(options) {
|
|
|
2146
2156
|
config.projectRoot = projectRoot;
|
|
2147
2157
|
}
|
|
2148
2158
|
}
|
|
2149
|
-
const configPath =
|
|
2159
|
+
const configPath = path20.join(targetDir, ".lee-spec-kit.json");
|
|
2150
2160
|
await fs15.writeJson(configPath, config, { spaces: 2 });
|
|
2151
2161
|
console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
|
|
2152
2162
|
console.log();
|
|
@@ -2217,7 +2227,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2217
2227
|
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
2218
2228
|
runGit2(["init"], gitWorkdir);
|
|
2219
2229
|
}
|
|
2220
|
-
const relativePath = docsRepo === "standalone" ? "." :
|
|
2230
|
+
const relativePath = docsRepo === "standalone" ? "." : path20.relative(cwd, targetDir);
|
|
2221
2231
|
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
2222
2232
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
2223
2233
|
console.log(
|
|
@@ -2274,17 +2284,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2274
2284
|
}
|
|
2275
2285
|
function getAncestorDirs(startDir) {
|
|
2276
2286
|
const dirs = [];
|
|
2277
|
-
let current =
|
|
2287
|
+
let current = path20.resolve(startDir);
|
|
2278
2288
|
while (true) {
|
|
2279
2289
|
dirs.push(current);
|
|
2280
|
-
const parent =
|
|
2290
|
+
const parent = path20.dirname(current);
|
|
2281
2291
|
if (parent === current) break;
|
|
2282
2292
|
current = parent;
|
|
2283
2293
|
}
|
|
2284
2294
|
return dirs;
|
|
2285
2295
|
}
|
|
2286
2296
|
function hasWorkspaceBoundary(dir) {
|
|
2287
|
-
return fs15.existsSync(
|
|
2297
|
+
return fs15.existsSync(path20.join(dir, "package.json")) || fs15.existsSync(path20.join(dir, ".git"));
|
|
2288
2298
|
}
|
|
2289
2299
|
function getSearchBaseDirs(cwd) {
|
|
2290
2300
|
const ancestors = getAncestorDirs(cwd);
|
|
@@ -2300,7 +2310,7 @@ function normalizeComponentKeys(value) {
|
|
|
2300
2310
|
return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
|
|
2301
2311
|
}
|
|
2302
2312
|
async function inferComponentsFromFeaturesDir(docsDir) {
|
|
2303
|
-
const featuresPath =
|
|
2313
|
+
const featuresPath = path20.join(docsDir, "features");
|
|
2304
2314
|
if (!await fs15.pathExists(featuresPath)) return [];
|
|
2305
2315
|
const entries = await fs15.readdir(featuresPath, { withFileTypes: true });
|
|
2306
2316
|
const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
|
|
@@ -2311,21 +2321,21 @@ async function inferComponentsFromFeaturesDir(docsDir) {
|
|
|
2311
2321
|
async function getConfig(cwd) {
|
|
2312
2322
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2313
2323
|
const baseDirs = [
|
|
2314
|
-
...explicitDocsDir ? [
|
|
2324
|
+
...explicitDocsDir ? [path20.resolve(explicitDocsDir)] : [],
|
|
2315
2325
|
...getSearchBaseDirs(cwd)
|
|
2316
2326
|
];
|
|
2317
2327
|
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
2318
2328
|
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
2319
2329
|
for (const baseDir of baseDirs) {
|
|
2320
|
-
const resolvedBaseDir =
|
|
2330
|
+
const resolvedBaseDir = path20.resolve(baseDir);
|
|
2321
2331
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
2322
2332
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
2323
|
-
const possibleDocsDirs = [
|
|
2333
|
+
const possibleDocsDirs = [path20.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2324
2334
|
for (const docsDir of possibleDocsDirs) {
|
|
2325
|
-
const resolvedDocsDir =
|
|
2335
|
+
const resolvedDocsDir = path20.resolve(docsDir);
|
|
2326
2336
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2327
2337
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2328
|
-
const configPath =
|
|
2338
|
+
const configPath = path20.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
2329
2339
|
if (await fs15.pathExists(configPath)) {
|
|
2330
2340
|
try {
|
|
2331
2341
|
const configFile = await fs15.readJson(configPath);
|
|
@@ -2355,16 +2365,16 @@ async function getConfig(cwd) {
|
|
|
2355
2365
|
} catch {
|
|
2356
2366
|
}
|
|
2357
2367
|
}
|
|
2358
|
-
const agentsPath =
|
|
2359
|
-
const featuresPath =
|
|
2368
|
+
const agentsPath = path20.join(resolvedDocsDir, "agents");
|
|
2369
|
+
const featuresPath = path20.join(resolvedDocsDir, "features");
|
|
2360
2370
|
if (await fs15.pathExists(agentsPath) && await fs15.pathExists(featuresPath)) {
|
|
2361
2371
|
const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
|
|
2362
2372
|
const projectType = inferredComponents.length > 0 ? "multi" : "single";
|
|
2363
2373
|
const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
|
|
2364
2374
|
const langProbeCandidates = [
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2375
|
+
path20.join(agentsPath, "custom.md"),
|
|
2376
|
+
path20.join(agentsPath, "constitution.md"),
|
|
2377
|
+
path20.join(agentsPath, "agents.md")
|
|
2368
2378
|
];
|
|
2369
2379
|
let lang = "en";
|
|
2370
2380
|
for (const candidate of langProbeCandidates) {
|
|
@@ -2430,13 +2440,13 @@ async function patchMarkdownIfExists(filePath, transform) {
|
|
|
2430
2440
|
await fs15.writeFile(filePath, transform(content), "utf-8");
|
|
2431
2441
|
}
|
|
2432
2442
|
async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
|
|
2433
|
-
await patchMarkdownIfExists(
|
|
2443
|
+
await patchMarkdownIfExists(path20.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
2434
2444
|
await patchMarkdownIfExists(
|
|
2435
|
-
|
|
2445
|
+
path20.join(featureDir, "tasks.md"),
|
|
2436
2446
|
(content) => sanitizeTasksForLocal(content, lang)
|
|
2437
2447
|
);
|
|
2438
|
-
await fs15.remove(
|
|
2439
|
-
await fs15.remove(
|
|
2448
|
+
await fs15.remove(path20.join(featureDir, "issue.md"));
|
|
2449
|
+
await fs15.remove(path20.join(featureDir, "pr.md"));
|
|
2440
2450
|
}
|
|
2441
2451
|
|
|
2442
2452
|
// src/commands/feature.ts
|
|
@@ -2581,19 +2591,19 @@ async function runFeature(name, options) {
|
|
|
2581
2591
|
}
|
|
2582
2592
|
let featuresDir;
|
|
2583
2593
|
if (projectType === "multi") {
|
|
2584
|
-
featuresDir =
|
|
2594
|
+
featuresDir = path20.join(docsDir, "features", component);
|
|
2585
2595
|
} else {
|
|
2586
|
-
featuresDir =
|
|
2596
|
+
featuresDir = path20.join(docsDir, "features");
|
|
2587
2597
|
}
|
|
2588
2598
|
const featureFolderName = `${featureId}-${name}`;
|
|
2589
|
-
const featureDir =
|
|
2599
|
+
const featureDir = path20.join(featuresDir, featureFolderName);
|
|
2590
2600
|
if (await fs15.pathExists(featureDir)) {
|
|
2591
2601
|
throw createCliError(
|
|
2592
2602
|
"INVALID_ARGUMENT",
|
|
2593
2603
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
2594
2604
|
);
|
|
2595
2605
|
}
|
|
2596
|
-
const featureBasePath =
|
|
2606
|
+
const featureBasePath = path20.join(
|
|
2597
2607
|
getTemplatesDir(),
|
|
2598
2608
|
lang,
|
|
2599
2609
|
"common",
|
|
@@ -2652,7 +2662,7 @@ async function runFeature(name, options) {
|
|
|
2652
2662
|
featureName: name,
|
|
2653
2663
|
component: projectType === "multi" ? component : void 0,
|
|
2654
2664
|
featurePath: featureDir,
|
|
2655
|
-
featurePathFromDocs:
|
|
2665
|
+
featurePathFromDocs: path20.relative(docsDir, featureDir)
|
|
2656
2666
|
};
|
|
2657
2667
|
},
|
|
2658
2668
|
{ owner: "feature" }
|
|
@@ -2664,9 +2674,9 @@ function sleep2(ms) {
|
|
|
2664
2674
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
2665
2675
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2666
2676
|
const candidates = [
|
|
2667
|
-
...explicitDocsDir ? [
|
|
2668
|
-
|
|
2669
|
-
|
|
2677
|
+
...explicitDocsDir ? [path20.resolve(explicitDocsDir)] : [],
|
|
2678
|
+
path20.resolve(cwd, "docs"),
|
|
2679
|
+
path20.resolve(cwd)
|
|
2670
2680
|
];
|
|
2671
2681
|
const endAt = Date.now() + timeoutMs;
|
|
2672
2682
|
while (Date.now() < endAt) {
|
|
@@ -2693,11 +2703,11 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
2693
2703
|
return getConfig(cwd);
|
|
2694
2704
|
}
|
|
2695
2705
|
async function getNextFeatureId(docsDir, projectType, components) {
|
|
2696
|
-
const featuresDir =
|
|
2706
|
+
const featuresDir = path20.join(docsDir, "features");
|
|
2697
2707
|
let max = 0;
|
|
2698
2708
|
const scanDirs = [];
|
|
2699
2709
|
if (projectType === "multi") {
|
|
2700
|
-
scanDirs.push(...components.map((component) =>
|
|
2710
|
+
scanDirs.push(...components.map((component) => path20.join(featuresDir, component)));
|
|
2701
2711
|
} else {
|
|
2702
2712
|
scanDirs.push(featuresDir);
|
|
2703
2713
|
}
|
|
@@ -2846,6 +2856,9 @@ function getPrReviewRemoteBlockReasons(feature, lang) {
|
|
|
2846
2856
|
const remote = feature.pr.remote;
|
|
2847
2857
|
if (!remote || !remote.available) return [];
|
|
2848
2858
|
const reasons = [];
|
|
2859
|
+
if (remote.state === "CLOSED" && !remote.isMerged) {
|
|
2860
|
+
reasons.push(tr(lang, "messages", "prReviewRemoteReasonClosed"));
|
|
2861
|
+
}
|
|
2849
2862
|
if (remote.hasBlockingReview) {
|
|
2850
2863
|
reasons.push(tr(lang, "messages", "prReviewRemoteReasonChangesRequested"));
|
|
2851
2864
|
}
|
|
@@ -2911,14 +2924,90 @@ function normalizeTaskTopic(value) {
|
|
|
2911
2924
|
function normalizeCommitSubjectForGate(value) {
|
|
2912
2925
|
return normalizeCommitTopicText(value).replace(/^[a-z]+(?:\([^)]*\))?!?:\s*/i, "").toLowerCase();
|
|
2913
2926
|
}
|
|
2927
|
+
function toTaskKey(rawTitle) {
|
|
2928
|
+
const trimmed = normalizeCommitTopicText(rawTitle);
|
|
2929
|
+
if (!trimmed) return "";
|
|
2930
|
+
const idMatch = trimmed.match(/^(T-[A-Za-z0-9-]+)/i);
|
|
2931
|
+
if (idMatch) return idMatch[1].toUpperCase();
|
|
2932
|
+
return normalizeTaskTopic(trimmed).toLowerCase();
|
|
2933
|
+
}
|
|
2934
|
+
function countDoneTransitionsInLatestTasksCommit(feature) {
|
|
2935
|
+
const docsGitCwd = feature.git.docsGitCwd;
|
|
2936
|
+
const tasksRelativePath = normalizeGitRelativePath(
|
|
2937
|
+
path20.join(feature.docs.featurePathFromDocs, "tasks.md")
|
|
2938
|
+
);
|
|
2939
|
+
const diff = readGitText(docsGitCwd, [
|
|
2940
|
+
"diff",
|
|
2941
|
+
"--unified=0",
|
|
2942
|
+
"--no-color",
|
|
2943
|
+
"HEAD~1",
|
|
2944
|
+
"HEAD",
|
|
2945
|
+
"--",
|
|
2946
|
+
tasksRelativePath
|
|
2947
|
+
]);
|
|
2948
|
+
if (diff === void 0) return 0;
|
|
2949
|
+
if (!diff.trim()) return 0;
|
|
2950
|
+
const removedByTask = /* @__PURE__ */ new Map();
|
|
2951
|
+
const addedByTask = /* @__PURE__ */ new Map();
|
|
2952
|
+
const parseTaskLine = (line) => {
|
|
2953
|
+
const match = line.match(/^\s*-\s*\[(TODO|DOING|DONE|REVIEW)\]\s+(.+?)\s*$/i);
|
|
2954
|
+
if (!match) return null;
|
|
2955
|
+
const key = toTaskKey(match[2]);
|
|
2956
|
+
if (!key) return null;
|
|
2957
|
+
return {
|
|
2958
|
+
key,
|
|
2959
|
+
status: match[1].toUpperCase()
|
|
2960
|
+
};
|
|
2961
|
+
};
|
|
2962
|
+
for (const line of diff.split("\n")) {
|
|
2963
|
+
if (line.startsWith("---") || line.startsWith("+++")) continue;
|
|
2964
|
+
if (line.startsWith("-")) {
|
|
2965
|
+
const parsed = parseTaskLine(line.slice(1));
|
|
2966
|
+
if (!parsed) continue;
|
|
2967
|
+
const existing = removedByTask.get(parsed.key) || /* @__PURE__ */ new Set();
|
|
2968
|
+
existing.add(parsed.status);
|
|
2969
|
+
removedByTask.set(parsed.key, existing);
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
if (line.startsWith("+")) {
|
|
2973
|
+
const parsed = parseTaskLine(line.slice(1));
|
|
2974
|
+
if (!parsed) continue;
|
|
2975
|
+
const existing = addedByTask.get(parsed.key) || /* @__PURE__ */ new Set();
|
|
2976
|
+
existing.add(parsed.status);
|
|
2977
|
+
addedByTask.set(parsed.key, existing);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
let doneTransitions = 0;
|
|
2981
|
+
for (const [taskKey, addedStatuses] of addedByTask.entries()) {
|
|
2982
|
+
if (!addedStatuses.has("DONE")) continue;
|
|
2983
|
+
const removedStatuses = removedByTask.get(taskKey);
|
|
2984
|
+
if (!removedStatuses) continue;
|
|
2985
|
+
const transitionedFromOpen = removedStatuses.has("TODO") || removedStatuses.has("DOING") || removedStatuses.has("REVIEW");
|
|
2986
|
+
if (transitionedFromOpen) {
|
|
2987
|
+
doneTransitions += 1;
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
return doneTransitions;
|
|
2991
|
+
}
|
|
2914
2992
|
function checkTaskCommitGate(feature) {
|
|
2993
|
+
const doneTransitions = countDoneTransitionsInLatestTasksCommit(feature);
|
|
2994
|
+
if (doneTransitions === 0) {
|
|
2995
|
+
return { pass: true, doneTransitions };
|
|
2996
|
+
}
|
|
2997
|
+
if (typeof doneTransitions === "number" && doneTransitions > 1) {
|
|
2998
|
+
return {
|
|
2999
|
+
pass: false,
|
|
3000
|
+
reason: "DONE_TRANSITIONS_COUNT",
|
|
3001
|
+
doneTransitions
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
2915
3004
|
const projectGitCwd = feature.git.projectGitCwd;
|
|
2916
3005
|
const lastDoneTopic = normalizeTaskTopic(feature.lastDoneTask?.title || "");
|
|
2917
3006
|
if (!projectGitCwd || !lastDoneTopic) {
|
|
2918
3007
|
return { pass: true };
|
|
2919
3008
|
}
|
|
2920
3009
|
const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
|
|
2921
|
-
const relativeDocsDir =
|
|
3010
|
+
const relativeDocsDir = path20.relative(projectGitCwd, feature.git.docsGitCwd);
|
|
2922
3011
|
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
2923
3012
|
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
2924
3013
|
args.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
@@ -2939,6 +3028,10 @@ function checkTaskCommitGate(feature) {
|
|
|
2939
3028
|
}
|
|
2940
3029
|
function getTaskCommitGateReasonText(lang, check) {
|
|
2941
3030
|
switch (check.reason) {
|
|
3031
|
+
case "DONE_TRANSITIONS_COUNT":
|
|
3032
|
+
return tr(lang, "messages", "taskCommitGateReasonDoneCount", {
|
|
3033
|
+
count: check.doneTransitions || 0
|
|
3034
|
+
});
|
|
2942
3035
|
case "NO_PROJECT_COMMIT":
|
|
2943
3036
|
return tr(lang, "messages", "taskCommitGateReasonNoTasksCommit");
|
|
2944
3037
|
case "PROJECT_LOG_UNAVAILABLE":
|
|
@@ -3752,6 +3845,16 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3752
3845
|
];
|
|
3753
3846
|
}
|
|
3754
3847
|
if (f.pr.status === "Review") {
|
|
3848
|
+
if (f.pr.remote?.available && f.pr.remote.isMerged) {
|
|
3849
|
+
return [
|
|
3850
|
+
{
|
|
3851
|
+
type: "instruction",
|
|
3852
|
+
category: "pr_status_update",
|
|
3853
|
+
requiresUserCheck: true,
|
|
3854
|
+
message: tr(lang, "messages", "prReviewMergedSyncStatus")
|
|
3855
|
+
}
|
|
3856
|
+
];
|
|
3857
|
+
}
|
|
3755
3858
|
if (!f.docs.prReviewFindingsFieldExists) {
|
|
3756
3859
|
return [
|
|
3757
3860
|
{
|
|
@@ -3793,6 +3896,7 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3793
3896
|
];
|
|
3794
3897
|
}
|
|
3795
3898
|
const remoteBlockReasons = getPrReviewRemoteBlockReasons(f, lang);
|
|
3899
|
+
const remoteUnavailable = workflowPolicy.mode === "github" && !!f.pr.link && (!f.pr.remote || !f.pr.remote.available);
|
|
3796
3900
|
const actions = [
|
|
3797
3901
|
{
|
|
3798
3902
|
type: "instruction",
|
|
@@ -3820,16 +3924,41 @@ ${tr(lang, "messages", "taskCommitGateWarnProceed", {
|
|
|
3820
3924
|
})
|
|
3821
3925
|
});
|
|
3822
3926
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3927
|
+
if (remoteBlockReasons.length > 0 || remoteUnavailable) {
|
|
3928
|
+
const reasons = [...remoteBlockReasons];
|
|
3929
|
+
if (remoteUnavailable) {
|
|
3930
|
+
reasons.push(tr(lang, "messages", "prReviewRemoteReasonUnavailable"));
|
|
3931
|
+
}
|
|
3932
|
+
actions.push({
|
|
3933
|
+
type: "instruction",
|
|
3934
|
+
category: "code_review",
|
|
3935
|
+
requiresUserCheck: true,
|
|
3936
|
+
message: tr(lang, "messages", "prReviewRemoteBlocked", {
|
|
3937
|
+
reasons: reasons.join("; ")
|
|
3938
|
+
})
|
|
3939
|
+
});
|
|
3940
|
+
} else if (f.git.docsGitCwd) {
|
|
3941
|
+
actions.push({
|
|
3942
|
+
type: "command",
|
|
3943
|
+
category: "code_review",
|
|
3944
|
+
requiresUserCheck: true,
|
|
3945
|
+
operationType: "remote",
|
|
3946
|
+
scope: "docs",
|
|
3947
|
+
cwd: f.git.docsGitCwd,
|
|
3948
|
+
cmd: tr(lang, "messages", "prReviewMergeCommand", {
|
|
3949
|
+
featureRef: f.id || f.folderName
|
|
3950
|
+
})
|
|
3951
|
+
});
|
|
3952
|
+
} else {
|
|
3953
|
+
actions.push({
|
|
3954
|
+
type: "instruction",
|
|
3955
|
+
category: "code_review",
|
|
3956
|
+
requiresUserCheck: true,
|
|
3957
|
+
message: tr(lang, "messages", "prReviewMerge", {
|
|
3958
|
+
featureRef: f.id || f.folderName
|
|
3959
|
+
})
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3833
3962
|
return actions;
|
|
3834
3963
|
}
|
|
3835
3964
|
return [
|
|
@@ -3907,14 +4036,32 @@ function applyApprovalPolicy(step, actions, approval) {
|
|
|
3907
4036
|
return { ...a, requiresUserCheck };
|
|
3908
4037
|
});
|
|
3909
4038
|
}
|
|
4039
|
+
function withUserRequestReplanOption(actions, lang) {
|
|
4040
|
+
if (actions.some((action) => action.category === "user_request_replan")) {
|
|
4041
|
+
return actions;
|
|
4042
|
+
}
|
|
4043
|
+
return [
|
|
4044
|
+
...actions,
|
|
4045
|
+
{
|
|
4046
|
+
type: "instruction",
|
|
4047
|
+
category: "user_request_replan",
|
|
4048
|
+
requiresUserCheck: true,
|
|
4049
|
+
message: tr(lang, "messages", "userRequestReplan")
|
|
4050
|
+
}
|
|
4051
|
+
];
|
|
4052
|
+
}
|
|
3910
4053
|
function resolveFeatureProgress(feature, stepDefinitions, lang, approval) {
|
|
3911
4054
|
const ordered = [...stepDefinitions].sort((a, b) => a.step - b.step);
|
|
3912
4055
|
for (const definition of ordered) {
|
|
3913
4056
|
if (!definition.current) continue;
|
|
3914
4057
|
if (definition.current.when(feature)) {
|
|
4058
|
+
const currentActions = withUserRequestReplanOption(
|
|
4059
|
+
definition.current.actions(feature),
|
|
4060
|
+
lang
|
|
4061
|
+
);
|
|
3915
4062
|
const actions = applyApprovalPolicy(
|
|
3916
4063
|
definition.step,
|
|
3917
|
-
|
|
4064
|
+
currentActions,
|
|
3918
4065
|
approval
|
|
3919
4066
|
);
|
|
3920
4067
|
return {
|
|
@@ -4040,6 +4187,10 @@ function isGitPathIgnored(cwd, relativePath) {
|
|
|
4040
4187
|
return void 0;
|
|
4041
4188
|
}
|
|
4042
4189
|
}
|
|
4190
|
+
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
4191
|
+
function resetContextGitCaches() {
|
|
4192
|
+
GIT_WORKTREE_CACHE.clear();
|
|
4193
|
+
}
|
|
4043
4194
|
function getGitTopLevel(cwd) {
|
|
4044
4195
|
try {
|
|
4045
4196
|
return execSync("git rev-parse --show-toplevel", {
|
|
@@ -4051,6 +4202,52 @@ function getGitTopLevel(cwd) {
|
|
|
4051
4202
|
return null;
|
|
4052
4203
|
}
|
|
4053
4204
|
}
|
|
4205
|
+
function listGitWorktrees(cwd) {
|
|
4206
|
+
const topLevel = getGitTopLevel(cwd) || cwd;
|
|
4207
|
+
const cacheKey = path20.resolve(topLevel);
|
|
4208
|
+
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
4209
|
+
if (cached) return cached;
|
|
4210
|
+
try {
|
|
4211
|
+
const out = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
4212
|
+
cwd: topLevel,
|
|
4213
|
+
encoding: "utf-8",
|
|
4214
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4215
|
+
});
|
|
4216
|
+
const entries = [];
|
|
4217
|
+
let current = null;
|
|
4218
|
+
for (const rawLine of out.split("\n")) {
|
|
4219
|
+
const line = rawLine.trim();
|
|
4220
|
+
if (!line) {
|
|
4221
|
+
if (current?.path) entries.push(current);
|
|
4222
|
+
current = null;
|
|
4223
|
+
continue;
|
|
4224
|
+
}
|
|
4225
|
+
if (line.startsWith("worktree ")) {
|
|
4226
|
+
if (current?.path) entries.push(current);
|
|
4227
|
+
current = { path: line.slice("worktree ".length).trim() };
|
|
4228
|
+
continue;
|
|
4229
|
+
}
|
|
4230
|
+
if (line.startsWith("branch ")) {
|
|
4231
|
+
if (!current) continue;
|
|
4232
|
+
const fullRef = line.slice("branch ".length).trim();
|
|
4233
|
+
current.branch = fullRef.replace(/^refs\/heads\//, "");
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
if (current?.path) entries.push(current);
|
|
4237
|
+
GIT_WORKTREE_CACHE.set(cacheKey, entries);
|
|
4238
|
+
return entries;
|
|
4239
|
+
} catch {
|
|
4240
|
+
return void 0;
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
function findWorktreePathForBranch(cwd, branchName) {
|
|
4244
|
+
const target = branchName.trim();
|
|
4245
|
+
if (!target) return void 0;
|
|
4246
|
+
const entries = listGitWorktrees(cwd);
|
|
4247
|
+
if (!entries) return void 0;
|
|
4248
|
+
const match = entries.find((entry) => entry.branch === target);
|
|
4249
|
+
return match?.path;
|
|
4250
|
+
}
|
|
4054
4251
|
function resolveProjectGitCwd(config, repo, lang = config.lang ?? DEFAULT_LANG) {
|
|
4055
4252
|
const docsRepo = config.docsRepo;
|
|
4056
4253
|
if (docsRepo !== "standalone") {
|
|
@@ -4204,13 +4401,13 @@ function parsePrLink(value) {
|
|
|
4204
4401
|
return trimmed;
|
|
4205
4402
|
}
|
|
4206
4403
|
function normalizeGitPath(value) {
|
|
4207
|
-
return value.split(
|
|
4404
|
+
return value.split(path20.sep).join("/");
|
|
4208
4405
|
}
|
|
4209
4406
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
4210
|
-
const relativeDocsDir =
|
|
4407
|
+
const relativeDocsDir = path20.relative(projectGitCwd, docsDir);
|
|
4211
4408
|
if (!relativeDocsDir) return [];
|
|
4212
|
-
if (
|
|
4213
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
4409
|
+
if (path20.isAbsolute(relativeDocsDir)) return [];
|
|
4410
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path20.sep}`)) {
|
|
4214
4411
|
return [];
|
|
4215
4412
|
}
|
|
4216
4413
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(/\/+$/, "");
|
|
@@ -4232,6 +4429,33 @@ function uniqueNormalizedPaths(values) {
|
|
|
4232
4429
|
var PROJECT_DIRTY_STATUS_CACHE = /* @__PURE__ */ new Map();
|
|
4233
4430
|
var COMPONENT_STATUS_PATH_CACHE = /* @__PURE__ */ new Map();
|
|
4234
4431
|
var PR_REMOTE_STATUS_CACHE = /* @__PURE__ */ new Map();
|
|
4432
|
+
var FEATURE_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
4433
|
+
function resetContextParseCaches() {
|
|
4434
|
+
PROJECT_DIRTY_STATUS_CACHE.clear();
|
|
4435
|
+
COMPONENT_STATUS_PATH_CACHE.clear();
|
|
4436
|
+
PR_REMOTE_STATUS_CACHE.clear();
|
|
4437
|
+
FEATURE_WORKTREE_CACHE.clear();
|
|
4438
|
+
}
|
|
4439
|
+
function resolveFeatureWorktreePath(projectGitCwd, issueNumber, slug, folderName) {
|
|
4440
|
+
const expectedBranches = [
|
|
4441
|
+
`feat/${issueNumber}-${slug}`,
|
|
4442
|
+
`feat/${issueNumber}-${folderName}`
|
|
4443
|
+
];
|
|
4444
|
+
for (const branchName of expectedBranches) {
|
|
4445
|
+
const cacheKey = `${projectGitCwd}::${branchName}`;
|
|
4446
|
+
let foundPath = FEATURE_WORKTREE_CACHE.get(cacheKey);
|
|
4447
|
+
if (typeof foundPath === "undefined") {
|
|
4448
|
+
foundPath = findWorktreePathForBranch(projectGitCwd, branchName) || null;
|
|
4449
|
+
FEATURE_WORKTREE_CACHE.set(cacheKey, foundPath);
|
|
4450
|
+
}
|
|
4451
|
+
if (!foundPath) continue;
|
|
4452
|
+
return {
|
|
4453
|
+
cwd: foundPath,
|
|
4454
|
+
branch: getCurrentBranch(foundPath) || branchName
|
|
4455
|
+
};
|
|
4456
|
+
}
|
|
4457
|
+
return void 0;
|
|
4458
|
+
}
|
|
4235
4459
|
function toUpperToken(value) {
|
|
4236
4460
|
if (typeof value !== "string") return void 0;
|
|
4237
4461
|
const normalized = value.trim().toUpperCase();
|
|
@@ -4274,7 +4498,7 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
|
|
|
4274
4498
|
"view",
|
|
4275
4499
|
prRef,
|
|
4276
4500
|
"--json",
|
|
4277
|
-
"reviewDecision,mergeStateStatus,isDraft,statusCheckRollup"
|
|
4501
|
+
"state,mergedAt,reviewDecision,mergeStateStatus,isDraft,statusCheckRollup"
|
|
4278
4502
|
],
|
|
4279
4503
|
{
|
|
4280
4504
|
cwd: projectGitCwd,
|
|
@@ -4289,6 +4513,9 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
|
|
|
4289
4513
|
return null;
|
|
4290
4514
|
}
|
|
4291
4515
|
const parsed = JSON.parse(raw);
|
|
4516
|
+
const state = toUpperToken(parsed.state);
|
|
4517
|
+
const mergedAt = typeof parsed.mergedAt === "string" && parsed.mergedAt.trim().length > 0 ? parsed.mergedAt.trim() : void 0;
|
|
4518
|
+
const isMerged = state === "MERGED" || !!mergedAt;
|
|
4292
4519
|
const reviewDecision = toUpperToken(parsed.reviewDecision);
|
|
4293
4520
|
const mergeStateStatus = toUpperToken(parsed.mergeStateStatus);
|
|
4294
4521
|
const isDraft = parsed.isDraft === true;
|
|
@@ -4303,11 +4530,14 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
|
|
|
4303
4530
|
const remote = {
|
|
4304
4531
|
source: "gh",
|
|
4305
4532
|
available: true,
|
|
4533
|
+
state,
|
|
4534
|
+
mergedAt,
|
|
4535
|
+
isMerged,
|
|
4306
4536
|
reviewDecision,
|
|
4307
4537
|
mergeStateStatus,
|
|
4308
4538
|
isDraft,
|
|
4309
4539
|
hasBlockingReview: reviewDecision === "CHANGES_REQUESTED" || reviewDecision === "REVIEW_REQUIRED",
|
|
4310
|
-
mergeBlocked: isDraft || isMergeBlockedState(mergeStateStatus),
|
|
4540
|
+
mergeBlocked: !isMerged && (isDraft || isMergeBlockedState(mergeStateStatus)),
|
|
4311
4541
|
failingChecks,
|
|
4312
4542
|
pendingChecks
|
|
4313
4543
|
};
|
|
@@ -4331,10 +4561,10 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
|
|
|
4331
4561
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
4332
4562
|
candidates.map((candidate) => {
|
|
4333
4563
|
if (!candidate) return "";
|
|
4334
|
-
if (!
|
|
4335
|
-
const relative =
|
|
4564
|
+
if (!path20.isAbsolute(candidate)) return candidate;
|
|
4565
|
+
const relative = path20.relative(projectGitCwd, candidate);
|
|
4336
4566
|
if (!relative) return "";
|
|
4337
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
4567
|
+
if (relative === ".." || relative.startsWith(`..${path20.sep}`)) return "";
|
|
4338
4568
|
return relative;
|
|
4339
4569
|
}).filter(Boolean)
|
|
4340
4570
|
);
|
|
@@ -4347,7 +4577,7 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
|
|
|
4347
4577
|
if (cached) return [...cached];
|
|
4348
4578
|
const existing = [];
|
|
4349
4579
|
for (const candidate of normalizedCandidates) {
|
|
4350
|
-
if (await fs15.pathExists(
|
|
4580
|
+
if (await fs15.pathExists(path20.join(projectGitCwd, candidate))) {
|
|
4351
4581
|
existing.push(candidate);
|
|
4352
4582
|
}
|
|
4353
4583
|
}
|
|
@@ -4436,15 +4666,15 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4436
4666
|
const lang = options.lang;
|
|
4437
4667
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
4438
4668
|
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
4439
|
-
const folderName =
|
|
4669
|
+
const folderName = path20.basename(featurePath);
|
|
4440
4670
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
4441
4671
|
const id = match?.[1];
|
|
4442
4672
|
const slug = match?.[2] || folderName;
|
|
4443
|
-
const specPath =
|
|
4444
|
-
const planPath =
|
|
4445
|
-
const tasksPath =
|
|
4446
|
-
const issueDocPath =
|
|
4447
|
-
const prDocPath =
|
|
4673
|
+
const specPath = path20.join(featurePath, "spec.md");
|
|
4674
|
+
const planPath = path20.join(featurePath, "plan.md");
|
|
4675
|
+
const tasksPath = path20.join(featurePath, "tasks.md");
|
|
4676
|
+
const issueDocPath = path20.join(featurePath, "issue.md");
|
|
4677
|
+
const prDocPath = path20.join(featurePath, "pr.md");
|
|
4448
4678
|
let specStatus;
|
|
4449
4679
|
let issueNumber;
|
|
4450
4680
|
const specExists = await fs15.pathExists(specPath);
|
|
@@ -4455,6 +4685,30 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4455
4685
|
const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
|
|
4456
4686
|
issueNumber = parseIssueNumber(issueValue);
|
|
4457
4687
|
}
|
|
4688
|
+
let effectiveProjectGitCwd = context.projectGitCwd;
|
|
4689
|
+
let effectiveProjectBranch = context.projectBranch;
|
|
4690
|
+
let effectiveProjectBranchAvailable = context.projectBranchAvailable;
|
|
4691
|
+
if (effectiveProjectGitCwd && issueNumber) {
|
|
4692
|
+
const alreadyExpected = isExpectedFeatureBranch(
|
|
4693
|
+
effectiveProjectBranch,
|
|
4694
|
+
issueNumber,
|
|
4695
|
+
slug,
|
|
4696
|
+
folderName
|
|
4697
|
+
);
|
|
4698
|
+
if (!alreadyExpected) {
|
|
4699
|
+
const worktree = resolveFeatureWorktreePath(
|
|
4700
|
+
effectiveProjectGitCwd,
|
|
4701
|
+
issueNumber,
|
|
4702
|
+
slug,
|
|
4703
|
+
folderName
|
|
4704
|
+
);
|
|
4705
|
+
if (worktree) {
|
|
4706
|
+
effectiveProjectGitCwd = worktree.cwd;
|
|
4707
|
+
effectiveProjectBranch = worktree.branch;
|
|
4708
|
+
effectiveProjectBranchAvailable = true;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4458
4712
|
let planStatus;
|
|
4459
4713
|
const planExists = await fs15.pathExists(planPath);
|
|
4460
4714
|
if (planExists) {
|
|
@@ -4573,6 +4827,27 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4573
4827
|
prReviewEvidence = prReviewEvidenceValue?.trim();
|
|
4574
4828
|
prReviewEvidenceProvided = !isPlaceholderReviewEvidence(prReviewEvidenceValue);
|
|
4575
4829
|
}
|
|
4830
|
+
if (effectiveProjectGitCwd && issueNumber) {
|
|
4831
|
+
const alreadyExpected = isExpectedFeatureBranch(
|
|
4832
|
+
effectiveProjectBranch,
|
|
4833
|
+
issueNumber,
|
|
4834
|
+
slug,
|
|
4835
|
+
folderName
|
|
4836
|
+
);
|
|
4837
|
+
if (!alreadyExpected) {
|
|
4838
|
+
const worktree = resolveFeatureWorktreePath(
|
|
4839
|
+
effectiveProjectGitCwd,
|
|
4840
|
+
issueNumber,
|
|
4841
|
+
slug,
|
|
4842
|
+
folderName
|
|
4843
|
+
);
|
|
4844
|
+
if (worktree) {
|
|
4845
|
+
effectiveProjectGitCwd = worktree.cwd;
|
|
4846
|
+
effectiveProjectBranch = worktree.branch;
|
|
4847
|
+
effectiveProjectBranchAvailable = true;
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4576
4851
|
const issueDocExists = await fs15.pathExists(issueDocPath);
|
|
4577
4852
|
if (issueDocExists) {
|
|
4578
4853
|
const content = await fs15.readFile(issueDocPath, "utf-8");
|
|
@@ -4594,20 +4869,20 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4594
4869
|
prDocPrFieldExists = hasAnySpecKey(content, ["PR", "Pull Request"]);
|
|
4595
4870
|
prDocReviewStatusFieldExists = hasAnySpecKey(content, ["PR \uC0C1\uD0DC", "PR Status"]);
|
|
4596
4871
|
}
|
|
4597
|
-
if (workflowPolicy.requireReview && prStatus === "Review" && prLink &&
|
|
4598
|
-
prRemote = resolvePrRemoteStatus(prLink,
|
|
4872
|
+
if (workflowPolicy.requireReview && prStatus === "Review" && prLink && effectiveProjectGitCwd) {
|
|
4873
|
+
prRemote = resolvePrRemoteStatus(prLink, effectiveProjectGitCwd) || void 0;
|
|
4599
4874
|
}
|
|
4600
4875
|
const warnings = [];
|
|
4601
|
-
if (
|
|
4876
|
+
if (effectiveProjectBranchAvailable === false) {
|
|
4602
4877
|
warnings.push(tr(lang, "warnings", "projectBranchUnavailable"));
|
|
4603
4878
|
}
|
|
4604
4879
|
const onExpectedBranch = isExpectedFeatureBranch(
|
|
4605
|
-
|
|
4880
|
+
effectiveProjectBranch,
|
|
4606
4881
|
issueNumber,
|
|
4607
4882
|
slug,
|
|
4608
4883
|
folderName
|
|
4609
4884
|
);
|
|
4610
|
-
const relativeFeaturePathFromDocs =
|
|
4885
|
+
const relativeFeaturePathFromDocs = path20.relative(context.docsDir, featurePath);
|
|
4611
4886
|
const normalizedFeaturePathFromDocs = normalizeGitPath(relativeFeaturePathFromDocs);
|
|
4612
4887
|
const docsPathIgnored = typeof context.docsPathIgnored === "boolean" ? context.docsPathIgnored : isGitPathIgnored(context.docsGitCwd, normalizedFeaturePathFromDocs);
|
|
4613
4888
|
let docsHasUncommittedChanges = typeof context.docsHasUncommittedChanges === "boolean" ? context.docsHasUncommittedChanges : false;
|
|
@@ -4631,10 +4906,10 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4631
4906
|
}
|
|
4632
4907
|
let projectHasUncommittedChanges = typeof context.projectHasUncommittedChanges === "boolean" ? context.projectHasUncommittedChanges : false;
|
|
4633
4908
|
let projectStatusUnavailable = !!context.projectStatusUnavailable;
|
|
4634
|
-
if (typeof context.projectHasUncommittedChanges !== "boolean" &&
|
|
4909
|
+
if (typeof context.projectHasUncommittedChanges !== "boolean" && effectiveProjectGitCwd) {
|
|
4635
4910
|
const dirtyScopePolicy = resolveCodeDirtyScopePolicy(options.workflow, options.projectType);
|
|
4636
4911
|
const projectCacheKey = JSON.stringify({
|
|
4637
|
-
projectGitCwd:
|
|
4912
|
+
projectGitCwd: effectiveProjectGitCwd,
|
|
4638
4913
|
docsDir: context.docsDir,
|
|
4639
4914
|
type,
|
|
4640
4915
|
dirtyScopePolicy,
|
|
@@ -4648,19 +4923,19 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4648
4923
|
let projectStatusPaths = [];
|
|
4649
4924
|
if (dirtyScopePolicy === "component" && type !== "single") {
|
|
4650
4925
|
const componentStatusPaths = await resolveComponentStatusPaths(
|
|
4651
|
-
|
|
4926
|
+
effectiveProjectGitCwd,
|
|
4652
4927
|
type,
|
|
4653
4928
|
options.workflow
|
|
4654
4929
|
);
|
|
4655
|
-
projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(
|
|
4930
|
+
projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(effectiveProjectGitCwd, context.docsDir);
|
|
4656
4931
|
} else {
|
|
4657
4932
|
projectStatusPaths = resolveProjectStatusPaths(
|
|
4658
|
-
|
|
4933
|
+
effectiveProjectGitCwd,
|
|
4659
4934
|
context.docsDir
|
|
4660
4935
|
);
|
|
4661
4936
|
}
|
|
4662
4937
|
const projectStatus = getGitStatusPorcelain(
|
|
4663
|
-
|
|
4938
|
+
effectiveProjectGitCwd,
|
|
4664
4939
|
projectStatusPaths
|
|
4665
4940
|
);
|
|
4666
4941
|
projectStatusUnavailable = projectStatus === void 0;
|
|
@@ -4829,10 +5104,10 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4829
5104
|
pr: { link: prLink, status: prStatus, remote: prRemote },
|
|
4830
5105
|
git: {
|
|
4831
5106
|
docsBranch: context.docsBranch,
|
|
4832
|
-
projectBranch:
|
|
4833
|
-
projectBranchAvailable:
|
|
5107
|
+
projectBranch: effectiveProjectBranch,
|
|
5108
|
+
projectBranchAvailable: effectiveProjectBranchAvailable,
|
|
4834
5109
|
docsGitCwd: context.docsGitCwd,
|
|
4835
|
-
projectGitCwd:
|
|
5110
|
+
projectGitCwd: effectiveProjectGitCwd,
|
|
4836
5111
|
onExpectedBranch,
|
|
4837
5112
|
docsEverCommitted,
|
|
4838
5113
|
docsHasUncommittedChanges,
|
|
@@ -4962,6 +5237,8 @@ function buildDocsFeatureGitMeta(docsGitCwd, relativeFeaturePaths) {
|
|
|
4962
5237
|
return map;
|
|
4963
5238
|
}
|
|
4964
5239
|
async function scanFeatures(config) {
|
|
5240
|
+
resetContextGitCaches();
|
|
5241
|
+
resetContextParseCaches();
|
|
4965
5242
|
const features = [];
|
|
4966
5243
|
const warnings = [];
|
|
4967
5244
|
const stepDefinitions = getStepDefinitions(config.lang, config.workflow);
|
|
@@ -5005,7 +5282,7 @@ async function scanFeatures(config) {
|
|
|
5005
5282
|
}
|
|
5006
5283
|
}
|
|
5007
5284
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
5008
|
-
(dir) => normalizeRelPath(
|
|
5285
|
+
(dir) => normalizeRelPath(path20.relative(config.docsDir, dir))
|
|
5009
5286
|
);
|
|
5010
5287
|
const docsGitMeta = buildDocsFeatureGitMeta(config.docsDir, relativeFeaturePaths);
|
|
5011
5288
|
const parseTargets = config.projectType === "single" ? [{ type: "single", dirs: componentFeatureDirs.get("single") || [] }] : resolveProjectComponents(config.projectType, config.components).map((component) => ({
|
|
@@ -5016,7 +5293,7 @@ async function scanFeatures(config) {
|
|
|
5016
5293
|
const parsed = await Promise.all(
|
|
5017
5294
|
target.dirs.map(async (dir) => {
|
|
5018
5295
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
5019
|
-
|
|
5296
|
+
path20.relative(config.docsDir, dir)
|
|
5020
5297
|
);
|
|
5021
5298
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
5022
5299
|
return parseFeature(
|
|
@@ -5085,13 +5362,13 @@ async function runStatus(options) {
|
|
|
5085
5362
|
);
|
|
5086
5363
|
}
|
|
5087
5364
|
const { docsDir, projectType, projectName, lang } = config;
|
|
5088
|
-
const featuresDir =
|
|
5365
|
+
const featuresDir = path20.join(docsDir, "features");
|
|
5089
5366
|
const scan = await scanFeatures(config);
|
|
5090
5367
|
const features = [];
|
|
5091
5368
|
const idMap = /* @__PURE__ */ new Map();
|
|
5092
5369
|
for (const f of scan.features) {
|
|
5093
5370
|
const id = f.id || "UNKNOWN";
|
|
5094
|
-
const relPath =
|
|
5371
|
+
const relPath = path20.relative(docsDir, f.path);
|
|
5095
5372
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
5096
5373
|
idMap.get(id).push(relPath);
|
|
5097
5374
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -5172,7 +5449,7 @@ async function runStatus(options) {
|
|
|
5172
5449
|
}
|
|
5173
5450
|
console.log();
|
|
5174
5451
|
if (options.write) {
|
|
5175
|
-
const outputPath =
|
|
5452
|
+
const outputPath = path20.join(featuresDir, "status.md");
|
|
5176
5453
|
const date = getLocalDateString();
|
|
5177
5454
|
const content = [
|
|
5178
5455
|
"# Feature Status",
|
|
@@ -5200,7 +5477,7 @@ function escapeRegExp2(value) {
|
|
|
5200
5477
|
}
|
|
5201
5478
|
async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
|
|
5202
5479
|
try {
|
|
5203
|
-
const specPath =
|
|
5480
|
+
const specPath = path20.join(featureDir, "spec.md");
|
|
5204
5481
|
if (!await fs15.pathExists(specPath)) return fallbackSlug;
|
|
5205
5482
|
const content = await fs15.readFile(specPath, "utf-8");
|
|
5206
5483
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
@@ -5282,10 +5559,10 @@ async function runUpdate(options) {
|
|
|
5282
5559
|
console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
5283
5560
|
}
|
|
5284
5561
|
if (agentsMode === "all") {
|
|
5285
|
-
const commonAgentsBase =
|
|
5286
|
-
const targetAgentsBase =
|
|
5287
|
-
const commonAgents =
|
|
5288
|
-
const targetAgents =
|
|
5562
|
+
const commonAgentsBase = path20.join(templatesDir, lang, "common", "agents");
|
|
5563
|
+
const targetAgentsBase = path20.join(docsDir, "agents");
|
|
5564
|
+
const commonAgents = commonAgentsBase;
|
|
5565
|
+
const targetAgents = targetAgentsBase;
|
|
5289
5566
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
5290
5567
|
const projectName = config.projectName ?? "{{projectName}}";
|
|
5291
5568
|
const commonReplacements = {
|
|
@@ -5312,7 +5589,7 @@ async function runUpdate(options) {
|
|
|
5312
5589
|
}
|
|
5313
5590
|
console.log(
|
|
5314
5591
|
chalk6.green(
|
|
5315
|
-
` \u2705 ${
|
|
5592
|
+
` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
|
|
5316
5593
|
)
|
|
5317
5594
|
);
|
|
5318
5595
|
}
|
|
@@ -5365,7 +5642,7 @@ function normalizeSkillList2(raw) {
|
|
|
5365
5642
|
return [...deduped];
|
|
5366
5643
|
}
|
|
5367
5644
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
5368
|
-
const configPath =
|
|
5645
|
+
const configPath = path20.join(docsDir, ".lee-spec-kit.json");
|
|
5369
5646
|
if (!await fs15.pathExists(configPath)) {
|
|
5370
5647
|
return { changed: false, changedPaths: [] };
|
|
5371
5648
|
}
|
|
@@ -5386,7 +5663,7 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
5386
5663
|
const workflow = raw.workflow;
|
|
5387
5664
|
setIfMissing(workflow, "mode", "github", "workflow.mode");
|
|
5388
5665
|
setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
|
|
5389
|
-
setIfMissing(workflow, "taskCommitGate", "
|
|
5666
|
+
setIfMissing(workflow, "taskCommitGate", "warn", "workflow.taskCommitGate");
|
|
5390
5667
|
if (!isPlainObject(workflow.prePrReview)) {
|
|
5391
5668
|
workflow.prePrReview = {};
|
|
5392
5669
|
changedPaths.push("workflow.prePrReview");
|
|
@@ -5438,8 +5715,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
5438
5715
|
const files = await fs15.readdir(sourceDir);
|
|
5439
5716
|
let updatedCount = 0;
|
|
5440
5717
|
for (const file of files) {
|
|
5441
|
-
const sourcePath =
|
|
5442
|
-
const targetPath =
|
|
5718
|
+
const sourcePath = path20.join(sourceDir, file);
|
|
5719
|
+
const targetPath = path20.join(targetDir, file);
|
|
5443
5720
|
const stat = await fs15.stat(sourcePath);
|
|
5444
5721
|
if (stat.isFile()) {
|
|
5445
5722
|
if (protectedFiles.has(file)) {
|
|
@@ -5522,7 +5799,7 @@ function extractPorcelainPaths(line) {
|
|
|
5522
5799
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
5523
5800
|
const top = getGitTopLevel2(docsDir);
|
|
5524
5801
|
if (!top) return null;
|
|
5525
|
-
const rel =
|
|
5802
|
+
const rel = path20.relative(top, docsDir) || ".";
|
|
5526
5803
|
try {
|
|
5527
5804
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
5528
5805
|
cwd: top,
|
|
@@ -5534,7 +5811,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
5534
5811
|
}
|
|
5535
5812
|
const ignoredRelPaths = new Set(
|
|
5536
5813
|
ignoredAbsPaths.map(
|
|
5537
|
-
(absPath) => normalizeGitPath2(
|
|
5814
|
+
(absPath) => normalizeGitPath2(path20.relative(top, absPath) || ".")
|
|
5538
5815
|
)
|
|
5539
5816
|
);
|
|
5540
5817
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -5591,7 +5868,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
5591
5868
|
}
|
|
5592
5869
|
async function runConfig(options) {
|
|
5593
5870
|
const cwd = process.cwd();
|
|
5594
|
-
const targetCwd = options.dir ?
|
|
5871
|
+
const targetCwd = options.dir ? path20.resolve(cwd, options.dir) : cwd;
|
|
5595
5872
|
const config = await getConfig(targetCwd);
|
|
5596
5873
|
if (!config) {
|
|
5597
5874
|
throw createCliError(
|
|
@@ -5599,7 +5876,7 @@ async function runConfig(options) {
|
|
|
5599
5876
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
5600
5877
|
);
|
|
5601
5878
|
}
|
|
5602
|
-
const configPath =
|
|
5879
|
+
const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
|
|
5603
5880
|
if (!options.projectRoot) {
|
|
5604
5881
|
console.log();
|
|
5605
5882
|
console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
@@ -5615,6 +5892,7 @@ async function runConfig(options) {
|
|
|
5615
5892
|
console.log();
|
|
5616
5893
|
return;
|
|
5617
5894
|
}
|
|
5895
|
+
const projectRoot = options.projectRoot;
|
|
5618
5896
|
await withFileLock(
|
|
5619
5897
|
getDocsLockPath(config.docsDir),
|
|
5620
5898
|
async () => {
|
|
@@ -5668,13 +5946,13 @@ async function runConfig(options) {
|
|
|
5668
5946
|
}
|
|
5669
5947
|
assertAllowedComponent(targetComponent, components);
|
|
5670
5948
|
const currentRoot = typeof configFile.projectRoot === "object" && configFile.projectRoot ? configFile.projectRoot : {};
|
|
5671
|
-
currentRoot[targetComponent] =
|
|
5949
|
+
currentRoot[targetComponent] = projectRoot;
|
|
5672
5950
|
configFile.projectRoot = currentRoot;
|
|
5673
5951
|
console.log(
|
|
5674
5952
|
chalk6.green(
|
|
5675
5953
|
tr(config.lang, "cli", "config.projectRootSet", {
|
|
5676
5954
|
repo: targetComponent.toUpperCase(),
|
|
5677
|
-
path:
|
|
5955
|
+
path: projectRoot
|
|
5678
5956
|
})
|
|
5679
5957
|
)
|
|
5680
5958
|
);
|
|
@@ -5685,11 +5963,11 @@ async function runConfig(options) {
|
|
|
5685
5963
|
"`--component` is only valid for multi projectRoot updates."
|
|
5686
5964
|
);
|
|
5687
5965
|
}
|
|
5688
|
-
configFile.projectRoot =
|
|
5966
|
+
configFile.projectRoot = projectRoot;
|
|
5689
5967
|
console.log(
|
|
5690
5968
|
chalk6.green(
|
|
5691
5969
|
tr(config.lang, "cli", "config.projectRootSetSingle", {
|
|
5692
|
-
path:
|
|
5970
|
+
path: projectRoot
|
|
5693
5971
|
})
|
|
5694
5972
|
)
|
|
5695
5973
|
);
|
|
@@ -5758,6 +6036,7 @@ function getActionSummary(action) {
|
|
|
5758
6036
|
if (action.category === "pre_pr_review") return "Run pre-PR self review";
|
|
5759
6037
|
if (action.category === "pr_status_update") return "Update PR status";
|
|
5760
6038
|
if (action.category === "code_review") return "Process code review feedback";
|
|
6039
|
+
if (action.category === "user_request_replan") return "Handle a new user request first";
|
|
5761
6040
|
if (action.category === "task_execute") return "Proceed with task execution";
|
|
5762
6041
|
if (action.category === "review_fix_commit") return "Commit review feedback fixes";
|
|
5763
6042
|
if (action.category === "feature_done") return "Feature is complete";
|
|
@@ -5772,6 +6051,19 @@ function getActionSummary(action) {
|
|
|
5772
6051
|
return action.message;
|
|
5773
6052
|
}
|
|
5774
6053
|
function formatActionSummary(action) {
|
|
6054
|
+
const formatBranchCreateDetail = (command) => {
|
|
6055
|
+
const worktreeMatch = command.match(/\.worktrees\/([A-Za-z0-9._-]+)/);
|
|
6056
|
+
const branchMatch = command.match(/\bfeat\/([A-Za-z0-9._-]+)/);
|
|
6057
|
+
const worktree = worktreeMatch ? `.worktrees/${worktreeMatch[1]}` : null;
|
|
6058
|
+
const branch = branchMatch ? `feat/${branchMatch[1]}` : null;
|
|
6059
|
+
if (worktree && branch) {
|
|
6060
|
+
return `(${action.scope}) create or reuse worktree ${worktree} for branch ${branch}`;
|
|
6061
|
+
}
|
|
6062
|
+
if (branch) {
|
|
6063
|
+
return `(${action.scope}) create or reuse worktree for branch ${branch}`;
|
|
6064
|
+
}
|
|
6065
|
+
return `(${action.scope}) create or reuse feature branch worktree`;
|
|
6066
|
+
};
|
|
5775
6067
|
const extractCommitMessage = (command) => {
|
|
5776
6068
|
const doubleQuoted = command.match(/git\s+commit\s+-m\s+"((?:\\"|[^"])*)"/);
|
|
5777
6069
|
if (doubleQuoted) {
|
|
@@ -5784,6 +6076,9 @@ function formatActionSummary(action) {
|
|
|
5784
6076
|
return null;
|
|
5785
6077
|
};
|
|
5786
6078
|
if (action.type === "command") {
|
|
6079
|
+
if (action.category === "branch_create") {
|
|
6080
|
+
return formatBranchCreateDetail(action.cmd);
|
|
6081
|
+
}
|
|
5787
6082
|
const commitMessage = extractCommitMessage(action.cmd);
|
|
5788
6083
|
if (commitMessage) {
|
|
5789
6084
|
return `(${action.scope}) commit: ${commitMessage}`;
|
|
@@ -5965,42 +6260,42 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
5965
6260
|
{
|
|
5966
6261
|
id: "agents",
|
|
5967
6262
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
5968
|
-
relativePath: (_, lang) =>
|
|
6263
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "agents.md")
|
|
5969
6264
|
},
|
|
5970
6265
|
{
|
|
5971
6266
|
id: "git-workflow",
|
|
5972
6267
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
5973
|
-
relativePath: (_, lang) =>
|
|
6268
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "git-workflow.md")
|
|
5974
6269
|
},
|
|
5975
6270
|
{
|
|
5976
6271
|
id: "issue-doc",
|
|
5977
6272
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
5978
|
-
relativePath: (_, lang) =>
|
|
6273
|
+
relativePath: (_, lang) => path20.join(lang, "common", "features", "feature-base", "issue.md")
|
|
5979
6274
|
},
|
|
5980
6275
|
{
|
|
5981
6276
|
id: "pr-doc",
|
|
5982
6277
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
5983
|
-
relativePath: (_, lang) =>
|
|
6278
|
+
relativePath: (_, lang) => path20.join(lang, "common", "features", "feature-base", "pr.md")
|
|
5984
6279
|
},
|
|
5985
6280
|
{
|
|
5986
6281
|
id: "create-feature",
|
|
5987
6282
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
5988
|
-
relativePath: (_, lang) =>
|
|
6283
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
5989
6284
|
},
|
|
5990
6285
|
{
|
|
5991
6286
|
id: "execute-task",
|
|
5992
6287
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
5993
|
-
relativePath: (_, lang) =>
|
|
6288
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
5994
6289
|
},
|
|
5995
6290
|
{
|
|
5996
6291
|
id: "create-issue",
|
|
5997
6292
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
5998
|
-
relativePath: (_, lang) =>
|
|
6293
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
5999
6294
|
},
|
|
6000
6295
|
{
|
|
6001
6296
|
id: "create-pr",
|
|
6002
6297
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
6003
|
-
relativePath: (_, lang) =>
|
|
6298
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
6004
6299
|
}
|
|
6005
6300
|
];
|
|
6006
6301
|
var DOC_FOLLOWUPS = {
|
|
@@ -6036,7 +6331,8 @@ var CATEGORY_DOC_MAP = {
|
|
|
6036
6331
|
pre_pr_review: ["create-pr"],
|
|
6037
6332
|
pr_create: ["create-pr", "pr-doc", "git-workflow"],
|
|
6038
6333
|
pr_status_update: ["create-pr"],
|
|
6039
|
-
code_review: ["create-pr"]
|
|
6334
|
+
code_review: ["create-pr"],
|
|
6335
|
+
user_request_replan: ["agents", "execute-task"]
|
|
6040
6336
|
};
|
|
6041
6337
|
function getBuiltinDocIds() {
|
|
6042
6338
|
return BUILTIN_DOC_DEFINITIONS.map((doc) => doc.id);
|
|
@@ -6087,7 +6383,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
6087
6383
|
id: doc.id,
|
|
6088
6384
|
title: doc.title[lang],
|
|
6089
6385
|
relativePath,
|
|
6090
|
-
absolutePath:
|
|
6386
|
+
absolutePath: path20.join(templatesDir, relativePath)
|
|
6091
6387
|
};
|
|
6092
6388
|
});
|
|
6093
6389
|
}
|
|
@@ -6136,7 +6432,7 @@ function parseApprovalLabel(input, validLabels) {
|
|
|
6136
6432
|
function getApprovalTicketPaths(config) {
|
|
6137
6433
|
return {
|
|
6138
6434
|
runtimePath: getApprovalTicketStorePath(config.docsDir),
|
|
6139
|
-
legacyPath:
|
|
6435
|
+
legacyPath: path20.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
|
|
6140
6436
|
};
|
|
6141
6437
|
}
|
|
6142
6438
|
function getApprovalSessionId() {
|
|
@@ -6164,7 +6460,7 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
6164
6460
|
}
|
|
6165
6461
|
}
|
|
6166
6462
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
6167
|
-
await fs15.ensureDir(
|
|
6463
|
+
await fs15.ensureDir(path20.dirname(storePath));
|
|
6168
6464
|
await fs15.writeJson(storePath, payload, { spaces: 2 });
|
|
6169
6465
|
}
|
|
6170
6466
|
async function resolveApprovalTicketStoreAndPath(config, nowMs) {
|
|
@@ -7130,7 +7426,7 @@ async function runContext(featureName, options) {
|
|
|
7130
7426
|
if (f.issueNumber) {
|
|
7131
7427
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
7132
7428
|
}
|
|
7133
|
-
console.log(` \u2022 Path: ${
|
|
7429
|
+
console.log(` \u2022 Path: ${path20.relative(cwd, f.path)}`);
|
|
7134
7430
|
if (f.git.projectBranch) {
|
|
7135
7431
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
7136
7432
|
}
|
|
@@ -7460,7 +7756,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
7460
7756
|
]);
|
|
7461
7757
|
function formatPath(cwd, p) {
|
|
7462
7758
|
if (!p) return "";
|
|
7463
|
-
return
|
|
7759
|
+
return path20.isAbsolute(p) ? path20.relative(cwd, p) : p;
|
|
7464
7760
|
}
|
|
7465
7761
|
function detectPlaceholders(content) {
|
|
7466
7762
|
const patterns = [
|
|
@@ -7609,7 +7905,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
7609
7905
|
const placeholderContext = {
|
|
7610
7906
|
projectName: config.projectName,
|
|
7611
7907
|
featureName: f.slug,
|
|
7612
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
7908
|
+
featurePath: f.docs.featurePathFromDocs || path20.relative(config.docsDir, f.path),
|
|
7613
7909
|
repoType: f.type,
|
|
7614
7910
|
featureNumber
|
|
7615
7911
|
};
|
|
@@ -7619,7 +7915,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
7619
7915
|
"tasks.md"
|
|
7620
7916
|
];
|
|
7621
7917
|
for (const file of files) {
|
|
7622
|
-
const fullPath =
|
|
7918
|
+
const fullPath = path20.join(f.path, file);
|
|
7623
7919
|
if (!await fs15.pathExists(fullPath)) continue;
|
|
7624
7920
|
const original = await fs15.readFile(fullPath, "utf-8");
|
|
7625
7921
|
let next = original;
|
|
@@ -7664,7 +7960,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
7664
7960
|
const issues = [];
|
|
7665
7961
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
7666
7962
|
for (const dir of requiredDirs) {
|
|
7667
|
-
const p =
|
|
7963
|
+
const p = path20.join(config.docsDir, dir);
|
|
7668
7964
|
if (!await fs15.pathExists(p)) {
|
|
7669
7965
|
issues.push({
|
|
7670
7966
|
level: "error",
|
|
@@ -7674,7 +7970,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
7674
7970
|
});
|
|
7675
7971
|
}
|
|
7676
7972
|
}
|
|
7677
|
-
const configPath =
|
|
7973
|
+
const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
|
|
7678
7974
|
if (!await fs15.pathExists(configPath)) {
|
|
7679
7975
|
issues.push({
|
|
7680
7976
|
level: "warn",
|
|
@@ -7697,7 +7993,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7697
7993
|
}
|
|
7698
7994
|
const idMap = /* @__PURE__ */ new Map();
|
|
7699
7995
|
for (const f of features) {
|
|
7700
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
7996
|
+
const rel = f.docs.featurePathFromDocs || path20.relative(config.docsDir, f.path);
|
|
7701
7997
|
const id = f.id || "UNKNOWN";
|
|
7702
7998
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
7703
7999
|
idMap.get(id).push(rel);
|
|
@@ -7705,7 +8001,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7705
8001
|
if (!isInitialTemplateState) {
|
|
7706
8002
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
7707
8003
|
for (const file of featureDocs) {
|
|
7708
|
-
const p =
|
|
8004
|
+
const p = path20.join(f.path, file);
|
|
7709
8005
|
if (!await fs15.pathExists(p)) continue;
|
|
7710
8006
|
const content = await fs15.readFile(p, "utf-8");
|
|
7711
8007
|
const placeholders = detectPlaceholders(content);
|
|
@@ -7720,7 +8016,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7720
8016
|
});
|
|
7721
8017
|
}
|
|
7722
8018
|
if (decisionsPlaceholderMode !== "off") {
|
|
7723
|
-
const decisionsPath =
|
|
8019
|
+
const decisionsPath = path20.join(f.path, "decisions.md");
|
|
7724
8020
|
if (await fs15.pathExists(decisionsPath)) {
|
|
7725
8021
|
const content = await fs15.readFile(decisionsPath, "utf-8");
|
|
7726
8022
|
const placeholders = detectPlaceholders(content);
|
|
@@ -7749,7 +8045,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7749
8045
|
level: "warn",
|
|
7750
8046
|
code: "spec_status_unset",
|
|
7751
8047
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
7752
|
-
path: formatPath(cwd,
|
|
8048
|
+
path: formatPath(cwd, path20.join(f.path, "spec.md"))
|
|
7753
8049
|
});
|
|
7754
8050
|
}
|
|
7755
8051
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -7757,7 +8053,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7757
8053
|
level: "warn",
|
|
7758
8054
|
code: "plan_status_unset",
|
|
7759
8055
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
7760
|
-
path: formatPath(cwd,
|
|
8056
|
+
path: formatPath(cwd, path20.join(f.path, "plan.md"))
|
|
7761
8057
|
});
|
|
7762
8058
|
}
|
|
7763
8059
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -7765,7 +8061,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7765
8061
|
level: "warn",
|
|
7766
8062
|
code: "tasks_empty",
|
|
7767
8063
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
7768
|
-
path: formatPath(cwd,
|
|
8064
|
+
path: formatPath(cwd, path20.join(f.path, "tasks.md"))
|
|
7769
8065
|
});
|
|
7770
8066
|
}
|
|
7771
8067
|
if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
|
|
@@ -7773,7 +8069,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7773
8069
|
level: "warn",
|
|
7774
8070
|
code: "tasks_doc_status_missing",
|
|
7775
8071
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
7776
|
-
path: formatPath(cwd,
|
|
8072
|
+
path: formatPath(cwd, path20.join(f.path, "tasks.md"))
|
|
7777
8073
|
});
|
|
7778
8074
|
}
|
|
7779
8075
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -7781,7 +8077,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7781
8077
|
level: "warn",
|
|
7782
8078
|
code: "tasks_doc_status_unset",
|
|
7783
8079
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
7784
|
-
path: formatPath(cwd,
|
|
8080
|
+
path: formatPath(cwd, path20.join(f.path, "tasks.md"))
|
|
7785
8081
|
});
|
|
7786
8082
|
}
|
|
7787
8083
|
}
|
|
@@ -7805,7 +8101,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7805
8101
|
level: "warn",
|
|
7806
8102
|
code: "missing_feature_id",
|
|
7807
8103
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
7808
|
-
path: formatPath(cwd,
|
|
8104
|
+
path: formatPath(cwd, path20.join(config.docsDir, p))
|
|
7809
8105
|
});
|
|
7810
8106
|
}
|
|
7811
8107
|
return issues;
|
|
@@ -7927,7 +8223,7 @@ function doctorCommand(program2) {
|
|
|
7927
8223
|
}
|
|
7928
8224
|
console.log();
|
|
7929
8225
|
console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
|
|
7930
|
-
console.log(chalk6.gray(`- Docs: ${
|
|
8226
|
+
console.log(chalk6.gray(`- Docs: ${path20.relative(cwd, docsDir)}`));
|
|
7931
8227
|
console.log(chalk6.gray(`- Type: ${projectType}`));
|
|
7932
8228
|
console.log(chalk6.gray(`- Lang: ${lang}`));
|
|
7933
8229
|
console.log();
|
|
@@ -8100,7 +8396,7 @@ async function runView(featureName, options) {
|
|
|
8100
8396
|
}
|
|
8101
8397
|
console.log();
|
|
8102
8398
|
console.log(chalk6.bold("\u{1F4CA} Workflow View"));
|
|
8103
|
-
console.log(chalk6.gray(`- Docs: ${
|
|
8399
|
+
console.log(chalk6.gray(`- Docs: ${path20.relative(cwd, config.docsDir)}`));
|
|
8104
8400
|
console.log(
|
|
8105
8401
|
chalk6.gray(
|
|
8106
8402
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
@@ -8473,25 +8769,25 @@ function tg(lang, key, vars = {}) {
|
|
|
8473
8769
|
}
|
|
8474
8770
|
function detectGithubCliLangSync(cwd) {
|
|
8475
8771
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
8476
|
-
const startDirs = [explicitDocsDir ?
|
|
8772
|
+
const startDirs = [explicitDocsDir ? path20.resolve(explicitDocsDir) : "", path20.resolve(cwd)].filter(Boolean);
|
|
8477
8773
|
const scanOrder = [];
|
|
8478
8774
|
const seen = /* @__PURE__ */ new Set();
|
|
8479
8775
|
for (const start of startDirs) {
|
|
8480
8776
|
let current = start;
|
|
8481
8777
|
while (true) {
|
|
8482
|
-
const abs =
|
|
8778
|
+
const abs = path20.resolve(current);
|
|
8483
8779
|
if (!seen.has(abs)) {
|
|
8484
8780
|
scanOrder.push(abs);
|
|
8485
8781
|
seen.add(abs);
|
|
8486
8782
|
}
|
|
8487
|
-
const parent =
|
|
8783
|
+
const parent = path20.dirname(abs);
|
|
8488
8784
|
if (parent === abs) break;
|
|
8489
8785
|
current = parent;
|
|
8490
8786
|
}
|
|
8491
8787
|
}
|
|
8492
8788
|
for (const base of scanOrder) {
|
|
8493
|
-
for (const docsDir of [
|
|
8494
|
-
const configPath =
|
|
8789
|
+
for (const docsDir of [path20.join(base, "docs"), base]) {
|
|
8790
|
+
const configPath = path20.join(docsDir, ".lee-spec-kit.json");
|
|
8495
8791
|
if (fs15.existsSync(configPath)) {
|
|
8496
8792
|
try {
|
|
8497
8793
|
const parsed = fs15.readJsonSync(configPath);
|
|
@@ -8499,11 +8795,11 @@ function detectGithubCliLangSync(cwd) {
|
|
|
8499
8795
|
} catch {
|
|
8500
8796
|
}
|
|
8501
8797
|
}
|
|
8502
|
-
const agentsPath =
|
|
8503
|
-
const featuresPath =
|
|
8798
|
+
const agentsPath = path20.join(docsDir, "agents");
|
|
8799
|
+
const featuresPath = path20.join(docsDir, "features");
|
|
8504
8800
|
if (!fs15.existsSync(agentsPath) || !fs15.existsSync(featuresPath)) continue;
|
|
8505
8801
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
8506
|
-
const file =
|
|
8802
|
+
const file = path20.join(agentsPath, probe);
|
|
8507
8803
|
if (!fs15.existsSync(file)) continue;
|
|
8508
8804
|
try {
|
|
8509
8805
|
const content = fs15.readFileSync(file, "utf-8");
|
|
@@ -8601,7 +8897,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
8601
8897
|
}
|
|
8602
8898
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
8603
8899
|
const missing = relativePaths.filter(
|
|
8604
|
-
(relativePath) => !fs15.existsSync(
|
|
8900
|
+
(relativePath) => !fs15.existsSync(path20.join(docsDir, relativePath))
|
|
8605
8901
|
);
|
|
8606
8902
|
if (missing.length > 0) {
|
|
8607
8903
|
throw createCliError(
|
|
@@ -8611,13 +8907,13 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
8611
8907
|
}
|
|
8612
8908
|
}
|
|
8613
8909
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
8614
|
-
const key = `${
|
|
8910
|
+
const key = `${path20.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
8615
8911
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
8616
8912
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
8617
8913
|
}
|
|
8618
8914
|
function toBodyFilePath(raw, kind, docsDir, component) {
|
|
8619
|
-
const selected = raw?.trim() ||
|
|
8620
|
-
return
|
|
8915
|
+
const selected = raw?.trim() || path20.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
8916
|
+
return path20.resolve(selected);
|
|
8621
8917
|
}
|
|
8622
8918
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
8623
8919
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -8710,6 +9006,10 @@ function resolveGithubDocsCwd(config, feature) {
|
|
|
8710
9006
|
if (docsGitCwd) return docsGitCwd;
|
|
8711
9007
|
return config.docsDir;
|
|
8712
9008
|
}
|
|
9009
|
+
function shouldPushDocsSync(config) {
|
|
9010
|
+
if (config.docsRepo !== "standalone") return true;
|
|
9011
|
+
return config.pushDocs === true;
|
|
9012
|
+
}
|
|
8713
9013
|
function getFeatureDocPaths(feature) {
|
|
8714
9014
|
const featurePathFromDocs = feature.docs.featurePathFromDocs;
|
|
8715
9015
|
return {
|
|
@@ -9432,8 +9732,8 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
9432
9732
|
);
|
|
9433
9733
|
}
|
|
9434
9734
|
}
|
|
9435
|
-
function commitAndPushPath(cwd, absPath, message, lang) {
|
|
9436
|
-
const relativePath =
|
|
9735
|
+
function commitAndPushPath(cwd, absPath, message, lang, options) {
|
|
9736
|
+
const relativePath = path20.relative(cwd, absPath) || absPath;
|
|
9437
9737
|
const status = runProcessOrThrow(
|
|
9438
9738
|
"git",
|
|
9439
9739
|
["status", "--porcelain=v1", "--", relativePath],
|
|
@@ -9443,6 +9743,7 @@ function commitAndPushPath(cwd, absPath, message, lang) {
|
|
|
9443
9743
|
if (status.stdout.trim().length === 0) return;
|
|
9444
9744
|
runProcessOrThrow("git", ["add", "--", relativePath], cwd, tg(lang, "stageFileFailed"));
|
|
9445
9745
|
runProcessOrThrow("git", ["commit", "-m", message], cwd, tg(lang, "commitSyncFailed"));
|
|
9746
|
+
if (options?.pushToOrigin === false) return;
|
|
9446
9747
|
const branch = gitCurrentBranch(cwd, lang);
|
|
9447
9748
|
runProcessOrThrow(
|
|
9448
9749
|
"git",
|
|
@@ -9565,9 +9866,9 @@ function githubCommand(program2) {
|
|
|
9565
9866
|
[paths.specPath, paths.planPath, paths.tasksPath],
|
|
9566
9867
|
config.lang
|
|
9567
9868
|
);
|
|
9568
|
-
const specContent = await fs15.readFile(
|
|
9569
|
-
const planContent = await fs15.readFile(
|
|
9570
|
-
const tasksContent = await fs15.readFile(
|
|
9869
|
+
const specContent = await fs15.readFile(path20.join(config.docsDir, paths.specPath), "utf-8");
|
|
9870
|
+
const planContent = await fs15.readFile(path20.join(config.docsDir, paths.planPath), "utf-8");
|
|
9871
|
+
const tasksContent = await fs15.readFile(path20.join(config.docsDir, paths.tasksPath), "utf-8");
|
|
9571
9872
|
const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
|
|
9572
9873
|
const title = options.title?.trim() || tg(config.lang, "issueDefaultTitle", {
|
|
9573
9874
|
slug: feature.slug,
|
|
@@ -9605,7 +9906,7 @@ function githubCommand(program2) {
|
|
|
9605
9906
|
config.lang
|
|
9606
9907
|
);
|
|
9607
9908
|
} else {
|
|
9608
|
-
await fs15.ensureDir(
|
|
9909
|
+
await fs15.ensureDir(path20.dirname(bodyFile));
|
|
9609
9910
|
await fs15.writeFile(bodyFile, generatedBody, "utf-8");
|
|
9610
9911
|
}
|
|
9611
9912
|
let issueUrl;
|
|
@@ -9704,10 +10005,10 @@ function githubCommand(program2) {
|
|
|
9704
10005
|
const labels = parseLabels(options.labels, config.lang);
|
|
9705
10006
|
const paths = getFeatureDocPaths(feature);
|
|
9706
10007
|
ensureDocsExist(config.docsDir, [paths.specPath, paths.tasksPath], config.lang);
|
|
9707
|
-
const specContent = await fs15.readFile(
|
|
9708
|
-
const planPath =
|
|
10008
|
+
const specContent = await fs15.readFile(path20.join(config.docsDir, paths.specPath), "utf-8");
|
|
10009
|
+
const planPath = path20.join(config.docsDir, paths.planPath);
|
|
9709
10010
|
const planContent = await fs15.pathExists(planPath) ? await fs15.readFile(planPath, "utf-8") : "";
|
|
9710
|
-
const tasksContent = await fs15.readFile(
|
|
10011
|
+
const tasksContent = await fs15.readFile(path20.join(config.docsDir, paths.tasksPath), "utf-8");
|
|
9711
10012
|
const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
|
|
9712
10013
|
const defaultTitle = feature.issueNumber ? tg(config.lang, "prDefaultTitleWithIssue", {
|
|
9713
10014
|
issue: feature.issueNumber,
|
|
@@ -9750,13 +10051,14 @@ function githubCommand(program2) {
|
|
|
9750
10051
|
config.lang
|
|
9751
10052
|
);
|
|
9752
10053
|
} else {
|
|
9753
|
-
await fs15.ensureDir(
|
|
10054
|
+
await fs15.ensureDir(path20.dirname(bodyFile));
|
|
9754
10055
|
await fs15.writeFile(bodyFile, generatedBody, "utf-8");
|
|
9755
10056
|
}
|
|
9756
10057
|
const retryCount = toRetryCount(options.retry, config.lang);
|
|
9757
10058
|
let prUrl = options.pr?.trim() || "";
|
|
9758
10059
|
let mergedAttempts;
|
|
9759
10060
|
let syncChanged = false;
|
|
10061
|
+
const pushDocsSync = shouldPushDocsSync(config);
|
|
9760
10062
|
if (options.create) {
|
|
9761
10063
|
const projectGitCwd = resolveGithubProjectCwd(config, feature);
|
|
9762
10064
|
ensureNoTodoPlaceholders(body, tg(config.lang, "kindPr"), config.lang);
|
|
@@ -9789,6 +10091,9 @@ function githubCommand(program2) {
|
|
|
9789
10091
|
);
|
|
9790
10092
|
prUrl = created.stdout.trim();
|
|
9791
10093
|
}
|
|
10094
|
+
if (!prUrl && options.merge) {
|
|
10095
|
+
prUrl = (feature.pr.link || "").trim();
|
|
10096
|
+
}
|
|
9792
10097
|
if (!prUrl && options.merge) {
|
|
9793
10098
|
throw createCliError(
|
|
9794
10099
|
"INVALID_ARGUMENT",
|
|
@@ -9804,7 +10109,7 @@ function githubCommand(program2) {
|
|
|
9804
10109
|
}
|
|
9805
10110
|
if (prUrl && options.syncTasks !== false) {
|
|
9806
10111
|
const synced = syncTasksPrMetadata(
|
|
9807
|
-
|
|
10112
|
+
path20.join(config.docsDir, paths.tasksPath),
|
|
9808
10113
|
prUrl,
|
|
9809
10114
|
"Review",
|
|
9810
10115
|
config.lang
|
|
@@ -9823,7 +10128,8 @@ function githubCommand(program2) {
|
|
|
9823
10128
|
docsGitCwd,
|
|
9824
10129
|
synced.path,
|
|
9825
10130
|
message,
|
|
9826
|
-
config.lang
|
|
10131
|
+
config.lang,
|
|
10132
|
+
{ pushToOrigin: pushDocsSync }
|
|
9827
10133
|
);
|
|
9828
10134
|
}
|
|
9829
10135
|
}
|
|
@@ -9851,7 +10157,7 @@ function githubCommand(program2) {
|
|
|
9851
10157
|
);
|
|
9852
10158
|
if (prUrl && options.syncTasks !== false) {
|
|
9853
10159
|
const mergedSync = syncTasksPrMetadata(
|
|
9854
|
-
|
|
10160
|
+
path20.join(config.docsDir, paths.tasksPath),
|
|
9855
10161
|
prUrl,
|
|
9856
10162
|
"Approved",
|
|
9857
10163
|
config.lang
|
|
@@ -9869,7 +10175,8 @@ function githubCommand(program2) {
|
|
|
9869
10175
|
docsGitCwd,
|
|
9870
10176
|
mergedSync.path,
|
|
9871
10177
|
message,
|
|
9872
|
-
config.lang
|
|
10178
|
+
config.lang,
|
|
10179
|
+
{ pushToOrigin: pushDocsSync }
|
|
9873
10180
|
);
|
|
9874
10181
|
}
|
|
9875
10182
|
}
|
|
@@ -10056,7 +10363,7 @@ function docsCommand(program2) {
|
|
|
10056
10363
|
);
|
|
10057
10364
|
return;
|
|
10058
10365
|
}
|
|
10059
|
-
const relativeFromCwd =
|
|
10366
|
+
const relativeFromCwd = path20.relative(process.cwd(), loaded.entry.absolutePath);
|
|
10060
10367
|
console.log();
|
|
10061
10368
|
console.log(chalk6.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
10062
10369
|
console.log(
|
|
@@ -10134,7 +10441,7 @@ function detectCommand(program2) {
|
|
|
10134
10441
|
}
|
|
10135
10442
|
async function runDetect(options) {
|
|
10136
10443
|
const cwd = process.cwd();
|
|
10137
|
-
const targetCwd = options.dir ?
|
|
10444
|
+
const targetCwd = options.dir ? path20.resolve(cwd, options.dir) : cwd;
|
|
10138
10445
|
const config = await getConfig(targetCwd);
|
|
10139
10446
|
const detected = !!config;
|
|
10140
10447
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
@@ -10161,7 +10468,7 @@ async function runDetect(options) {
|
|
|
10161
10468
|
);
|
|
10162
10469
|
return;
|
|
10163
10470
|
}
|
|
10164
|
-
const configPath2 =
|
|
10471
|
+
const configPath2 = path20.join(config.docsDir, ".lee-spec-kit.json");
|
|
10165
10472
|
const configFilePresent2 = await fs15.pathExists(configPath2);
|
|
10166
10473
|
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
10167
10474
|
console.log(
|
|
@@ -10195,7 +10502,7 @@ async function runDetect(options) {
|
|
|
10195
10502
|
console.log();
|
|
10196
10503
|
return;
|
|
10197
10504
|
}
|
|
10198
|
-
const configPath =
|
|
10505
|
+
const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
|
|
10199
10506
|
const configFilePresent = await fs15.pathExists(configPath);
|
|
10200
10507
|
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
10201
10508
|
console.log(chalk6.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
@@ -10290,7 +10597,7 @@ async function countFeatureDirs(docsDir, projectType) {
|
|
|
10290
10597
|
ignore: ["**/feature-base/**"]
|
|
10291
10598
|
});
|
|
10292
10599
|
return dirs.map((value) => value.replace(/\\/g, "/").replace(/\/+$/, "")).filter((value) => {
|
|
10293
|
-
const base =
|
|
10600
|
+
const base = path20.posix.basename(value);
|
|
10294
10601
|
return !!base && base !== "feature-base";
|
|
10295
10602
|
}).length;
|
|
10296
10603
|
}
|
|
@@ -10302,7 +10609,7 @@ async function hasUserPrdFile(prdDir) {
|
|
|
10302
10609
|
absolute: false,
|
|
10303
10610
|
ignore: ["**/node_modules/**"]
|
|
10304
10611
|
});
|
|
10305
|
-
return files.some((relativePath) =>
|
|
10612
|
+
return files.some((relativePath) => path20.basename(relativePath).toLowerCase() !== "readme.md");
|
|
10306
10613
|
}
|
|
10307
10614
|
function finalizeChecks(checks) {
|
|
10308
10615
|
const summary = checks.reduce(
|
|
@@ -10431,7 +10738,7 @@ async function runOnboardChecks(config) {
|
|
|
10431
10738
|
});
|
|
10432
10739
|
}
|
|
10433
10740
|
}
|
|
10434
|
-
const constitutionPath =
|
|
10741
|
+
const constitutionPath = path20.join(docsDir, "agents", "constitution.md");
|
|
10435
10742
|
if (!await fs15.pathExists(constitutionPath)) {
|
|
10436
10743
|
checks.push({
|
|
10437
10744
|
id: "constitution_exists",
|
|
@@ -10469,7 +10776,7 @@ async function runOnboardChecks(config) {
|
|
|
10469
10776
|
});
|
|
10470
10777
|
}
|
|
10471
10778
|
}
|
|
10472
|
-
const customPath =
|
|
10779
|
+
const customPath = path20.join(docsDir, "agents", "custom.md");
|
|
10473
10780
|
if (await fs15.pathExists(customPath)) {
|
|
10474
10781
|
const content = await fs15.readFile(customPath, "utf-8");
|
|
10475
10782
|
if (hasTemplateMarkers(content)) {
|
|
@@ -10494,7 +10801,7 @@ async function runOnboardChecks(config) {
|
|
|
10494
10801
|
});
|
|
10495
10802
|
}
|
|
10496
10803
|
}
|
|
10497
|
-
const prdDir =
|
|
10804
|
+
const prdDir = path20.join(docsDir, "prd");
|
|
10498
10805
|
const featureCount = await countFeatureDirs(docsDir, config.projectType);
|
|
10499
10806
|
const prdReady = await hasUserPrdFile(prdDir);
|
|
10500
10807
|
if (!prdReady) {
|
|
@@ -10512,7 +10819,7 @@ async function runOnboardChecks(config) {
|
|
|
10512
10819
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
10513
10820
|
),
|
|
10514
10821
|
path: prdDir,
|
|
10515
|
-
suggestedCommand: `touch ${quotePath(
|
|
10822
|
+
suggestedCommand: `touch ${quotePath(path20.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
10516
10823
|
});
|
|
10517
10824
|
} else {
|
|
10518
10825
|
checks.push({
|
|
@@ -10725,11 +11032,11 @@ ${version}
|
|
|
10725
11032
|
}
|
|
10726
11033
|
return `${ascii}${footer}`;
|
|
10727
11034
|
}
|
|
10728
|
-
var CACHE_FILE =
|
|
11035
|
+
var CACHE_FILE = path20.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
10729
11036
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
10730
11037
|
function getCurrentVersion() {
|
|
10731
11038
|
try {
|
|
10732
|
-
const packageJsonPath =
|
|
11039
|
+
const packageJsonPath = path20.join(__dirname$1, "..", "package.json");
|
|
10733
11040
|
if (fs15.existsSync(packageJsonPath)) {
|
|
10734
11041
|
const pkg = fs15.readJsonSync(packageJsonPath);
|
|
10735
11042
|
return pkg.version;
|
|
@@ -10833,7 +11140,7 @@ function shouldCheckForUpdates() {
|
|
|
10833
11140
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
10834
11141
|
function getCliVersion() {
|
|
10835
11142
|
try {
|
|
10836
|
-
const packageJsonPath =
|
|
11143
|
+
const packageJsonPath = path20.join(__dirname$1, "..", "package.json");
|
|
10837
11144
|
if (fs15.existsSync(packageJsonPath)) {
|
|
10838
11145
|
const pkg = fs15.readJsonSync(packageJsonPath);
|
|
10839
11146
|
if (pkg?.version) return String(pkg.version);
|