lee-spec-kit 0.6.17 → 0.6.18
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 +459 -200
- package/package.json +8 -3
- package/templates/en/common/agents/git-workflow.md +8 -1
- package/templates/ko/common/agents/git-workflow.md +8 -1
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
|
|
@@ -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
|
},
|
|
@@ -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,7 @@ function isGitPathIgnored(cwd, relativePath) {
|
|
|
4040
4187
|
return void 0;
|
|
4041
4188
|
}
|
|
4042
4189
|
}
|
|
4190
|
+
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
4043
4191
|
function getGitTopLevel(cwd) {
|
|
4044
4192
|
try {
|
|
4045
4193
|
return execSync("git rev-parse --show-toplevel", {
|
|
@@ -4051,6 +4199,52 @@ function getGitTopLevel(cwd) {
|
|
|
4051
4199
|
return null;
|
|
4052
4200
|
}
|
|
4053
4201
|
}
|
|
4202
|
+
function listGitWorktrees(cwd) {
|
|
4203
|
+
const topLevel = getGitTopLevel(cwd) || cwd;
|
|
4204
|
+
const cacheKey = path20.resolve(topLevel);
|
|
4205
|
+
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
4206
|
+
if (cached) return cached;
|
|
4207
|
+
try {
|
|
4208
|
+
const out = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
4209
|
+
cwd: topLevel,
|
|
4210
|
+
encoding: "utf-8",
|
|
4211
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4212
|
+
});
|
|
4213
|
+
const entries = [];
|
|
4214
|
+
let current = null;
|
|
4215
|
+
for (const rawLine of out.split("\n")) {
|
|
4216
|
+
const line = rawLine.trim();
|
|
4217
|
+
if (!line) {
|
|
4218
|
+
if (current?.path) entries.push(current);
|
|
4219
|
+
current = null;
|
|
4220
|
+
continue;
|
|
4221
|
+
}
|
|
4222
|
+
if (line.startsWith("worktree ")) {
|
|
4223
|
+
if (current?.path) entries.push(current);
|
|
4224
|
+
current = { path: line.slice("worktree ".length).trim() };
|
|
4225
|
+
continue;
|
|
4226
|
+
}
|
|
4227
|
+
if (line.startsWith("branch ")) {
|
|
4228
|
+
if (!current) continue;
|
|
4229
|
+
const fullRef = line.slice("branch ".length).trim();
|
|
4230
|
+
current.branch = fullRef.replace(/^refs\/heads\//, "");
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
if (current?.path) entries.push(current);
|
|
4234
|
+
GIT_WORKTREE_CACHE.set(cacheKey, entries);
|
|
4235
|
+
return entries;
|
|
4236
|
+
} catch {
|
|
4237
|
+
return void 0;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
function findWorktreePathForBranch(cwd, branchName) {
|
|
4241
|
+
const target = branchName.trim();
|
|
4242
|
+
if (!target) return void 0;
|
|
4243
|
+
const entries = listGitWorktrees(cwd);
|
|
4244
|
+
if (!entries) return void 0;
|
|
4245
|
+
const match = entries.find((entry) => entry.branch === target);
|
|
4246
|
+
return match?.path;
|
|
4247
|
+
}
|
|
4054
4248
|
function resolveProjectGitCwd(config, repo, lang = config.lang ?? DEFAULT_LANG) {
|
|
4055
4249
|
const docsRepo = config.docsRepo;
|
|
4056
4250
|
if (docsRepo !== "standalone") {
|
|
@@ -4204,13 +4398,13 @@ function parsePrLink(value) {
|
|
|
4204
4398
|
return trimmed;
|
|
4205
4399
|
}
|
|
4206
4400
|
function normalizeGitPath(value) {
|
|
4207
|
-
return value.split(
|
|
4401
|
+
return value.split(path20.sep).join("/");
|
|
4208
4402
|
}
|
|
4209
4403
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
4210
|
-
const relativeDocsDir =
|
|
4404
|
+
const relativeDocsDir = path20.relative(projectGitCwd, docsDir);
|
|
4211
4405
|
if (!relativeDocsDir) return [];
|
|
4212
|
-
if (
|
|
4213
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
4406
|
+
if (path20.isAbsolute(relativeDocsDir)) return [];
|
|
4407
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path20.sep}`)) {
|
|
4214
4408
|
return [];
|
|
4215
4409
|
}
|
|
4216
4410
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(/\/+$/, "");
|
|
@@ -4232,6 +4426,27 @@ function uniqueNormalizedPaths(values) {
|
|
|
4232
4426
|
var PROJECT_DIRTY_STATUS_CACHE = /* @__PURE__ */ new Map();
|
|
4233
4427
|
var COMPONENT_STATUS_PATH_CACHE = /* @__PURE__ */ new Map();
|
|
4234
4428
|
var PR_REMOTE_STATUS_CACHE = /* @__PURE__ */ new Map();
|
|
4429
|
+
var FEATURE_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
4430
|
+
function resolveFeatureWorktreePath(projectGitCwd, issueNumber, slug, folderName) {
|
|
4431
|
+
const expectedBranches = [
|
|
4432
|
+
`feat/${issueNumber}-${slug}`,
|
|
4433
|
+
`feat/${issueNumber}-${folderName}`
|
|
4434
|
+
];
|
|
4435
|
+
for (const branchName of expectedBranches) {
|
|
4436
|
+
const cacheKey = `${projectGitCwd}::${branchName}`;
|
|
4437
|
+
let foundPath = FEATURE_WORKTREE_CACHE.get(cacheKey);
|
|
4438
|
+
if (typeof foundPath === "undefined") {
|
|
4439
|
+
foundPath = findWorktreePathForBranch(projectGitCwd, branchName) || null;
|
|
4440
|
+
FEATURE_WORKTREE_CACHE.set(cacheKey, foundPath);
|
|
4441
|
+
}
|
|
4442
|
+
if (!foundPath) continue;
|
|
4443
|
+
return {
|
|
4444
|
+
cwd: foundPath,
|
|
4445
|
+
branch: getCurrentBranch(foundPath) || branchName
|
|
4446
|
+
};
|
|
4447
|
+
}
|
|
4448
|
+
return void 0;
|
|
4449
|
+
}
|
|
4235
4450
|
function toUpperToken(value) {
|
|
4236
4451
|
if (typeof value !== "string") return void 0;
|
|
4237
4452
|
const normalized = value.trim().toUpperCase();
|
|
@@ -4274,7 +4489,7 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
|
|
|
4274
4489
|
"view",
|
|
4275
4490
|
prRef,
|
|
4276
4491
|
"--json",
|
|
4277
|
-
"reviewDecision,mergeStateStatus,isDraft,statusCheckRollup"
|
|
4492
|
+
"state,mergedAt,reviewDecision,mergeStateStatus,isDraft,statusCheckRollup"
|
|
4278
4493
|
],
|
|
4279
4494
|
{
|
|
4280
4495
|
cwd: projectGitCwd,
|
|
@@ -4289,6 +4504,9 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
|
|
|
4289
4504
|
return null;
|
|
4290
4505
|
}
|
|
4291
4506
|
const parsed = JSON.parse(raw);
|
|
4507
|
+
const state = toUpperToken(parsed.state);
|
|
4508
|
+
const mergedAt = typeof parsed.mergedAt === "string" && parsed.mergedAt.trim().length > 0 ? parsed.mergedAt.trim() : void 0;
|
|
4509
|
+
const isMerged = state === "MERGED" || !!mergedAt;
|
|
4292
4510
|
const reviewDecision = toUpperToken(parsed.reviewDecision);
|
|
4293
4511
|
const mergeStateStatus = toUpperToken(parsed.mergeStateStatus);
|
|
4294
4512
|
const isDraft = parsed.isDraft === true;
|
|
@@ -4303,11 +4521,14 @@ function resolvePrRemoteStatus(prRef, projectGitCwd) {
|
|
|
4303
4521
|
const remote = {
|
|
4304
4522
|
source: "gh",
|
|
4305
4523
|
available: true,
|
|
4524
|
+
state,
|
|
4525
|
+
mergedAt,
|
|
4526
|
+
isMerged,
|
|
4306
4527
|
reviewDecision,
|
|
4307
4528
|
mergeStateStatus,
|
|
4308
4529
|
isDraft,
|
|
4309
4530
|
hasBlockingReview: reviewDecision === "CHANGES_REQUESTED" || reviewDecision === "REVIEW_REQUIRED",
|
|
4310
|
-
mergeBlocked: isDraft || isMergeBlockedState(mergeStateStatus),
|
|
4531
|
+
mergeBlocked: !isMerged && (isDraft || isMergeBlockedState(mergeStateStatus)),
|
|
4311
4532
|
failingChecks,
|
|
4312
4533
|
pendingChecks
|
|
4313
4534
|
};
|
|
@@ -4331,10 +4552,10 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
|
|
|
4331
4552
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
4332
4553
|
candidates.map((candidate) => {
|
|
4333
4554
|
if (!candidate) return "";
|
|
4334
|
-
if (!
|
|
4335
|
-
const relative =
|
|
4555
|
+
if (!path20.isAbsolute(candidate)) return candidate;
|
|
4556
|
+
const relative = path20.relative(projectGitCwd, candidate);
|
|
4336
4557
|
if (!relative) return "";
|
|
4337
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
4558
|
+
if (relative === ".." || relative.startsWith(`..${path20.sep}`)) return "";
|
|
4338
4559
|
return relative;
|
|
4339
4560
|
}).filter(Boolean)
|
|
4340
4561
|
);
|
|
@@ -4347,7 +4568,7 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
|
|
|
4347
4568
|
if (cached) return [...cached];
|
|
4348
4569
|
const existing = [];
|
|
4349
4570
|
for (const candidate of normalizedCandidates) {
|
|
4350
|
-
if (await fs15.pathExists(
|
|
4571
|
+
if (await fs15.pathExists(path20.join(projectGitCwd, candidate))) {
|
|
4351
4572
|
existing.push(candidate);
|
|
4352
4573
|
}
|
|
4353
4574
|
}
|
|
@@ -4436,15 +4657,15 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4436
4657
|
const lang = options.lang;
|
|
4437
4658
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
4438
4659
|
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
4439
|
-
const folderName =
|
|
4660
|
+
const folderName = path20.basename(featurePath);
|
|
4440
4661
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
4441
4662
|
const id = match?.[1];
|
|
4442
4663
|
const slug = match?.[2] || folderName;
|
|
4443
|
-
const specPath =
|
|
4444
|
-
const planPath =
|
|
4445
|
-
const tasksPath =
|
|
4446
|
-
const issueDocPath =
|
|
4447
|
-
const prDocPath =
|
|
4664
|
+
const specPath = path20.join(featurePath, "spec.md");
|
|
4665
|
+
const planPath = path20.join(featurePath, "plan.md");
|
|
4666
|
+
const tasksPath = path20.join(featurePath, "tasks.md");
|
|
4667
|
+
const issueDocPath = path20.join(featurePath, "issue.md");
|
|
4668
|
+
const prDocPath = path20.join(featurePath, "pr.md");
|
|
4448
4669
|
let specStatus;
|
|
4449
4670
|
let issueNumber;
|
|
4450
4671
|
const specExists = await fs15.pathExists(specPath);
|
|
@@ -4455,6 +4676,30 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4455
4676
|
const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
|
|
4456
4677
|
issueNumber = parseIssueNumber(issueValue);
|
|
4457
4678
|
}
|
|
4679
|
+
let effectiveProjectGitCwd = context.projectGitCwd;
|
|
4680
|
+
let effectiveProjectBranch = context.projectBranch;
|
|
4681
|
+
let effectiveProjectBranchAvailable = context.projectBranchAvailable;
|
|
4682
|
+
if (effectiveProjectGitCwd && issueNumber) {
|
|
4683
|
+
const alreadyExpected = isExpectedFeatureBranch(
|
|
4684
|
+
effectiveProjectBranch,
|
|
4685
|
+
issueNumber,
|
|
4686
|
+
slug,
|
|
4687
|
+
folderName
|
|
4688
|
+
);
|
|
4689
|
+
if (!alreadyExpected) {
|
|
4690
|
+
const worktree = resolveFeatureWorktreePath(
|
|
4691
|
+
effectiveProjectGitCwd,
|
|
4692
|
+
issueNumber,
|
|
4693
|
+
slug,
|
|
4694
|
+
folderName
|
|
4695
|
+
);
|
|
4696
|
+
if (worktree) {
|
|
4697
|
+
effectiveProjectGitCwd = worktree.cwd;
|
|
4698
|
+
effectiveProjectBranch = worktree.branch;
|
|
4699
|
+
effectiveProjectBranchAvailable = true;
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4458
4703
|
let planStatus;
|
|
4459
4704
|
const planExists = await fs15.pathExists(planPath);
|
|
4460
4705
|
if (planExists) {
|
|
@@ -4594,20 +4839,20 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4594
4839
|
prDocPrFieldExists = hasAnySpecKey(content, ["PR", "Pull Request"]);
|
|
4595
4840
|
prDocReviewStatusFieldExists = hasAnySpecKey(content, ["PR \uC0C1\uD0DC", "PR Status"]);
|
|
4596
4841
|
}
|
|
4597
|
-
if (workflowPolicy.requireReview && prStatus === "Review" && prLink &&
|
|
4598
|
-
prRemote = resolvePrRemoteStatus(prLink,
|
|
4842
|
+
if (workflowPolicy.requireReview && prStatus === "Review" && prLink && effectiveProjectGitCwd) {
|
|
4843
|
+
prRemote = resolvePrRemoteStatus(prLink, effectiveProjectGitCwd) || void 0;
|
|
4599
4844
|
}
|
|
4600
4845
|
const warnings = [];
|
|
4601
|
-
if (
|
|
4846
|
+
if (effectiveProjectBranchAvailable === false) {
|
|
4602
4847
|
warnings.push(tr(lang, "warnings", "projectBranchUnavailable"));
|
|
4603
4848
|
}
|
|
4604
4849
|
const onExpectedBranch = isExpectedFeatureBranch(
|
|
4605
|
-
|
|
4850
|
+
effectiveProjectBranch,
|
|
4606
4851
|
issueNumber,
|
|
4607
4852
|
slug,
|
|
4608
4853
|
folderName
|
|
4609
4854
|
);
|
|
4610
|
-
const relativeFeaturePathFromDocs =
|
|
4855
|
+
const relativeFeaturePathFromDocs = path20.relative(context.docsDir, featurePath);
|
|
4611
4856
|
const normalizedFeaturePathFromDocs = normalizeGitPath(relativeFeaturePathFromDocs);
|
|
4612
4857
|
const docsPathIgnored = typeof context.docsPathIgnored === "boolean" ? context.docsPathIgnored : isGitPathIgnored(context.docsGitCwd, normalizedFeaturePathFromDocs);
|
|
4613
4858
|
let docsHasUncommittedChanges = typeof context.docsHasUncommittedChanges === "boolean" ? context.docsHasUncommittedChanges : false;
|
|
@@ -4631,10 +4876,10 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4631
4876
|
}
|
|
4632
4877
|
let projectHasUncommittedChanges = typeof context.projectHasUncommittedChanges === "boolean" ? context.projectHasUncommittedChanges : false;
|
|
4633
4878
|
let projectStatusUnavailable = !!context.projectStatusUnavailable;
|
|
4634
|
-
if (typeof context.projectHasUncommittedChanges !== "boolean" &&
|
|
4879
|
+
if (typeof context.projectHasUncommittedChanges !== "boolean" && effectiveProjectGitCwd) {
|
|
4635
4880
|
const dirtyScopePolicy = resolveCodeDirtyScopePolicy(options.workflow, options.projectType);
|
|
4636
4881
|
const projectCacheKey = JSON.stringify({
|
|
4637
|
-
projectGitCwd:
|
|
4882
|
+
projectGitCwd: effectiveProjectGitCwd,
|
|
4638
4883
|
docsDir: context.docsDir,
|
|
4639
4884
|
type,
|
|
4640
4885
|
dirtyScopePolicy,
|
|
@@ -4648,19 +4893,19 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4648
4893
|
let projectStatusPaths = [];
|
|
4649
4894
|
if (dirtyScopePolicy === "component" && type !== "single") {
|
|
4650
4895
|
const componentStatusPaths = await resolveComponentStatusPaths(
|
|
4651
|
-
|
|
4896
|
+
effectiveProjectGitCwd,
|
|
4652
4897
|
type,
|
|
4653
4898
|
options.workflow
|
|
4654
4899
|
);
|
|
4655
|
-
projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(
|
|
4900
|
+
projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(effectiveProjectGitCwd, context.docsDir);
|
|
4656
4901
|
} else {
|
|
4657
4902
|
projectStatusPaths = resolveProjectStatusPaths(
|
|
4658
|
-
|
|
4903
|
+
effectiveProjectGitCwd,
|
|
4659
4904
|
context.docsDir
|
|
4660
4905
|
);
|
|
4661
4906
|
}
|
|
4662
4907
|
const projectStatus = getGitStatusPorcelain(
|
|
4663
|
-
|
|
4908
|
+
effectiveProjectGitCwd,
|
|
4664
4909
|
projectStatusPaths
|
|
4665
4910
|
);
|
|
4666
4911
|
projectStatusUnavailable = projectStatus === void 0;
|
|
@@ -4829,10 +5074,10 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
4829
5074
|
pr: { link: prLink, status: prStatus, remote: prRemote },
|
|
4830
5075
|
git: {
|
|
4831
5076
|
docsBranch: context.docsBranch,
|
|
4832
|
-
projectBranch:
|
|
4833
|
-
projectBranchAvailable:
|
|
5077
|
+
projectBranch: effectiveProjectBranch,
|
|
5078
|
+
projectBranchAvailable: effectiveProjectBranchAvailable,
|
|
4834
5079
|
docsGitCwd: context.docsGitCwd,
|
|
4835
|
-
projectGitCwd:
|
|
5080
|
+
projectGitCwd: effectiveProjectGitCwd,
|
|
4836
5081
|
onExpectedBranch,
|
|
4837
5082
|
docsEverCommitted,
|
|
4838
5083
|
docsHasUncommittedChanges,
|
|
@@ -5005,7 +5250,7 @@ async function scanFeatures(config) {
|
|
|
5005
5250
|
}
|
|
5006
5251
|
}
|
|
5007
5252
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
5008
|
-
(dir) => normalizeRelPath(
|
|
5253
|
+
(dir) => normalizeRelPath(path20.relative(config.docsDir, dir))
|
|
5009
5254
|
);
|
|
5010
5255
|
const docsGitMeta = buildDocsFeatureGitMeta(config.docsDir, relativeFeaturePaths);
|
|
5011
5256
|
const parseTargets = config.projectType === "single" ? [{ type: "single", dirs: componentFeatureDirs.get("single") || [] }] : resolveProjectComponents(config.projectType, config.components).map((component) => ({
|
|
@@ -5016,7 +5261,7 @@ async function scanFeatures(config) {
|
|
|
5016
5261
|
const parsed = await Promise.all(
|
|
5017
5262
|
target.dirs.map(async (dir) => {
|
|
5018
5263
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
5019
|
-
|
|
5264
|
+
path20.relative(config.docsDir, dir)
|
|
5020
5265
|
);
|
|
5021
5266
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
5022
5267
|
return parseFeature(
|
|
@@ -5085,13 +5330,13 @@ async function runStatus(options) {
|
|
|
5085
5330
|
);
|
|
5086
5331
|
}
|
|
5087
5332
|
const { docsDir, projectType, projectName, lang } = config;
|
|
5088
|
-
const featuresDir =
|
|
5333
|
+
const featuresDir = path20.join(docsDir, "features");
|
|
5089
5334
|
const scan = await scanFeatures(config);
|
|
5090
5335
|
const features = [];
|
|
5091
5336
|
const idMap = /* @__PURE__ */ new Map();
|
|
5092
5337
|
for (const f of scan.features) {
|
|
5093
5338
|
const id = f.id || "UNKNOWN";
|
|
5094
|
-
const relPath =
|
|
5339
|
+
const relPath = path20.relative(docsDir, f.path);
|
|
5095
5340
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
5096
5341
|
idMap.get(id).push(relPath);
|
|
5097
5342
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -5172,7 +5417,7 @@ async function runStatus(options) {
|
|
|
5172
5417
|
}
|
|
5173
5418
|
console.log();
|
|
5174
5419
|
if (options.write) {
|
|
5175
|
-
const outputPath =
|
|
5420
|
+
const outputPath = path20.join(featuresDir, "status.md");
|
|
5176
5421
|
const date = getLocalDateString();
|
|
5177
5422
|
const content = [
|
|
5178
5423
|
"# Feature Status",
|
|
@@ -5200,7 +5445,7 @@ function escapeRegExp2(value) {
|
|
|
5200
5445
|
}
|
|
5201
5446
|
async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
|
|
5202
5447
|
try {
|
|
5203
|
-
const specPath =
|
|
5448
|
+
const specPath = path20.join(featureDir, "spec.md");
|
|
5204
5449
|
if (!await fs15.pathExists(specPath)) return fallbackSlug;
|
|
5205
5450
|
const content = await fs15.readFile(specPath, "utf-8");
|
|
5206
5451
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
@@ -5282,10 +5527,10 @@ async function runUpdate(options) {
|
|
|
5282
5527
|
console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
5283
5528
|
}
|
|
5284
5529
|
if (agentsMode === "all") {
|
|
5285
|
-
const commonAgentsBase =
|
|
5286
|
-
const targetAgentsBase =
|
|
5287
|
-
const commonAgents =
|
|
5288
|
-
const targetAgents =
|
|
5530
|
+
const commonAgentsBase = path20.join(templatesDir, lang, "common", "agents");
|
|
5531
|
+
const targetAgentsBase = path20.join(docsDir, "agents");
|
|
5532
|
+
const commonAgents = commonAgentsBase;
|
|
5533
|
+
const targetAgents = targetAgentsBase;
|
|
5289
5534
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
5290
5535
|
const projectName = config.projectName ?? "{{projectName}}";
|
|
5291
5536
|
const commonReplacements = {
|
|
@@ -5312,7 +5557,7 @@ async function runUpdate(options) {
|
|
|
5312
5557
|
}
|
|
5313
5558
|
console.log(
|
|
5314
5559
|
chalk6.green(
|
|
5315
|
-
` \u2705 ${
|
|
5560
|
+
` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`
|
|
5316
5561
|
)
|
|
5317
5562
|
);
|
|
5318
5563
|
}
|
|
@@ -5365,7 +5610,7 @@ function normalizeSkillList2(raw) {
|
|
|
5365
5610
|
return [...deduped];
|
|
5366
5611
|
}
|
|
5367
5612
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
5368
|
-
const configPath =
|
|
5613
|
+
const configPath = path20.join(docsDir, ".lee-spec-kit.json");
|
|
5369
5614
|
if (!await fs15.pathExists(configPath)) {
|
|
5370
5615
|
return { changed: false, changedPaths: [] };
|
|
5371
5616
|
}
|
|
@@ -5386,7 +5631,7 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
5386
5631
|
const workflow = raw.workflow;
|
|
5387
5632
|
setIfMissing(workflow, "mode", "github", "workflow.mode");
|
|
5388
5633
|
setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
|
|
5389
|
-
setIfMissing(workflow, "taskCommitGate", "
|
|
5634
|
+
setIfMissing(workflow, "taskCommitGate", "warn", "workflow.taskCommitGate");
|
|
5390
5635
|
if (!isPlainObject(workflow.prePrReview)) {
|
|
5391
5636
|
workflow.prePrReview = {};
|
|
5392
5637
|
changedPaths.push("workflow.prePrReview");
|
|
@@ -5438,8 +5683,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
5438
5683
|
const files = await fs15.readdir(sourceDir);
|
|
5439
5684
|
let updatedCount = 0;
|
|
5440
5685
|
for (const file of files) {
|
|
5441
|
-
const sourcePath =
|
|
5442
|
-
const targetPath =
|
|
5686
|
+
const sourcePath = path20.join(sourceDir, file);
|
|
5687
|
+
const targetPath = path20.join(targetDir, file);
|
|
5443
5688
|
const stat = await fs15.stat(sourcePath);
|
|
5444
5689
|
if (stat.isFile()) {
|
|
5445
5690
|
if (protectedFiles.has(file)) {
|
|
@@ -5522,7 +5767,7 @@ function extractPorcelainPaths(line) {
|
|
|
5522
5767
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
5523
5768
|
const top = getGitTopLevel2(docsDir);
|
|
5524
5769
|
if (!top) return null;
|
|
5525
|
-
const rel =
|
|
5770
|
+
const rel = path20.relative(top, docsDir) || ".";
|
|
5526
5771
|
try {
|
|
5527
5772
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
5528
5773
|
cwd: top,
|
|
@@ -5534,7 +5779,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
5534
5779
|
}
|
|
5535
5780
|
const ignoredRelPaths = new Set(
|
|
5536
5781
|
ignoredAbsPaths.map(
|
|
5537
|
-
(absPath) => normalizeGitPath2(
|
|
5782
|
+
(absPath) => normalizeGitPath2(path20.relative(top, absPath) || ".")
|
|
5538
5783
|
)
|
|
5539
5784
|
);
|
|
5540
5785
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -5591,7 +5836,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
5591
5836
|
}
|
|
5592
5837
|
async function runConfig(options) {
|
|
5593
5838
|
const cwd = process.cwd();
|
|
5594
|
-
const targetCwd = options.dir ?
|
|
5839
|
+
const targetCwd = options.dir ? path20.resolve(cwd, options.dir) : cwd;
|
|
5595
5840
|
const config = await getConfig(targetCwd);
|
|
5596
5841
|
if (!config) {
|
|
5597
5842
|
throw createCliError(
|
|
@@ -5599,7 +5844,7 @@ async function runConfig(options) {
|
|
|
5599
5844
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
5600
5845
|
);
|
|
5601
5846
|
}
|
|
5602
|
-
const configPath =
|
|
5847
|
+
const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
|
|
5603
5848
|
if (!options.projectRoot) {
|
|
5604
5849
|
console.log();
|
|
5605
5850
|
console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
@@ -5615,6 +5860,7 @@ async function runConfig(options) {
|
|
|
5615
5860
|
console.log();
|
|
5616
5861
|
return;
|
|
5617
5862
|
}
|
|
5863
|
+
const projectRoot = options.projectRoot;
|
|
5618
5864
|
await withFileLock(
|
|
5619
5865
|
getDocsLockPath(config.docsDir),
|
|
5620
5866
|
async () => {
|
|
@@ -5668,13 +5914,13 @@ async function runConfig(options) {
|
|
|
5668
5914
|
}
|
|
5669
5915
|
assertAllowedComponent(targetComponent, components);
|
|
5670
5916
|
const currentRoot = typeof configFile.projectRoot === "object" && configFile.projectRoot ? configFile.projectRoot : {};
|
|
5671
|
-
currentRoot[targetComponent] =
|
|
5917
|
+
currentRoot[targetComponent] = projectRoot;
|
|
5672
5918
|
configFile.projectRoot = currentRoot;
|
|
5673
5919
|
console.log(
|
|
5674
5920
|
chalk6.green(
|
|
5675
5921
|
tr(config.lang, "cli", "config.projectRootSet", {
|
|
5676
5922
|
repo: targetComponent.toUpperCase(),
|
|
5677
|
-
path:
|
|
5923
|
+
path: projectRoot
|
|
5678
5924
|
})
|
|
5679
5925
|
)
|
|
5680
5926
|
);
|
|
@@ -5685,11 +5931,11 @@ async function runConfig(options) {
|
|
|
5685
5931
|
"`--component` is only valid for multi projectRoot updates."
|
|
5686
5932
|
);
|
|
5687
5933
|
}
|
|
5688
|
-
configFile.projectRoot =
|
|
5934
|
+
configFile.projectRoot = projectRoot;
|
|
5689
5935
|
console.log(
|
|
5690
5936
|
chalk6.green(
|
|
5691
5937
|
tr(config.lang, "cli", "config.projectRootSetSingle", {
|
|
5692
|
-
path:
|
|
5938
|
+
path: projectRoot
|
|
5693
5939
|
})
|
|
5694
5940
|
)
|
|
5695
5941
|
);
|
|
@@ -5758,6 +6004,7 @@ function getActionSummary(action) {
|
|
|
5758
6004
|
if (action.category === "pre_pr_review") return "Run pre-PR self review";
|
|
5759
6005
|
if (action.category === "pr_status_update") return "Update PR status";
|
|
5760
6006
|
if (action.category === "code_review") return "Process code review feedback";
|
|
6007
|
+
if (action.category === "user_request_replan") return "Handle a new user request first";
|
|
5761
6008
|
if (action.category === "task_execute") return "Proceed with task execution";
|
|
5762
6009
|
if (action.category === "review_fix_commit") return "Commit review feedback fixes";
|
|
5763
6010
|
if (action.category === "feature_done") return "Feature is complete";
|
|
@@ -5965,42 +6212,42 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
5965
6212
|
{
|
|
5966
6213
|
id: "agents",
|
|
5967
6214
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
5968
|
-
relativePath: (_, lang) =>
|
|
6215
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "agents.md")
|
|
5969
6216
|
},
|
|
5970
6217
|
{
|
|
5971
6218
|
id: "git-workflow",
|
|
5972
6219
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
5973
|
-
relativePath: (_, lang) =>
|
|
6220
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "git-workflow.md")
|
|
5974
6221
|
},
|
|
5975
6222
|
{
|
|
5976
6223
|
id: "issue-doc",
|
|
5977
6224
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
5978
|
-
relativePath: (_, lang) =>
|
|
6225
|
+
relativePath: (_, lang) => path20.join(lang, "common", "features", "feature-base", "issue.md")
|
|
5979
6226
|
},
|
|
5980
6227
|
{
|
|
5981
6228
|
id: "pr-doc",
|
|
5982
6229
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
5983
|
-
relativePath: (_, lang) =>
|
|
6230
|
+
relativePath: (_, lang) => path20.join(lang, "common", "features", "feature-base", "pr.md")
|
|
5984
6231
|
},
|
|
5985
6232
|
{
|
|
5986
6233
|
id: "create-feature",
|
|
5987
6234
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
5988
|
-
relativePath: (_, lang) =>
|
|
6235
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
5989
6236
|
},
|
|
5990
6237
|
{
|
|
5991
6238
|
id: "execute-task",
|
|
5992
6239
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
5993
|
-
relativePath: (_, lang) =>
|
|
6240
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
5994
6241
|
},
|
|
5995
6242
|
{
|
|
5996
6243
|
id: "create-issue",
|
|
5997
6244
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
5998
|
-
relativePath: (_, lang) =>
|
|
6245
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
5999
6246
|
},
|
|
6000
6247
|
{
|
|
6001
6248
|
id: "create-pr",
|
|
6002
6249
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
6003
|
-
relativePath: (_, lang) =>
|
|
6250
|
+
relativePath: (_, lang) => path20.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
6004
6251
|
}
|
|
6005
6252
|
];
|
|
6006
6253
|
var DOC_FOLLOWUPS = {
|
|
@@ -6036,7 +6283,8 @@ var CATEGORY_DOC_MAP = {
|
|
|
6036
6283
|
pre_pr_review: ["create-pr"],
|
|
6037
6284
|
pr_create: ["create-pr", "pr-doc", "git-workflow"],
|
|
6038
6285
|
pr_status_update: ["create-pr"],
|
|
6039
|
-
code_review: ["create-pr"]
|
|
6286
|
+
code_review: ["create-pr"],
|
|
6287
|
+
user_request_replan: ["agents", "execute-task"]
|
|
6040
6288
|
};
|
|
6041
6289
|
function getBuiltinDocIds() {
|
|
6042
6290
|
return BUILTIN_DOC_DEFINITIONS.map((doc) => doc.id);
|
|
@@ -6087,7 +6335,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
6087
6335
|
id: doc.id,
|
|
6088
6336
|
title: doc.title[lang],
|
|
6089
6337
|
relativePath,
|
|
6090
|
-
absolutePath:
|
|
6338
|
+
absolutePath: path20.join(templatesDir, relativePath)
|
|
6091
6339
|
};
|
|
6092
6340
|
});
|
|
6093
6341
|
}
|
|
@@ -6136,7 +6384,7 @@ function parseApprovalLabel(input, validLabels) {
|
|
|
6136
6384
|
function getApprovalTicketPaths(config) {
|
|
6137
6385
|
return {
|
|
6138
6386
|
runtimePath: getApprovalTicketStorePath(config.docsDir),
|
|
6139
|
-
legacyPath:
|
|
6387
|
+
legacyPath: path20.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
|
|
6140
6388
|
};
|
|
6141
6389
|
}
|
|
6142
6390
|
function getApprovalSessionId() {
|
|
@@ -6164,7 +6412,7 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
6164
6412
|
}
|
|
6165
6413
|
}
|
|
6166
6414
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
6167
|
-
await fs15.ensureDir(
|
|
6415
|
+
await fs15.ensureDir(path20.dirname(storePath));
|
|
6168
6416
|
await fs15.writeJson(storePath, payload, { spaces: 2 });
|
|
6169
6417
|
}
|
|
6170
6418
|
async function resolveApprovalTicketStoreAndPath(config, nowMs) {
|
|
@@ -7130,7 +7378,7 @@ async function runContext(featureName, options) {
|
|
|
7130
7378
|
if (f.issueNumber) {
|
|
7131
7379
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
7132
7380
|
}
|
|
7133
|
-
console.log(` \u2022 Path: ${
|
|
7381
|
+
console.log(` \u2022 Path: ${path20.relative(cwd, f.path)}`);
|
|
7134
7382
|
if (f.git.projectBranch) {
|
|
7135
7383
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
7136
7384
|
}
|
|
@@ -7460,7 +7708,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
7460
7708
|
]);
|
|
7461
7709
|
function formatPath(cwd, p) {
|
|
7462
7710
|
if (!p) return "";
|
|
7463
|
-
return
|
|
7711
|
+
return path20.isAbsolute(p) ? path20.relative(cwd, p) : p;
|
|
7464
7712
|
}
|
|
7465
7713
|
function detectPlaceholders(content) {
|
|
7466
7714
|
const patterns = [
|
|
@@ -7609,7 +7857,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
7609
7857
|
const placeholderContext = {
|
|
7610
7858
|
projectName: config.projectName,
|
|
7611
7859
|
featureName: f.slug,
|
|
7612
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
7860
|
+
featurePath: f.docs.featurePathFromDocs || path20.relative(config.docsDir, f.path),
|
|
7613
7861
|
repoType: f.type,
|
|
7614
7862
|
featureNumber
|
|
7615
7863
|
};
|
|
@@ -7619,7 +7867,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
7619
7867
|
"tasks.md"
|
|
7620
7868
|
];
|
|
7621
7869
|
for (const file of files) {
|
|
7622
|
-
const fullPath =
|
|
7870
|
+
const fullPath = path20.join(f.path, file);
|
|
7623
7871
|
if (!await fs15.pathExists(fullPath)) continue;
|
|
7624
7872
|
const original = await fs15.readFile(fullPath, "utf-8");
|
|
7625
7873
|
let next = original;
|
|
@@ -7664,7 +7912,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
7664
7912
|
const issues = [];
|
|
7665
7913
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
7666
7914
|
for (const dir of requiredDirs) {
|
|
7667
|
-
const p =
|
|
7915
|
+
const p = path20.join(config.docsDir, dir);
|
|
7668
7916
|
if (!await fs15.pathExists(p)) {
|
|
7669
7917
|
issues.push({
|
|
7670
7918
|
level: "error",
|
|
@@ -7674,7 +7922,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
7674
7922
|
});
|
|
7675
7923
|
}
|
|
7676
7924
|
}
|
|
7677
|
-
const configPath =
|
|
7925
|
+
const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
|
|
7678
7926
|
if (!await fs15.pathExists(configPath)) {
|
|
7679
7927
|
issues.push({
|
|
7680
7928
|
level: "warn",
|
|
@@ -7697,7 +7945,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7697
7945
|
}
|
|
7698
7946
|
const idMap = /* @__PURE__ */ new Map();
|
|
7699
7947
|
for (const f of features) {
|
|
7700
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
7948
|
+
const rel = f.docs.featurePathFromDocs || path20.relative(config.docsDir, f.path);
|
|
7701
7949
|
const id = f.id || "UNKNOWN";
|
|
7702
7950
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
7703
7951
|
idMap.get(id).push(rel);
|
|
@@ -7705,7 +7953,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7705
7953
|
if (!isInitialTemplateState) {
|
|
7706
7954
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
7707
7955
|
for (const file of featureDocs) {
|
|
7708
|
-
const p =
|
|
7956
|
+
const p = path20.join(f.path, file);
|
|
7709
7957
|
if (!await fs15.pathExists(p)) continue;
|
|
7710
7958
|
const content = await fs15.readFile(p, "utf-8");
|
|
7711
7959
|
const placeholders = detectPlaceholders(content);
|
|
@@ -7720,7 +7968,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7720
7968
|
});
|
|
7721
7969
|
}
|
|
7722
7970
|
if (decisionsPlaceholderMode !== "off") {
|
|
7723
|
-
const decisionsPath =
|
|
7971
|
+
const decisionsPath = path20.join(f.path, "decisions.md");
|
|
7724
7972
|
if (await fs15.pathExists(decisionsPath)) {
|
|
7725
7973
|
const content = await fs15.readFile(decisionsPath, "utf-8");
|
|
7726
7974
|
const placeholders = detectPlaceholders(content);
|
|
@@ -7749,7 +7997,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7749
7997
|
level: "warn",
|
|
7750
7998
|
code: "spec_status_unset",
|
|
7751
7999
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
7752
|
-
path: formatPath(cwd,
|
|
8000
|
+
path: formatPath(cwd, path20.join(f.path, "spec.md"))
|
|
7753
8001
|
});
|
|
7754
8002
|
}
|
|
7755
8003
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -7757,7 +8005,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7757
8005
|
level: "warn",
|
|
7758
8006
|
code: "plan_status_unset",
|
|
7759
8007
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
7760
|
-
path: formatPath(cwd,
|
|
8008
|
+
path: formatPath(cwd, path20.join(f.path, "plan.md"))
|
|
7761
8009
|
});
|
|
7762
8010
|
}
|
|
7763
8011
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -7765,7 +8013,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7765
8013
|
level: "warn",
|
|
7766
8014
|
code: "tasks_empty",
|
|
7767
8015
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
7768
|
-
path: formatPath(cwd,
|
|
8016
|
+
path: formatPath(cwd, path20.join(f.path, "tasks.md"))
|
|
7769
8017
|
});
|
|
7770
8018
|
}
|
|
7771
8019
|
if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
|
|
@@ -7773,7 +8021,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7773
8021
|
level: "warn",
|
|
7774
8022
|
code: "tasks_doc_status_missing",
|
|
7775
8023
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
7776
|
-
path: formatPath(cwd,
|
|
8024
|
+
path: formatPath(cwd, path20.join(f.path, "tasks.md"))
|
|
7777
8025
|
});
|
|
7778
8026
|
}
|
|
7779
8027
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -7781,7 +8029,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7781
8029
|
level: "warn",
|
|
7782
8030
|
code: "tasks_doc_status_unset",
|
|
7783
8031
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
7784
|
-
path: formatPath(cwd,
|
|
8032
|
+
path: formatPath(cwd, path20.join(f.path, "tasks.md"))
|
|
7785
8033
|
});
|
|
7786
8034
|
}
|
|
7787
8035
|
}
|
|
@@ -7805,7 +8053,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
7805
8053
|
level: "warn",
|
|
7806
8054
|
code: "missing_feature_id",
|
|
7807
8055
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
7808
|
-
path: formatPath(cwd,
|
|
8056
|
+
path: formatPath(cwd, path20.join(config.docsDir, p))
|
|
7809
8057
|
});
|
|
7810
8058
|
}
|
|
7811
8059
|
return issues;
|
|
@@ -7927,7 +8175,7 @@ function doctorCommand(program2) {
|
|
|
7927
8175
|
}
|
|
7928
8176
|
console.log();
|
|
7929
8177
|
console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
|
|
7930
|
-
console.log(chalk6.gray(`- Docs: ${
|
|
8178
|
+
console.log(chalk6.gray(`- Docs: ${path20.relative(cwd, docsDir)}`));
|
|
7931
8179
|
console.log(chalk6.gray(`- Type: ${projectType}`));
|
|
7932
8180
|
console.log(chalk6.gray(`- Lang: ${lang}`));
|
|
7933
8181
|
console.log();
|
|
@@ -8100,7 +8348,7 @@ async function runView(featureName, options) {
|
|
|
8100
8348
|
}
|
|
8101
8349
|
console.log();
|
|
8102
8350
|
console.log(chalk6.bold("\u{1F4CA} Workflow View"));
|
|
8103
|
-
console.log(chalk6.gray(`- Docs: ${
|
|
8351
|
+
console.log(chalk6.gray(`- Docs: ${path20.relative(cwd, config.docsDir)}`));
|
|
8104
8352
|
console.log(
|
|
8105
8353
|
chalk6.gray(
|
|
8106
8354
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
@@ -8473,25 +8721,25 @@ function tg(lang, key, vars = {}) {
|
|
|
8473
8721
|
}
|
|
8474
8722
|
function detectGithubCliLangSync(cwd) {
|
|
8475
8723
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
8476
|
-
const startDirs = [explicitDocsDir ?
|
|
8724
|
+
const startDirs = [explicitDocsDir ? path20.resolve(explicitDocsDir) : "", path20.resolve(cwd)].filter(Boolean);
|
|
8477
8725
|
const scanOrder = [];
|
|
8478
8726
|
const seen = /* @__PURE__ */ new Set();
|
|
8479
8727
|
for (const start of startDirs) {
|
|
8480
8728
|
let current = start;
|
|
8481
8729
|
while (true) {
|
|
8482
|
-
const abs =
|
|
8730
|
+
const abs = path20.resolve(current);
|
|
8483
8731
|
if (!seen.has(abs)) {
|
|
8484
8732
|
scanOrder.push(abs);
|
|
8485
8733
|
seen.add(abs);
|
|
8486
8734
|
}
|
|
8487
|
-
const parent =
|
|
8735
|
+
const parent = path20.dirname(abs);
|
|
8488
8736
|
if (parent === abs) break;
|
|
8489
8737
|
current = parent;
|
|
8490
8738
|
}
|
|
8491
8739
|
}
|
|
8492
8740
|
for (const base of scanOrder) {
|
|
8493
|
-
for (const docsDir of [
|
|
8494
|
-
const configPath =
|
|
8741
|
+
for (const docsDir of [path20.join(base, "docs"), base]) {
|
|
8742
|
+
const configPath = path20.join(docsDir, ".lee-spec-kit.json");
|
|
8495
8743
|
if (fs15.existsSync(configPath)) {
|
|
8496
8744
|
try {
|
|
8497
8745
|
const parsed = fs15.readJsonSync(configPath);
|
|
@@ -8499,11 +8747,11 @@ function detectGithubCliLangSync(cwd) {
|
|
|
8499
8747
|
} catch {
|
|
8500
8748
|
}
|
|
8501
8749
|
}
|
|
8502
|
-
const agentsPath =
|
|
8503
|
-
const featuresPath =
|
|
8750
|
+
const agentsPath = path20.join(docsDir, "agents");
|
|
8751
|
+
const featuresPath = path20.join(docsDir, "features");
|
|
8504
8752
|
if (!fs15.existsSync(agentsPath) || !fs15.existsSync(featuresPath)) continue;
|
|
8505
8753
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
8506
|
-
const file =
|
|
8754
|
+
const file = path20.join(agentsPath, probe);
|
|
8507
8755
|
if (!fs15.existsSync(file)) continue;
|
|
8508
8756
|
try {
|
|
8509
8757
|
const content = fs15.readFileSync(file, "utf-8");
|
|
@@ -8601,7 +8849,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
8601
8849
|
}
|
|
8602
8850
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
8603
8851
|
const missing = relativePaths.filter(
|
|
8604
|
-
(relativePath) => !fs15.existsSync(
|
|
8852
|
+
(relativePath) => !fs15.existsSync(path20.join(docsDir, relativePath))
|
|
8605
8853
|
);
|
|
8606
8854
|
if (missing.length > 0) {
|
|
8607
8855
|
throw createCliError(
|
|
@@ -8611,13 +8859,13 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
8611
8859
|
}
|
|
8612
8860
|
}
|
|
8613
8861
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
8614
|
-
const key = `${
|
|
8862
|
+
const key = `${path20.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
8615
8863
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
8616
8864
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
8617
8865
|
}
|
|
8618
8866
|
function toBodyFilePath(raw, kind, docsDir, component) {
|
|
8619
|
-
const selected = raw?.trim() ||
|
|
8620
|
-
return
|
|
8867
|
+
const selected = raw?.trim() || path20.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
8868
|
+
return path20.resolve(selected);
|
|
8621
8869
|
}
|
|
8622
8870
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
8623
8871
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -8710,6 +8958,10 @@ function resolveGithubDocsCwd(config, feature) {
|
|
|
8710
8958
|
if (docsGitCwd) return docsGitCwd;
|
|
8711
8959
|
return config.docsDir;
|
|
8712
8960
|
}
|
|
8961
|
+
function shouldPushDocsSync(config) {
|
|
8962
|
+
if (config.docsRepo !== "standalone") return true;
|
|
8963
|
+
return config.pushDocs === true;
|
|
8964
|
+
}
|
|
8713
8965
|
function getFeatureDocPaths(feature) {
|
|
8714
8966
|
const featurePathFromDocs = feature.docs.featurePathFromDocs;
|
|
8715
8967
|
return {
|
|
@@ -9432,8 +9684,8 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
9432
9684
|
);
|
|
9433
9685
|
}
|
|
9434
9686
|
}
|
|
9435
|
-
function commitAndPushPath(cwd, absPath, message, lang) {
|
|
9436
|
-
const relativePath =
|
|
9687
|
+
function commitAndPushPath(cwd, absPath, message, lang, options) {
|
|
9688
|
+
const relativePath = path20.relative(cwd, absPath) || absPath;
|
|
9437
9689
|
const status = runProcessOrThrow(
|
|
9438
9690
|
"git",
|
|
9439
9691
|
["status", "--porcelain=v1", "--", relativePath],
|
|
@@ -9443,6 +9695,7 @@ function commitAndPushPath(cwd, absPath, message, lang) {
|
|
|
9443
9695
|
if (status.stdout.trim().length === 0) return;
|
|
9444
9696
|
runProcessOrThrow("git", ["add", "--", relativePath], cwd, tg(lang, "stageFileFailed"));
|
|
9445
9697
|
runProcessOrThrow("git", ["commit", "-m", message], cwd, tg(lang, "commitSyncFailed"));
|
|
9698
|
+
if (options?.pushToOrigin === false) return;
|
|
9446
9699
|
const branch = gitCurrentBranch(cwd, lang);
|
|
9447
9700
|
runProcessOrThrow(
|
|
9448
9701
|
"git",
|
|
@@ -9565,9 +9818,9 @@ function githubCommand(program2) {
|
|
|
9565
9818
|
[paths.specPath, paths.planPath, paths.tasksPath],
|
|
9566
9819
|
config.lang
|
|
9567
9820
|
);
|
|
9568
|
-
const specContent = await fs15.readFile(
|
|
9569
|
-
const planContent = await fs15.readFile(
|
|
9570
|
-
const tasksContent = await fs15.readFile(
|
|
9821
|
+
const specContent = await fs15.readFile(path20.join(config.docsDir, paths.specPath), "utf-8");
|
|
9822
|
+
const planContent = await fs15.readFile(path20.join(config.docsDir, paths.planPath), "utf-8");
|
|
9823
|
+
const tasksContent = await fs15.readFile(path20.join(config.docsDir, paths.tasksPath), "utf-8");
|
|
9571
9824
|
const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
|
|
9572
9825
|
const title = options.title?.trim() || tg(config.lang, "issueDefaultTitle", {
|
|
9573
9826
|
slug: feature.slug,
|
|
@@ -9605,7 +9858,7 @@ function githubCommand(program2) {
|
|
|
9605
9858
|
config.lang
|
|
9606
9859
|
);
|
|
9607
9860
|
} else {
|
|
9608
|
-
await fs15.ensureDir(
|
|
9861
|
+
await fs15.ensureDir(path20.dirname(bodyFile));
|
|
9609
9862
|
await fs15.writeFile(bodyFile, generatedBody, "utf-8");
|
|
9610
9863
|
}
|
|
9611
9864
|
let issueUrl;
|
|
@@ -9704,10 +9957,10 @@ function githubCommand(program2) {
|
|
|
9704
9957
|
const labels = parseLabels(options.labels, config.lang);
|
|
9705
9958
|
const paths = getFeatureDocPaths(feature);
|
|
9706
9959
|
ensureDocsExist(config.docsDir, [paths.specPath, paths.tasksPath], config.lang);
|
|
9707
|
-
const specContent = await fs15.readFile(
|
|
9708
|
-
const planPath =
|
|
9960
|
+
const specContent = await fs15.readFile(path20.join(config.docsDir, paths.specPath), "utf-8");
|
|
9961
|
+
const planPath = path20.join(config.docsDir, paths.planPath);
|
|
9709
9962
|
const planContent = await fs15.pathExists(planPath) ? await fs15.readFile(planPath, "utf-8") : "";
|
|
9710
|
-
const tasksContent = await fs15.readFile(
|
|
9963
|
+
const tasksContent = await fs15.readFile(path20.join(config.docsDir, paths.tasksPath), "utf-8");
|
|
9711
9964
|
const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
|
|
9712
9965
|
const defaultTitle = feature.issueNumber ? tg(config.lang, "prDefaultTitleWithIssue", {
|
|
9713
9966
|
issue: feature.issueNumber,
|
|
@@ -9750,13 +10003,14 @@ function githubCommand(program2) {
|
|
|
9750
10003
|
config.lang
|
|
9751
10004
|
);
|
|
9752
10005
|
} else {
|
|
9753
|
-
await fs15.ensureDir(
|
|
10006
|
+
await fs15.ensureDir(path20.dirname(bodyFile));
|
|
9754
10007
|
await fs15.writeFile(bodyFile, generatedBody, "utf-8");
|
|
9755
10008
|
}
|
|
9756
10009
|
const retryCount = toRetryCount(options.retry, config.lang);
|
|
9757
10010
|
let prUrl = options.pr?.trim() || "";
|
|
9758
10011
|
let mergedAttempts;
|
|
9759
10012
|
let syncChanged = false;
|
|
10013
|
+
const pushDocsSync = shouldPushDocsSync(config);
|
|
9760
10014
|
if (options.create) {
|
|
9761
10015
|
const projectGitCwd = resolveGithubProjectCwd(config, feature);
|
|
9762
10016
|
ensureNoTodoPlaceholders(body, tg(config.lang, "kindPr"), config.lang);
|
|
@@ -9789,6 +10043,9 @@ function githubCommand(program2) {
|
|
|
9789
10043
|
);
|
|
9790
10044
|
prUrl = created.stdout.trim();
|
|
9791
10045
|
}
|
|
10046
|
+
if (!prUrl && options.merge) {
|
|
10047
|
+
prUrl = (feature.pr.link || "").trim();
|
|
10048
|
+
}
|
|
9792
10049
|
if (!prUrl && options.merge) {
|
|
9793
10050
|
throw createCliError(
|
|
9794
10051
|
"INVALID_ARGUMENT",
|
|
@@ -9804,7 +10061,7 @@ function githubCommand(program2) {
|
|
|
9804
10061
|
}
|
|
9805
10062
|
if (prUrl && options.syncTasks !== false) {
|
|
9806
10063
|
const synced = syncTasksPrMetadata(
|
|
9807
|
-
|
|
10064
|
+
path20.join(config.docsDir, paths.tasksPath),
|
|
9808
10065
|
prUrl,
|
|
9809
10066
|
"Review",
|
|
9810
10067
|
config.lang
|
|
@@ -9823,7 +10080,8 @@ function githubCommand(program2) {
|
|
|
9823
10080
|
docsGitCwd,
|
|
9824
10081
|
synced.path,
|
|
9825
10082
|
message,
|
|
9826
|
-
config.lang
|
|
10083
|
+
config.lang,
|
|
10084
|
+
{ pushToOrigin: pushDocsSync }
|
|
9827
10085
|
);
|
|
9828
10086
|
}
|
|
9829
10087
|
}
|
|
@@ -9851,7 +10109,7 @@ function githubCommand(program2) {
|
|
|
9851
10109
|
);
|
|
9852
10110
|
if (prUrl && options.syncTasks !== false) {
|
|
9853
10111
|
const mergedSync = syncTasksPrMetadata(
|
|
9854
|
-
|
|
10112
|
+
path20.join(config.docsDir, paths.tasksPath),
|
|
9855
10113
|
prUrl,
|
|
9856
10114
|
"Approved",
|
|
9857
10115
|
config.lang
|
|
@@ -9869,7 +10127,8 @@ function githubCommand(program2) {
|
|
|
9869
10127
|
docsGitCwd,
|
|
9870
10128
|
mergedSync.path,
|
|
9871
10129
|
message,
|
|
9872
|
-
config.lang
|
|
10130
|
+
config.lang,
|
|
10131
|
+
{ pushToOrigin: pushDocsSync }
|
|
9873
10132
|
);
|
|
9874
10133
|
}
|
|
9875
10134
|
}
|
|
@@ -10056,7 +10315,7 @@ function docsCommand(program2) {
|
|
|
10056
10315
|
);
|
|
10057
10316
|
return;
|
|
10058
10317
|
}
|
|
10059
|
-
const relativeFromCwd =
|
|
10318
|
+
const relativeFromCwd = path20.relative(process.cwd(), loaded.entry.absolutePath);
|
|
10060
10319
|
console.log();
|
|
10061
10320
|
console.log(chalk6.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
10062
10321
|
console.log(
|
|
@@ -10134,7 +10393,7 @@ function detectCommand(program2) {
|
|
|
10134
10393
|
}
|
|
10135
10394
|
async function runDetect(options) {
|
|
10136
10395
|
const cwd = process.cwd();
|
|
10137
|
-
const targetCwd = options.dir ?
|
|
10396
|
+
const targetCwd = options.dir ? path20.resolve(cwd, options.dir) : cwd;
|
|
10138
10397
|
const config = await getConfig(targetCwd);
|
|
10139
10398
|
const detected = !!config;
|
|
10140
10399
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
@@ -10161,7 +10420,7 @@ async function runDetect(options) {
|
|
|
10161
10420
|
);
|
|
10162
10421
|
return;
|
|
10163
10422
|
}
|
|
10164
|
-
const configPath2 =
|
|
10423
|
+
const configPath2 = path20.join(config.docsDir, ".lee-spec-kit.json");
|
|
10165
10424
|
const configFilePresent2 = await fs15.pathExists(configPath2);
|
|
10166
10425
|
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
10167
10426
|
console.log(
|
|
@@ -10195,7 +10454,7 @@ async function runDetect(options) {
|
|
|
10195
10454
|
console.log();
|
|
10196
10455
|
return;
|
|
10197
10456
|
}
|
|
10198
|
-
const configPath =
|
|
10457
|
+
const configPath = path20.join(config.docsDir, ".lee-spec-kit.json");
|
|
10199
10458
|
const configFilePresent = await fs15.pathExists(configPath);
|
|
10200
10459
|
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
10201
10460
|
console.log(chalk6.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
@@ -10290,7 +10549,7 @@ async function countFeatureDirs(docsDir, projectType) {
|
|
|
10290
10549
|
ignore: ["**/feature-base/**"]
|
|
10291
10550
|
});
|
|
10292
10551
|
return dirs.map((value) => value.replace(/\\/g, "/").replace(/\/+$/, "")).filter((value) => {
|
|
10293
|
-
const base =
|
|
10552
|
+
const base = path20.posix.basename(value);
|
|
10294
10553
|
return !!base && base !== "feature-base";
|
|
10295
10554
|
}).length;
|
|
10296
10555
|
}
|
|
@@ -10302,7 +10561,7 @@ async function hasUserPrdFile(prdDir) {
|
|
|
10302
10561
|
absolute: false,
|
|
10303
10562
|
ignore: ["**/node_modules/**"]
|
|
10304
10563
|
});
|
|
10305
|
-
return files.some((relativePath) =>
|
|
10564
|
+
return files.some((relativePath) => path20.basename(relativePath).toLowerCase() !== "readme.md");
|
|
10306
10565
|
}
|
|
10307
10566
|
function finalizeChecks(checks) {
|
|
10308
10567
|
const summary = checks.reduce(
|
|
@@ -10431,7 +10690,7 @@ async function runOnboardChecks(config) {
|
|
|
10431
10690
|
});
|
|
10432
10691
|
}
|
|
10433
10692
|
}
|
|
10434
|
-
const constitutionPath =
|
|
10693
|
+
const constitutionPath = path20.join(docsDir, "agents", "constitution.md");
|
|
10435
10694
|
if (!await fs15.pathExists(constitutionPath)) {
|
|
10436
10695
|
checks.push({
|
|
10437
10696
|
id: "constitution_exists",
|
|
@@ -10469,7 +10728,7 @@ async function runOnboardChecks(config) {
|
|
|
10469
10728
|
});
|
|
10470
10729
|
}
|
|
10471
10730
|
}
|
|
10472
|
-
const customPath =
|
|
10731
|
+
const customPath = path20.join(docsDir, "agents", "custom.md");
|
|
10473
10732
|
if (await fs15.pathExists(customPath)) {
|
|
10474
10733
|
const content = await fs15.readFile(customPath, "utf-8");
|
|
10475
10734
|
if (hasTemplateMarkers(content)) {
|
|
@@ -10494,7 +10753,7 @@ async function runOnboardChecks(config) {
|
|
|
10494
10753
|
});
|
|
10495
10754
|
}
|
|
10496
10755
|
}
|
|
10497
|
-
const prdDir =
|
|
10756
|
+
const prdDir = path20.join(docsDir, "prd");
|
|
10498
10757
|
const featureCount = await countFeatureDirs(docsDir, config.projectType);
|
|
10499
10758
|
const prdReady = await hasUserPrdFile(prdDir);
|
|
10500
10759
|
if (!prdReady) {
|
|
@@ -10512,7 +10771,7 @@ async function runOnboardChecks(config) {
|
|
|
10512
10771
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
10513
10772
|
),
|
|
10514
10773
|
path: prdDir,
|
|
10515
|
-
suggestedCommand: `touch ${quotePath(
|
|
10774
|
+
suggestedCommand: `touch ${quotePath(path20.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
10516
10775
|
});
|
|
10517
10776
|
} else {
|
|
10518
10777
|
checks.push({
|
|
@@ -10725,11 +10984,11 @@ ${version}
|
|
|
10725
10984
|
}
|
|
10726
10985
|
return `${ascii}${footer}`;
|
|
10727
10986
|
}
|
|
10728
|
-
var CACHE_FILE =
|
|
10987
|
+
var CACHE_FILE = path20.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
10729
10988
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
10730
10989
|
function getCurrentVersion() {
|
|
10731
10990
|
try {
|
|
10732
|
-
const packageJsonPath =
|
|
10991
|
+
const packageJsonPath = path20.join(__dirname$1, "..", "package.json");
|
|
10733
10992
|
if (fs15.existsSync(packageJsonPath)) {
|
|
10734
10993
|
const pkg = fs15.readJsonSync(packageJsonPath);
|
|
10735
10994
|
return pkg.version;
|
|
@@ -10833,7 +11092,7 @@ function shouldCheckForUpdates() {
|
|
|
10833
11092
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
10834
11093
|
function getCliVersion() {
|
|
10835
11094
|
try {
|
|
10836
|
-
const packageJsonPath =
|
|
11095
|
+
const packageJsonPath = path20.join(__dirname$1, "..", "package.json");
|
|
10837
11096
|
if (fs15.existsSync(packageJsonPath)) {
|
|
10838
11097
|
const pkg = fs15.readJsonSync(packageJsonPath);
|
|
10839
11098
|
if (pkg?.version) return String(pkg.version);
|