lee-spec-kit 0.5.1 → 0.6.0
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 +29 -5
- package/README.md +29 -12
- package/dist/index.js +641 -501
- package/package.json +1 -1
- package/templates/en/fullstack/features/feature-base/tasks.md +2 -0
- package/templates/en/single/features/feature-base/tasks.md +2 -0
- package/templates/ko/fullstack/features/feature-base/tasks.md +2 -0
- package/templates/ko/single/features/feature-base/tasks.md +2 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import path6 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { program } from 'commander';
|
|
5
5
|
import fs2 from 'fs-extra';
|
|
@@ -11,7 +11,7 @@ import { createHash } from 'crypto';
|
|
|
11
11
|
import os from 'os';
|
|
12
12
|
|
|
13
13
|
var getFilename = () => fileURLToPath(import.meta.url);
|
|
14
|
-
var getDirname = () =>
|
|
14
|
+
var getDirname = () => path6.dirname(getFilename());
|
|
15
15
|
var __dirname$1 = /* @__PURE__ */ getDirname();
|
|
16
16
|
async function copyTemplates(src, dest) {
|
|
17
17
|
await fs2.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 = path6.dirname(__filename2);
|
|
46
46
|
function getTemplatesDir() {
|
|
47
|
-
const rootDir =
|
|
48
|
-
return
|
|
47
|
+
const rootDir = path6.resolve(__dirname2, "..");
|
|
48
|
+
return path6.join(rootDir, "templates");
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// src/utils/i18n.ts
|
|
@@ -73,7 +73,7 @@ var I18N = {
|
|
|
73
73
|
"status.wrote": "\u2705 {path} \uC0DD\uC131 \uC644\uB8CC",
|
|
74
74
|
"feature.selectRepo": "\uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
75
75
|
"feature.folderExists": "\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: {path}",
|
|
76
|
-
"feature.baseNotFound": "feature
|
|
76
|
+
"feature.baseNotFound": "CLI \uB0B4\uC7A5 feature \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
77
77
|
"feature.created": "\u2705 Feature \uD3F4\uB354 \uC0DD\uC131 \uC644\uB8CC: {path}",
|
|
78
78
|
"feature.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
|
|
79
79
|
"feature.nextSteps1": " 1. {path}/spec.md \uC791\uC131",
|
|
@@ -94,6 +94,9 @@ var I18N = {
|
|
|
94
94
|
"update.agentsUpdated": "agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
|
|
95
95
|
"update.skillsUpdated": "agents/skills \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
|
|
96
96
|
"update.updatingFeatureBase": "\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
|
|
97
|
+
"update.engineManagedSkillsBuiltin": "agents/skills\uB294 CLI \uB0B4\uC7A5 \uADDC\uCE59\uC73C\uB85C \uAD00\uB9AC\uB418\uC5B4 docs\uB85C \uB3D9\uAE30\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
98
|
+
"update.engineManagedFeatureBaseBuiltin": "features/feature-base\uB294 CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF\uC73C\uB85C \uAD00\uB9AC\uB418\uC5B4 docs\uB85C \uB3D9\uAE30\uD654\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
99
|
+
"update.engineManagedPruned": "docs\uC5D0\uC11C CLI \uAD00\uB9AC \uBB38\uC11C {count}\uAC1C\uB97C \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.",
|
|
97
100
|
"update.filesUpdated": "{count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
|
|
98
101
|
"update.updatedTotal": "\uCD1D {count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!",
|
|
99
102
|
"update.changeDetected": "\uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)",
|
|
@@ -128,15 +131,17 @@ var I18N = {
|
|
|
128
131
|
"context.tipShowAll": "\uC804\uCCB4 \uBCF4\uAE30",
|
|
129
132
|
"context.tipShowDone": "\uC644\uB8CC\uB9CC \uBCF4\uAE30",
|
|
130
133
|
"context.checkRequired": "[\uD655\uC778 \uD544\uC694] ",
|
|
131
|
-
"context.checkPolicyHint": "\u2139\uFE0F \uC0AC\uC6A9\uC790 \uD655\uC778 \
|
|
134
|
+
"context.checkPolicyHint": "\u2139\uFE0F \uC0AC\uC6A9\uC790 \uD655\uC778 \uC815\uCC45 \uC548\uB0B4(\uD604\uC7AC Next Action \uC544\uB2D8): CLI \uB0B4\uC7A5 \uC5D0\uC774\uC804\uD2B8 \uC815\uCC45 \uCC38\uACE0 (git push/merge/merge commit \uD3EC\uD568). [\uD655\uC778 \uD544\uC694]\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`) \uC751\uB2F5\uC744 \uBC1B\uC740 \uB4A4 \uC9C4\uD589 (config: approval\uB85C \uC870\uC815 \uAC00\uB2A5)",
|
|
132
135
|
"context.actionOptionHint": "\uC2B9\uC778 \uC751\uB2F5 \uD615\uC2DD: `<\uB77C\uBCA8>` \uB610\uB294 `<\uB77C\uBCA8> OK` (\uC608: `A`, `A OK`)",
|
|
133
136
|
"context.actionExplainHint": "\uC2B9\uC778 \uC694\uCCAD \uC804, \uAC01 \uB77C\uBCA8\uC774 \uBB34\uC5C7\uC744 \uC2E4\uD589/\uBCC0\uACBD\uD558\uB294\uC9C0 \uD55C \uC904 \uC694\uC57D\uACFC \uD568\uAED8 \uC124\uBA85\uD558\uC138\uC694.",
|
|
134
|
-
"context.tipDocsCommitRules": "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59:
|
|
137
|
+
"context.tipDocsCommitRules": "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59: CLI \uB0B4\uC7A5 git-workflow \uADDC\uCE59 \uCC38\uACE0",
|
|
135
138
|
"context.list.docsCommitNeeded": "\uBB38\uC11C \uCEE4\uBC0B \uD544\uC694",
|
|
136
139
|
"context.list.projectCommitNeeded": "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694",
|
|
137
140
|
"context.list.issueNumberNeeded": "\uC774\uC288 \uBC88\uD638 \uAE30\uB85D \uD544\uC694",
|
|
138
141
|
"context.list.addPrMetadata": "PR \uBA54\uD0C0\uB370\uC774\uD130(PR/PR \uC0C1\uD0DC) \uCD94\uAC00",
|
|
139
142
|
"context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
|
|
143
|
+
"context.list.addPrePrReviewField": "Pre-PR Review \uD544\uB4DC \uCD94\uAC00",
|
|
144
|
+
"context.list.completePrePrReview": "Pre-PR \uB9AC\uBDF0 \uC644\uB8CC \uCC98\uB9AC",
|
|
140
145
|
"context.list.setPrStatus": "PR \uC0C1\uD0DC \uC124\uC815",
|
|
141
146
|
"context.list.prStatusToApproved": "PR \uC0C1\uD0DC {status} \u2192 Approved",
|
|
142
147
|
"context.list.approveSpec": "spec \uC2B9\uC778 \uD544\uC694",
|
|
@@ -205,23 +210,24 @@ var I18N = {
|
|
|
205
210
|
branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
|
|
206
211
|
tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
|
|
207
212
|
docsCommitSync: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
|
|
213
|
+
prePrReview: "Pre-PR \uB9AC\uBDF0",
|
|
208
214
|
prCreate: "PR \uC0DD\uC131",
|
|
209
215
|
codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
|
|
210
216
|
featureDone: "Feature \uC644\uB8CC"
|
|
211
217
|
},
|
|
212
218
|
messages: {
|
|
213
|
-
specCreate: "spec.md \uD15C\uD50C\uB9BF
|
|
219
|
+
specCreate: "spec.md\uB97C CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF \uAE30\uC900\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.",
|
|
214
220
|
specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
215
221
|
specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
216
|
-
planCreate: "plan.md \uD15C\uD50C\uB9BF
|
|
222
|
+
planCreate: "plan.md\uB97C CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF \uAE30\uC900\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.",
|
|
217
223
|
planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
218
224
|
planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
219
|
-
tasksCreate: "tasks.md \uD15C\uD50C\uB9BF
|
|
225
|
+
tasksCreate: "tasks.md\uB97C CLI \uB0B4\uC7A5 \uD15C\uD50C\uB9BF \uAE30\uC900\uC73C\uB85C \uC791\uC131\uD558\uC138\uC694.",
|
|
220
226
|
tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
|
|
221
227
|
tasksImprove: "tasks.md\uB97C \uBCF4\uC644\uD558\uACE0 \uBB38\uC11C \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
222
228
|
tasksApproval: "tasks.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC9C4\uD589 \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD)\uC744 \uBC1B\uC73C\uC138\uC694. (\uC2B9\uC778 \uD6C4 \uBB38\uC11C \uC0C1\uD0DC\uB97C Approved\uB85C \uBCC0\uACBD)",
|
|
223
229
|
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
|
|
224
|
-
issueCreateAndWrite: "GitHub Issue\uB97C \uC0DD\uC131\uD55C \uB4A4, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694. (
|
|
230
|
+
issueCreateAndWrite: "GitHub Issue\uB97C \uC0DD\uC131\uD55C \uB4A4, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694. (CLI \uB0B4\uC7A5 create-issue \uAC00\uC774\uB4DC)",
|
|
225
231
|
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
|
|
226
232
|
docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
|
|
227
233
|
projectCommitIssueUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat(#{issueNumber}): {folderName} \uAD6C\uD604 \uC5C5\uB370\uC774\uD2B8"',
|
|
@@ -230,11 +236,15 @@ var I18N = {
|
|
|
230
236
|
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
231
237
|
tasksAllDoneButNoChecklist: '\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC139\uC158\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC744 \uCD94\uAC00/\uD655\uC778\uD558\uC138\uC694.',
|
|
232
238
|
tasksAllDoneButChecklist: "\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uAC00 \uC644\uC804\uD788 \uCCB4\uD06C\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. ({checked}/{total})",
|
|
233
|
-
finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (\uC644\uB8CC \uC804 \uACB0\uACFC/\uAC80\uC99D \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DONE \uCC98\uB9AC) (
|
|
234
|
-
startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (\uC2DC\uC791 \uC804 \uC81C\uBAA9 \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DOING \uCC98\uB9AC) (
|
|
235
|
-
checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (
|
|
239
|
+
finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (\uC644\uB8CC \uC804 \uACB0\uACFC/\uAC80\uC99D \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DONE \uCC98\uB9AC) (CLI \uB0B4\uC7A5 execute-task \uAC00\uC774\uB4DC)',
|
|
240
|
+
startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (\uC2DC\uC791 \uC804 \uC81C\uBAA9 \uACF5\uC720 + \uC2B9\uC778(`A` \uB610\uB294 `A OK` \uD615\uC2DD) \uD6C4 DOING \uCC98\uB9AC) (CLI \uB0B4\uC7A5 execute-task \uAC00\uC774\uB4DC)',
|
|
241
|
+
checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (CLI \uB0B4\uC7A5 execute-task \uAC00\uC774\uB4DC)",
|
|
236
242
|
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)",
|
|
237
|
-
|
|
243
|
+
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)",
|
|
244
|
+
prePrReviewRun: "PR \uC0DD\uC131 \uC804 \uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0\uB97C \uC9C4\uD589\uD558\uC138\uC694. \uC6B0\uC120\uC21C\uC704 \uC2A4\uD0AC: {skills} (\uC124\uCE58\uB41C \uB354 \uC801\uD569\uD55C \uC2A4\uD0AC\uC774 \uC788\uB2E4\uBA74 \uBA3C\uC800 \uC81C\uC548 \uD6C4 \uC0AC\uC6A9). \uC2A4\uD0AC\uC744 \uC4F8 \uC218 \uC5C6\uC73C\uBA74 `{fallback}` \uC815\uCC45\uC73C\uB85C \uC9C4\uD589\uD558\uACE0 `PR \uC804 \uB9AC\uBDF0`\uB97C Done\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. Findings \uC815\uCC45: {findingsPolicy}",
|
|
245
|
+
prePrReviewFindingsBlock: "\uC911\uC694 \uC774\uC288\uB294 \uC218\uC815/\uD569\uC758 \uD6C4\uC5D0\uB9CC PR \uC0DD\uC131",
|
|
246
|
+
prePrReviewFindingsWarn: "\uB9AC\uC2A4\uD06C\uB97C \uACF5\uC720\uD558\uBA74 PR \uC0DD\uC131 \uC9C4\uD589 \uAC00\uB2A5",
|
|
247
|
+
prCreate: "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694. (CLI \uB0B4\uC7A5 create-pr \uAC00\uC774\uB4DC)",
|
|
238
248
|
prFillStatus: "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694. (merge \uD6C4 Approved\uB85C \uC5C5\uB370\uC774\uD2B8)",
|
|
239
249
|
prResolveReview: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD574\uACB0\uD558\uACE0 PR \uC0C1\uD0DC\uB97C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. (PR \uC0C1\uD0DC: Review \u2192 Approved)",
|
|
240
250
|
prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.",
|
|
@@ -245,17 +255,20 @@ var I18N = {
|
|
|
245
255
|
projectBranchUnavailable: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.)",
|
|
246
256
|
docsGitUnavailable: "docs \uB808\uD3EC\uC758 git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uB808\uD3EC \uC704\uCE58 / git init \uD655\uC778)",
|
|
247
257
|
docsPathIgnored: "\uD604\uC7AC Feature \uBB38\uC11C \uACBD\uB85C\uAC00 git ignore \uB300\uC0C1\uC785\uB2C8\uB2E4: {path} (docs \uCEE4\uBC0B \uAC10\uC9C0\uAC00 \uC81C\uD55C\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4.)",
|
|
248
|
-
docsUncommittedChanges: "\uBB38\uC11C \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uBB38\uC11C \uCEE4\uBC0B \uD544\uC694) \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59:
|
|
258
|
+
docsUncommittedChanges: "\uBB38\uC11C \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uBB38\uC11C \uCEE4\uBC0B \uD544\uC694) \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uADDC\uCE59: CLI \uB0B4\uC7A5 git-workflow \uADDC\uCE59 \uCC38\uACE0",
|
|
249
259
|
projectUncommittedChanges: "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uCF54\uB4DC \uCEE4\uBC0B \uD544\uC694)",
|
|
250
260
|
legacyTasksDocStatusField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. `\uBB38\uC11C \uC0C1\uD0DC` \uD544\uB4DC(Review/Approved)\uB97C \uCD94\uAC00\uD574 \uD0DC\uC2A4\uD06C \uC2B9\uC778 \uB2E8\uACC4\uB97C \uD65C\uC131\uD654\uD558\uC138\uC694.",
|
|
251
261
|
legacyTasksPrFields: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR` \uBC0F `PR \uC0C1\uD0DC` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
|
|
262
|
+
legacyTasksPrePrReviewField: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694. (`- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`)",
|
|
252
263
|
workflowSpecNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC spec.md \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (spec.md\uC758 \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
|
|
253
264
|
workflowPlanNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC plan.md \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (plan.md\uC758 \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
|
|
254
265
|
workflowIssueMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC \uC774\uC288 \uBC88\uD638\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uC138\uC694.)",
|
|
255
266
|
workflowProjectUncommittedChanges: "\uC644\uB8CC \uC870\uAC74 \uC774\uC804\uC5D0 \uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uBCC0\uACBD\uC0AC\uD56D\uC744 \uCEE4\uBC0B\uD574\uC57C \uD569\uB2C8\uB2E4. (\uD504\uB85C\uC81D\uD2B8 \uC6CC\uD06C\uD2B8\uB9AC \uBBF8\uCEE4\uBC0B \uBCC0\uACBD \uC874\uC7AC)",
|
|
256
267
|
workflowPrLinkMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uB9C1\uD06C\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (tasks.md\uC758 PR \uD544\uB4DC\uB97C \uCC44\uC6B0\uC138\uC694.)",
|
|
257
268
|
workflowPrStatusMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694.)",
|
|
258
|
-
workflowPrStatusNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (merge \uD6C4 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)"
|
|
269
|
+
workflowPrStatusNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (merge \uD6C4 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
|
|
270
|
+
workflowPrePrReviewMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0` \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. (tasks.md\uC5D0 `- **PR \uC804 \uB9AC\uBDF0**: Pending | Done`\uC744 \uCD94\uAC00\uD558\uC138\uC694.)",
|
|
271
|
+
workflowPrePrReviewNotDone: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC `PR \uC804 \uB9AC\uBDF0`\uAC00 Done\uC774 \uC544\uB2D9\uB2C8\uB2E4. (\uC0AC\uC804 \uCF54\uB4DC\uB9AC\uBDF0 \uD6C4 Done\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)"
|
|
259
272
|
}
|
|
260
273
|
},
|
|
261
274
|
en: {
|
|
@@ -270,7 +283,7 @@ var I18N = {
|
|
|
270
283
|
"status.wrote": "\u2705 Wrote {path}",
|
|
271
284
|
"feature.selectRepo": "Select a repository:",
|
|
272
285
|
"feature.folderExists": "Folder already exists: {path}",
|
|
273
|
-
"feature.baseNotFound": "
|
|
286
|
+
"feature.baseNotFound": "Built-in feature template not found.",
|
|
274
287
|
"feature.created": "\u2705 Feature folder created: {path}",
|
|
275
288
|
"feature.nextStepsTitle": "Next steps:",
|
|
276
289
|
"feature.nextSteps1": " 1. Write {path}/spec.md",
|
|
@@ -291,6 +304,9 @@ var I18N = {
|
|
|
291
304
|
"update.agentsUpdated": "agents/ updated",
|
|
292
305
|
"update.skillsUpdated": "agents/skills updated",
|
|
293
306
|
"update.updatingFeatureBase": "\u{1F4C1} Updating features/feature-base/ folder...",
|
|
307
|
+
"update.engineManagedSkillsBuiltin": "agents/skills is CLI-managed and is not synced into docs.",
|
|
308
|
+
"update.engineManagedFeatureBaseBuiltin": "features/feature-base is CLI-managed and is not synced into docs.",
|
|
309
|
+
"update.engineManagedPruned": "Removed {count} CLI-managed docs entries from this docs tree.",
|
|
294
310
|
"update.filesUpdated": "{count} files updated",
|
|
295
311
|
"update.updatedTotal": "Updated {count} files!",
|
|
296
312
|
"update.changeDetected": "changes detected (use --force to overwrite)",
|
|
@@ -325,15 +341,17 @@ var I18N = {
|
|
|
325
341
|
"context.tipShowAll": "Show all",
|
|
326
342
|
"context.tipShowDone": "Show done only",
|
|
327
343
|
"context.checkRequired": "[CHECK required] ",
|
|
328
|
-
"context.checkPolicyHint": "\u2139\uFE0F User check policy: see
|
|
344
|
+
"context.checkPolicyHint": "\u2139\uFE0F User check policy notice (not the current next action): see the CLI built-in agent policy (includes git push/merge and merge commits). If you see [CHECK required], wait for `<label>` or `<label> OK` (e.g. `A`, `A OK`) before proceeding (config: approval can override)",
|
|
329
345
|
"context.actionOptionHint": "Approval reply format: `<label>` or `<label> OK` (e.g. `A`, `A OK`)",
|
|
330
346
|
"context.actionExplainHint": "Before requesting approval, explain what each label will run/change with a one-line summary.",
|
|
331
|
-
"context.tipDocsCommitRules": "Commit message rules:
|
|
347
|
+
"context.tipDocsCommitRules": "Commit message rules: CLI built-in git-workflow policy",
|
|
332
348
|
"context.list.docsCommitNeeded": "Commit docs changes",
|
|
333
349
|
"context.list.projectCommitNeeded": "Commit project code changes",
|
|
334
350
|
"context.list.issueNumberNeeded": "Fill issue number in docs",
|
|
335
351
|
"context.list.addPrMetadata": "Add PR metadata (PR/PR Status)",
|
|
336
352
|
"context.list.recordPrLink": "Record PR link",
|
|
353
|
+
"context.list.addPrePrReviewField": "Add Pre-PR Review field",
|
|
354
|
+
"context.list.completePrePrReview": "Complete Pre-PR review",
|
|
337
355
|
"context.list.setPrStatus": "Set PR Status",
|
|
338
356
|
"context.list.prStatusToApproved": "PR Status {status} \u2192 Approved",
|
|
339
357
|
"context.list.approveSpec": "Approve spec",
|
|
@@ -402,23 +420,24 @@ var I18N = {
|
|
|
402
420
|
branchCreate: "Create branch",
|
|
403
421
|
tasksExecute: "Execute tasks",
|
|
404
422
|
docsCommitSync: "Commit docs (sync)",
|
|
423
|
+
prePrReview: "Pre-PR review",
|
|
405
424
|
prCreate: "Create PR",
|
|
406
425
|
codeReview: "Code review",
|
|
407
426
|
featureDone: "Feature done"
|
|
408
427
|
},
|
|
409
428
|
messages: {
|
|
410
|
-
specCreate: "Create spec.md
|
|
429
|
+
specCreate: "Create spec.md using the CLI built-in template policy.",
|
|
411
430
|
specImprove: "Improve spec.md and change Status to Review.",
|
|
412
431
|
specApproval: "Share spec.md with the user and get approval (`A` or `A OK` format).",
|
|
413
|
-
planCreate: "Create plan.md
|
|
432
|
+
planCreate: "Create plan.md using the CLI built-in template policy.",
|
|
414
433
|
planImprove: "Improve plan.md and change Status to Review.",
|
|
415
434
|
planApproval: "Share plan.md with the user and get approval (`A` or `A OK` format).",
|
|
416
|
-
tasksCreate: "Create tasks.md
|
|
435
|
+
tasksCreate: "Create tasks.md using the CLI built-in template policy.",
|
|
417
436
|
tasksNeedAtLeastOne: "Write at least 1 task in tasks.md.",
|
|
418
437
|
tasksImprove: "Improve tasks.md and change Doc Status to Review.",
|
|
419
438
|
tasksApproval: "Share tasks.md with the user and get progress approval (`A` or `A OK` format). (Then set Doc Status to Approved)",
|
|
420
439
|
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
|
|
421
|
-
issueCreateAndWrite: "Create a GitHub Issue, fill the issue number in spec.md/tasks.md, then prepare a docs commit. (
|
|
440
|
+
issueCreateAndWrite: "Create a GitHub Issue, fill the issue number in spec.md/tasks.md, then prepare a docs commit. (CLI built-in create-issue guide)",
|
|
422
441
|
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
|
|
423
442
|
docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} docs update"',
|
|
424
443
|
projectCommitIssueUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat(#{issueNumber}): {folderName} implementation update"',
|
|
@@ -427,11 +446,15 @@ var I18N = {
|
|
|
427
446
|
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
428
447
|
tasksAllDoneButNoChecklist: 'All tasks are DONE, but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
|
|
429
448
|
tasksAllDoneButChecklist: "All tasks are DONE, but the completion checklist is not fully checked. ({checked}/{total})",
|
|
430
|
-
finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) (Share outcome/verification + get OK before marking DONE) (
|
|
431
|
-
startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (Share title + get OK before marking DOING) (
|
|
432
|
-
checkTaskStatuses: "Check task statuses. ({done}/{total}) (
|
|
449
|
+
finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) (Share outcome/verification + get OK before marking DONE) (CLI built-in execute-task guide)',
|
|
450
|
+
startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (Share title + get OK before marking DOING) (CLI built-in execute-task guide)',
|
|
451
|
+
checkTaskStatuses: "Check task statuses. ({done}/{total}) (CLI built-in execute-task guide)",
|
|
433
452
|
prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (CHECK required)",
|
|
434
|
-
|
|
453
|
+
prePrReviewFieldMissing: "tasks.md is missing the `Pre-PR Review` field. Add `- **Pre-PR Review**: Pending | Done` and run context again. (CHECK required)",
|
|
454
|
+
prePrReviewRun: "Run a pre-PR code review before creating the PR. Preferred skills: {skills} (if a better installed skill fits this change, propose it first). If no skill can run, use `{fallback}` and set `Pre-PR Review` to Done in tasks.md. Findings policy: {findingsPolicy}",
|
|
455
|
+
prePrReviewFindingsBlock: "major findings must be fixed/aligned before PR creation",
|
|
456
|
+
prePrReviewFindingsWarn: "you may proceed after sharing the risks",
|
|
457
|
+
prCreate: "Create a PR and record the PR link in tasks.md. (CLI built-in create-pr guide)",
|
|
435
458
|
prFillStatus: "Set PR Status in tasks.md to Review/Approved. (After merge, update it to Approved.)",
|
|
436
459
|
prResolveReview: "Resolve review comments and update PR Status. (PR Status: Review \u2192 Approved)",
|
|
437
460
|
prRequestReview: "Request review and update PR Status to Review.",
|
|
@@ -442,17 +465,20 @@ var I18N = {
|
|
|
442
465
|
projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
|
|
443
466
|
docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
|
|
444
467
|
docsPathIgnored: "Current feature docs path is ignored by git: {path} (docs commit detection may be limited).",
|
|
445
|
-
docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Commit message rules:
|
|
468
|
+
docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.) Commit message rules: CLI built-in git-workflow policy",
|
|
446
469
|
projectUncommittedChanges: "Project code changes are not committed. (Additional code commit needed.)",
|
|
447
470
|
legacyTasksDocStatusField: "Legacy tasks.md format detected. Add a `Doc Status` field (Review/Approved) to enable tasks approval.",
|
|
448
471
|
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
|
|
472
|
+
legacyTasksPrePrReviewField: "Legacy tasks.md format detected. Add `Pre-PR Review` before PR steps. (`- **Pre-PR Review**: Pending | Done`)",
|
|
449
473
|
workflowSpecNotApproved: "Implementation is done but spec.md Status is not Approved. (Update spec.md Status to Approved.)",
|
|
450
474
|
workflowPlanNotApproved: "Implementation is done but plan.md Status is not Approved. (Update plan.md Status to Approved.)",
|
|
451
475
|
workflowIssueMissing: "Implementation is done but Issue Number is missing. (Fill Issue Number in spec.md/tasks.md.)",
|
|
452
476
|
workflowProjectUncommittedChanges: "Commit project code changes before completing workflow. (Project worktree has uncommitted changes.)",
|
|
453
477
|
workflowPrLinkMissing: "Implementation is done but PR link is missing. (Fill the PR field in tasks.md.)",
|
|
454
478
|
workflowPrStatusMissing: "Implementation is done but PR Status is missing. (Set PR Status to Review/Approved in tasks.md.)",
|
|
455
|
-
workflowPrStatusNotApproved: "Implementation is done but PR Status is not Approved. (After merge, update PR Status to Approved in tasks.md.)"
|
|
479
|
+
workflowPrStatusNotApproved: "Implementation is done but PR Status is not Approved. (After merge, update PR Status to Approved in tasks.md.)",
|
|
480
|
+
workflowPrePrReviewMissing: "Implementation is done but `Pre-PR Review` is missing. (Add `- **Pre-PR Review**: Pending | Done` in tasks.md.)",
|
|
481
|
+
workflowPrePrReviewNotDone: "Implementation is done but `Pre-PR Review` is not Done. (Run pre-PR review, then update it to Done.)"
|
|
456
482
|
}
|
|
457
483
|
}
|
|
458
484
|
};
|
|
@@ -1033,12 +1059,12 @@ function sleep(ms) {
|
|
|
1033
1059
|
return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
|
|
1034
1060
|
}
|
|
1035
1061
|
function getDocsLockPath(docsDir) {
|
|
1036
|
-
return
|
|
1062
|
+
return path6.join(docsDir, ".lee-spec-kit.lock");
|
|
1037
1063
|
}
|
|
1038
1064
|
function getInitLockPath(targetDir) {
|
|
1039
|
-
return
|
|
1040
|
-
|
|
1041
|
-
`.lee-spec-kit.${
|
|
1065
|
+
return path6.join(
|
|
1066
|
+
path6.dirname(targetDir),
|
|
1067
|
+
`.lee-spec-kit.${path6.basename(targetDir)}.lock`
|
|
1042
1068
|
);
|
|
1043
1069
|
}
|
|
1044
1070
|
async function isStaleLock(lockPath, staleMs) {
|
|
@@ -1080,7 +1106,7 @@ function isProcessAlive(pid) {
|
|
|
1080
1106
|
}
|
|
1081
1107
|
}
|
|
1082
1108
|
async function tryAcquire(lockPath, owner) {
|
|
1083
|
-
await fs2.ensureDir(
|
|
1109
|
+
await fs2.ensureDir(path6.dirname(lockPath));
|
|
1084
1110
|
try {
|
|
1085
1111
|
const fd = await fs2.open(lockPath, "wx");
|
|
1086
1112
|
const payload = JSON.stringify(
|
|
@@ -1150,48 +1176,39 @@ function getLocalDateString(date = /* @__PURE__ */ new Date()) {
|
|
|
1150
1176
|
const day = String(date.getDate()).padStart(2, "0");
|
|
1151
1177
|
return `${year}-${month}-${day}`;
|
|
1152
1178
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
if (/^\s*-\s*(Example|Values)\s*:/.test(line)) continue;
|
|
1173
|
-
if (/^\s*-\s*(예|값)\s*:/.test(line)) continue;
|
|
1174
|
-
filtered.push(line);
|
|
1179
|
+
var ENGINE_MANAGED_AGENT_FILES = [
|
|
1180
|
+
"agents.md",
|
|
1181
|
+
"git-workflow.md",
|
|
1182
|
+
"issue-template.md",
|
|
1183
|
+
"pr-template.md"
|
|
1184
|
+
];
|
|
1185
|
+
var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
|
|
1186
|
+
var ENGINE_MANAGED_FEATURE_PATH = path6.join(
|
|
1187
|
+
"features",
|
|
1188
|
+
"feature-base"
|
|
1189
|
+
);
|
|
1190
|
+
async function pruneEngineManagedDocs(docsDir) {
|
|
1191
|
+
const removed = [];
|
|
1192
|
+
for (const file of ENGINE_MANAGED_AGENT_FILES) {
|
|
1193
|
+
const target = path6.join(docsDir, "agents", file);
|
|
1194
|
+
if (await fs2.pathExists(target)) {
|
|
1195
|
+
await fs2.remove(target);
|
|
1196
|
+
removed.push(path6.relative(docsDir, target));
|
|
1197
|
+
}
|
|
1175
1198
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
(content) => sanitizeTasksForLocal(content, lang)
|
|
1190
|
-
);
|
|
1191
|
-
}
|
|
1192
|
-
async function applyLocalWorkflowTemplateToFeatureBase(docsDir, lang) {
|
|
1193
|
-
const baseDir = path13.join(docsDir, "features", "feature-base");
|
|
1194
|
-
await applyLocalWorkflowTemplateToFeatureDir(baseDir, lang);
|
|
1199
|
+
for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
|
|
1200
|
+
const target = path6.join(docsDir, "agents", dir);
|
|
1201
|
+
if (await fs2.pathExists(target)) {
|
|
1202
|
+
await fs2.remove(target);
|
|
1203
|
+
removed.push(path6.relative(docsDir, target));
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
const featureBasePath = path6.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
|
|
1207
|
+
if (await fs2.pathExists(featureBasePath)) {
|
|
1208
|
+
await fs2.remove(featureBasePath);
|
|
1209
|
+
removed.push(path6.relative(docsDir, featureBasePath));
|
|
1210
|
+
}
|
|
1211
|
+
return removed;
|
|
1195
1212
|
}
|
|
1196
1213
|
|
|
1197
1214
|
// src/commands/init.ts
|
|
@@ -1242,7 +1259,7 @@ ${tr(lang2, "cli", "common.canceled")}`)
|
|
|
1242
1259
|
}
|
|
1243
1260
|
async function runInit(options) {
|
|
1244
1261
|
const cwd = process.cwd();
|
|
1245
|
-
const defaultName =
|
|
1262
|
+
const defaultName = path6.basename(cwd);
|
|
1246
1263
|
let projectName = options.name || defaultName;
|
|
1247
1264
|
let projectType = options.type;
|
|
1248
1265
|
let components = parseComponentsOption(options.components);
|
|
@@ -1252,7 +1269,7 @@ async function runInit(options) {
|
|
|
1252
1269
|
let pushDocs = typeof options.pushDocs === "boolean" ? options.pushDocs : void 0;
|
|
1253
1270
|
let docsRemote = options.docsRemote;
|
|
1254
1271
|
let projectRoot;
|
|
1255
|
-
const targetDir =
|
|
1272
|
+
const targetDir = path6.resolve(cwd, options.dir || "./docs");
|
|
1256
1273
|
const skipPrompts = !!options.yes || !!options.nonInteractive;
|
|
1257
1274
|
if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
|
|
1258
1275
|
throw createCliError(
|
|
@@ -1591,9 +1608,9 @@ async function runInit(options) {
|
|
|
1591
1608
|
);
|
|
1592
1609
|
console.log();
|
|
1593
1610
|
const templatesDir = getTemplatesDir();
|
|
1594
|
-
const commonPath =
|
|
1611
|
+
const commonPath = path6.join(templatesDir, lang, "common");
|
|
1595
1612
|
const templateProjectType = toTemplateProjectType(projectType);
|
|
1596
|
-
const typePath =
|
|
1613
|
+
const typePath = path6.join(templatesDir, lang, templateProjectType);
|
|
1597
1614
|
if (await fs2.pathExists(commonPath)) {
|
|
1598
1615
|
await copyTemplates(commonPath, targetDir);
|
|
1599
1616
|
}
|
|
@@ -1604,13 +1621,13 @@ async function runInit(options) {
|
|
|
1604
1621
|
}
|
|
1605
1622
|
await copyTemplates(typePath, targetDir);
|
|
1606
1623
|
if (projectType === "multi" && !isDefaultFullstackComponents(components)) {
|
|
1607
|
-
const featuresRoot =
|
|
1608
|
-
await fs2.remove(
|
|
1609
|
-
await fs2.remove(
|
|
1624
|
+
const featuresRoot = path6.join(targetDir, "features");
|
|
1625
|
+
await fs2.remove(path6.join(featuresRoot, "fe"));
|
|
1626
|
+
await fs2.remove(path6.join(featuresRoot, "be"));
|
|
1610
1627
|
for (const component of components) {
|
|
1611
|
-
const componentDir =
|
|
1628
|
+
const componentDir = path6.join(featuresRoot, component);
|
|
1612
1629
|
await fs2.ensureDir(componentDir);
|
|
1613
|
-
const readmePath =
|
|
1630
|
+
const readmePath = path6.join(componentDir, "README.md");
|
|
1614
1631
|
if (!await fs2.pathExists(readmePath)) {
|
|
1615
1632
|
await fs2.writeFile(
|
|
1616
1633
|
readmePath,
|
|
@@ -1630,9 +1647,7 @@ Store ${component} feature specs here.
|
|
|
1630
1647
|
"{{featurePath}}": featurePath
|
|
1631
1648
|
};
|
|
1632
1649
|
await replaceInFiles(targetDir, replacements);
|
|
1633
|
-
|
|
1634
|
-
await applyLocalWorkflowTemplateToFeatureBase(targetDir, lang);
|
|
1635
|
-
}
|
|
1650
|
+
await pruneEngineManagedDocs(targetDir);
|
|
1636
1651
|
const config = {
|
|
1637
1652
|
projectName,
|
|
1638
1653
|
projectType,
|
|
@@ -1640,7 +1655,11 @@ Store ${component} feature specs here.
|
|
|
1640
1655
|
lang,
|
|
1641
1656
|
createdAt: getLocalDateString(),
|
|
1642
1657
|
docsRepo,
|
|
1643
|
-
workflow: {
|
|
1658
|
+
workflow: {
|
|
1659
|
+
mode: workflowMode,
|
|
1660
|
+
codeDirtyScope: "auto",
|
|
1661
|
+
prePrReview: { skills: ["code-review-excellence"] }
|
|
1662
|
+
},
|
|
1644
1663
|
pr: {
|
|
1645
1664
|
screenshots: { upload: false }
|
|
1646
1665
|
},
|
|
@@ -1659,7 +1678,7 @@ Store ${component} feature specs here.
|
|
|
1659
1678
|
config.projectRoot = projectRoot;
|
|
1660
1679
|
}
|
|
1661
1680
|
}
|
|
1662
|
-
const configPath =
|
|
1681
|
+
const configPath = path6.join(targetDir, ".lee-spec-kit.json");
|
|
1663
1682
|
await fs2.writeJson(configPath, config, { spaces: 2 });
|
|
1664
1683
|
console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
|
|
1665
1684
|
console.log();
|
|
@@ -1728,7 +1747,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
1728
1747
|
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
1729
1748
|
runGit(["init"], cwd);
|
|
1730
1749
|
}
|
|
1731
|
-
const relativePath =
|
|
1750
|
+
const relativePath = path6.relative(cwd, targetDir);
|
|
1732
1751
|
const stagedBeforeAdd = getCachedStagedFiles(cwd);
|
|
1733
1752
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
1734
1753
|
console.log(
|
|
@@ -1785,17 +1804,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
1785
1804
|
}
|
|
1786
1805
|
function getAncestorDirs(startDir) {
|
|
1787
1806
|
const dirs = [];
|
|
1788
|
-
let current =
|
|
1807
|
+
let current = path6.resolve(startDir);
|
|
1789
1808
|
while (true) {
|
|
1790
1809
|
dirs.push(current);
|
|
1791
|
-
const parent =
|
|
1810
|
+
const parent = path6.dirname(current);
|
|
1792
1811
|
if (parent === current) break;
|
|
1793
1812
|
current = parent;
|
|
1794
1813
|
}
|
|
1795
1814
|
return dirs;
|
|
1796
1815
|
}
|
|
1797
1816
|
function hasWorkspaceBoundary(dir) {
|
|
1798
|
-
return fs2.existsSync(
|
|
1817
|
+
return fs2.existsSync(path6.join(dir, "package.json")) || fs2.existsSync(path6.join(dir, ".git"));
|
|
1799
1818
|
}
|
|
1800
1819
|
function getSearchBaseDirs(cwd) {
|
|
1801
1820
|
const ancestors = getAncestorDirs(cwd);
|
|
@@ -1808,21 +1827,21 @@ function getSearchBaseDirs(cwd) {
|
|
|
1808
1827
|
async function getConfig(cwd) {
|
|
1809
1828
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
1810
1829
|
const baseDirs = [
|
|
1811
|
-
...explicitDocsDir ? [
|
|
1830
|
+
...explicitDocsDir ? [path6.resolve(explicitDocsDir)] : [],
|
|
1812
1831
|
...getSearchBaseDirs(cwd)
|
|
1813
1832
|
];
|
|
1814
1833
|
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
1815
1834
|
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
1816
1835
|
for (const baseDir of baseDirs) {
|
|
1817
|
-
const resolvedBaseDir =
|
|
1836
|
+
const resolvedBaseDir = path6.resolve(baseDir);
|
|
1818
1837
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
1819
1838
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
1820
|
-
const possibleDocsDirs = [
|
|
1839
|
+
const possibleDocsDirs = [path6.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
1821
1840
|
for (const docsDir of possibleDocsDirs) {
|
|
1822
|
-
const resolvedDocsDir =
|
|
1841
|
+
const resolvedDocsDir = path6.resolve(docsDir);
|
|
1823
1842
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
1824
1843
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
1825
|
-
const configPath =
|
|
1844
|
+
const configPath = path6.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
1826
1845
|
if (await fs2.pathExists(configPath)) {
|
|
1827
1846
|
try {
|
|
1828
1847
|
const configFile = await fs2.readJson(configPath);
|
|
@@ -1848,18 +1867,26 @@ async function getConfig(cwd) {
|
|
|
1848
1867
|
} catch {
|
|
1849
1868
|
}
|
|
1850
1869
|
}
|
|
1851
|
-
const agentsPath =
|
|
1852
|
-
const featuresPath =
|
|
1870
|
+
const agentsPath = path6.join(resolvedDocsDir, "agents");
|
|
1871
|
+
const featuresPath = path6.join(resolvedDocsDir, "features");
|
|
1853
1872
|
if (await fs2.pathExists(agentsPath) && await fs2.pathExists(featuresPath)) {
|
|
1854
|
-
const bePath =
|
|
1855
|
-
const fePath =
|
|
1873
|
+
const bePath = path6.join(featuresPath, "be");
|
|
1874
|
+
const fePath = path6.join(featuresPath, "fe");
|
|
1856
1875
|
const projectType = await fs2.pathExists(bePath) || await fs2.pathExists(fePath) ? "multi" : "single";
|
|
1857
1876
|
const components = projectType === "multi" ? resolveProjectComponents("multi", ["fe", "be"]) : void 0;
|
|
1858
|
-
const
|
|
1877
|
+
const langProbeCandidates = [
|
|
1878
|
+
path6.join(agentsPath, "custom.md"),
|
|
1879
|
+
path6.join(agentsPath, "constitution.md"),
|
|
1880
|
+
path6.join(agentsPath, "agents.md")
|
|
1881
|
+
];
|
|
1859
1882
|
let lang = "en";
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1883
|
+
for (const candidate of langProbeCandidates) {
|
|
1884
|
+
if (!await fs2.pathExists(candidate)) continue;
|
|
1885
|
+
const content = await fs2.readFile(candidate, "utf-8");
|
|
1886
|
+
if (/[가-힣]/.test(content)) {
|
|
1887
|
+
lang = "ko";
|
|
1888
|
+
break;
|
|
1889
|
+
}
|
|
1863
1890
|
}
|
|
1864
1891
|
return { docsDir: resolvedDocsDir, projectType, components, lang };
|
|
1865
1892
|
}
|
|
@@ -1867,6 +1894,51 @@ async function getConfig(cwd) {
|
|
|
1867
1894
|
}
|
|
1868
1895
|
return null;
|
|
1869
1896
|
}
|
|
1897
|
+
function normalizeTrailingBlankLines(content) {
|
|
1898
|
+
return content.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
1899
|
+
}
|
|
1900
|
+
function sanitizeSpecForLocal(content) {
|
|
1901
|
+
const withoutIssueLine = content.split("\n").filter(
|
|
1902
|
+
(line) => !/^\s*-\s*\*\*(Issue Number|이슈 번호)\*\*\s*:/.test(line)
|
|
1903
|
+
).join("\n");
|
|
1904
|
+
return normalizeTrailingBlankLines(withoutIssueLine);
|
|
1905
|
+
}
|
|
1906
|
+
function sanitizeTasksForLocal(content, lang) {
|
|
1907
|
+
let next = content;
|
|
1908
|
+
next = next.replace(
|
|
1909
|
+
/^##\s+GitHub Issue\s*$/m,
|
|
1910
|
+
lang === "ko" ? "## \uB85C\uCEEC \uCD94\uC801 \uC815\uBCF4" : "## Local Tracking"
|
|
1911
|
+
);
|
|
1912
|
+
const lines = next.split("\n");
|
|
1913
|
+
const filtered = [];
|
|
1914
|
+
for (const line of lines) {
|
|
1915
|
+
if (/^\s*-\s*\*\*(Issue|PR|PR Status|PR 상태|Pre-PR Review|PR 전 리뷰)\*\*\s*:/.test(
|
|
1916
|
+
line
|
|
1917
|
+
)) {
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
if (/^\s*-\s*(Example|Values)\s*:/.test(line)) continue;
|
|
1921
|
+
if (/^\s*-\s*(예|값)\s*:/.test(line)) continue;
|
|
1922
|
+
if (/^\s*-\s*Mark\s+`?Done`?/i.test(line)) continue;
|
|
1923
|
+
if (/^\s*-\s*사전 코드리뷰 완료 후/.test(line)) continue;
|
|
1924
|
+
filtered.push(line);
|
|
1925
|
+
}
|
|
1926
|
+
next = filtered.join("\n");
|
|
1927
|
+
next = next.replace(/feat\/\{issue-number\}-/g, "feat/").replace(/feat\/\{이슈번호\}-/g, "feat/").replace(/feat\/-/g, "feat/");
|
|
1928
|
+
return normalizeTrailingBlankLines(next);
|
|
1929
|
+
}
|
|
1930
|
+
async function patchMarkdownIfExists(filePath, transform) {
|
|
1931
|
+
if (!await fs2.pathExists(filePath)) return;
|
|
1932
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
1933
|
+
await fs2.writeFile(filePath, transform(content), "utf-8");
|
|
1934
|
+
}
|
|
1935
|
+
async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
|
|
1936
|
+
await patchMarkdownIfExists(path6.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
1937
|
+
await patchMarkdownIfExists(
|
|
1938
|
+
path6.join(featureDir, "tasks.md"),
|
|
1939
|
+
(content) => sanitizeTasksForLocal(content, lang)
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1870
1942
|
|
|
1871
1943
|
// src/commands/feature.ts
|
|
1872
1944
|
function featureCommand(program2) {
|
|
@@ -2005,19 +2077,25 @@ async function runFeature(name, options) {
|
|
|
2005
2077
|
}
|
|
2006
2078
|
let featuresDir;
|
|
2007
2079
|
if (projectType === "multi") {
|
|
2008
|
-
featuresDir =
|
|
2080
|
+
featuresDir = path6.join(docsDir, "features", component);
|
|
2009
2081
|
} else {
|
|
2010
|
-
featuresDir =
|
|
2082
|
+
featuresDir = path6.join(docsDir, "features");
|
|
2011
2083
|
}
|
|
2012
2084
|
const featureFolderName = `${featureId}-${name}`;
|
|
2013
|
-
const featureDir =
|
|
2085
|
+
const featureDir = path6.join(featuresDir, featureFolderName);
|
|
2014
2086
|
if (await fs2.pathExists(featureDir)) {
|
|
2015
2087
|
throw createCliError(
|
|
2016
2088
|
"INVALID_ARGUMENT",
|
|
2017
2089
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
2018
2090
|
);
|
|
2019
2091
|
}
|
|
2020
|
-
const featureBasePath =
|
|
2092
|
+
const featureBasePath = path6.join(
|
|
2093
|
+
getTemplatesDir(),
|
|
2094
|
+
lang,
|
|
2095
|
+
toTemplateProjectType(projectType),
|
|
2096
|
+
"features",
|
|
2097
|
+
"feature-base"
|
|
2098
|
+
);
|
|
2021
2099
|
if (!await fs2.pathExists(featureBasePath)) {
|
|
2022
2100
|
throw createCliError("DOCS_NOT_FOUND", tr(lang, "cli", "feature.baseNotFound"));
|
|
2023
2101
|
}
|
|
@@ -2069,7 +2147,7 @@ async function runFeature(name, options) {
|
|
|
2069
2147
|
featureName: name,
|
|
2070
2148
|
component: projectType === "multi" ? component : void 0,
|
|
2071
2149
|
featurePath: featureDir,
|
|
2072
|
-
featurePathFromDocs:
|
|
2150
|
+
featurePathFromDocs: path6.relative(docsDir, featureDir)
|
|
2073
2151
|
};
|
|
2074
2152
|
},
|
|
2075
2153
|
{ owner: "feature" }
|
|
@@ -2081,9 +2159,9 @@ function sleep2(ms) {
|
|
|
2081
2159
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
2082
2160
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2083
2161
|
const candidates = [
|
|
2084
|
-
...explicitDocsDir ? [
|
|
2085
|
-
|
|
2086
|
-
|
|
2162
|
+
...explicitDocsDir ? [path6.resolve(explicitDocsDir)] : [],
|
|
2163
|
+
path6.resolve(cwd, "docs"),
|
|
2164
|
+
path6.resolve(cwd)
|
|
2087
2165
|
];
|
|
2088
2166
|
const endAt = Date.now() + timeoutMs;
|
|
2089
2167
|
while (Date.now() < endAt) {
|
|
@@ -2110,11 +2188,11 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
2110
2188
|
return getConfig(cwd);
|
|
2111
2189
|
}
|
|
2112
2190
|
async function getNextFeatureId(docsDir, projectType, components) {
|
|
2113
|
-
const featuresDir =
|
|
2191
|
+
const featuresDir = path6.join(docsDir, "features");
|
|
2114
2192
|
let max = 0;
|
|
2115
2193
|
const scanDirs = [];
|
|
2116
2194
|
if (projectType === "multi") {
|
|
2117
|
-
scanDirs.push(...components.map((component) =>
|
|
2195
|
+
scanDirs.push(...components.map((component) => path6.join(featuresDir, component)));
|
|
2118
2196
|
} else {
|
|
2119
2197
|
scanDirs.push(featuresDir);
|
|
2120
2198
|
}
|
|
@@ -2136,6 +2214,7 @@ async function getNextFeatureId(docsDir, projectType, components) {
|
|
|
2136
2214
|
}
|
|
2137
2215
|
|
|
2138
2216
|
// src/utils/workflow.ts
|
|
2217
|
+
var DEFAULT_PRE_PR_REVIEW_SKILLS = ["code-review-excellence"];
|
|
2139
2218
|
function resolveWorkflowPolicy(workflow) {
|
|
2140
2219
|
const mode = workflow?.mode === "local" ? "local" : "github";
|
|
2141
2220
|
const policy = mode === "local" ? {
|
|
@@ -2182,6 +2261,28 @@ function resolveCodeDirtyScopePolicy(workflow, projectType) {
|
|
|
2182
2261
|
}
|
|
2183
2262
|
return projectType === "multi" ? "component" : "repo";
|
|
2184
2263
|
}
|
|
2264
|
+
function normalizeSkillList(input) {
|
|
2265
|
+
if (!Array.isArray(input)) return [];
|
|
2266
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
2267
|
+
for (const raw of input) {
|
|
2268
|
+
const value = String(raw || "").trim();
|
|
2269
|
+
if (!value) continue;
|
|
2270
|
+
deduped.add(value);
|
|
2271
|
+
}
|
|
2272
|
+
return [...deduped];
|
|
2273
|
+
}
|
|
2274
|
+
function resolvePrePrReviewPolicy(workflow) {
|
|
2275
|
+
const workflowPolicy = resolveWorkflowPolicy(workflow);
|
|
2276
|
+
const configured = workflow?.prePrReview;
|
|
2277
|
+
const configuredSkills = normalizeSkillList(configured?.skills);
|
|
2278
|
+
const configuredEnabled = typeof configured?.enabled === "boolean" ? configured.enabled : workflowPolicy.requirePr;
|
|
2279
|
+
return {
|
|
2280
|
+
enabled: workflowPolicy.requirePr ? configuredEnabled : false,
|
|
2281
|
+
skills: configuredSkills.length > 0 ? configuredSkills : DEFAULT_PRE_PR_REVIEW_SKILLS,
|
|
2282
|
+
fallback: configured?.fallback === "builtin-checklist" ? configured.fallback : "builtin-checklist",
|
|
2283
|
+
blockOnFindings: typeof configured?.blockOnFindings === "boolean" ? configured.blockOnFindings : true
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2185
2286
|
|
|
2186
2287
|
// src/utils/context/steps.ts
|
|
2187
2288
|
function isCompletionChecklistDone(feature) {
|
|
@@ -2196,11 +2297,18 @@ function isImplementationDone(feature) {
|
|
|
2196
2297
|
function isPrMetadataConfigured(feature) {
|
|
2197
2298
|
return feature.docs.prFieldExists && feature.docs.prStatusFieldExists;
|
|
2198
2299
|
}
|
|
2199
|
-
function isFeatureDone(feature, workflowPolicy) {
|
|
2200
|
-
return feature.specStatus === "Approved" && feature.planStatus === "Approved" && !feature.git.docsHasUncommittedChanges && !feature.git.projectHasUncommittedChanges && feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isTasksDocApproved(feature) && (!workflowPolicy.requireIssue || !!feature.issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured(feature) && !!feature.pr.link) && (!workflowPolicy.requireReview || feature.pr.status === "Approved");
|
|
2300
|
+
function isFeatureDone(feature, workflowPolicy, prePrReviewPolicy) {
|
|
2301
|
+
return feature.specStatus === "Approved" && feature.planStatus === "Approved" && !feature.git.docsHasUncommittedChanges && !feature.git.projectHasUncommittedChanges && feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isTasksDocApproved(feature) && (!workflowPolicy.requireIssue || !!feature.issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured(feature) && !!feature.pr.link) && (!workflowPolicy.requireReview || feature.pr.status === "Approved") && (!prePrReviewPolicy.enabled || feature.docs.prePrReviewFieldExists && feature.prePrReview.status === "Done");
|
|
2302
|
+
}
|
|
2303
|
+
function formatSkillList(skills) {
|
|
2304
|
+
return skills.join(", ");
|
|
2305
|
+
}
|
|
2306
|
+
function getFindingsPolicyText(lang, blockOnFindings) {
|
|
2307
|
+
return blockOnFindings ? tr(lang, "messages", "prePrReviewFindingsBlock") : tr(lang, "messages", "prePrReviewFindingsWarn");
|
|
2201
2308
|
}
|
|
2202
2309
|
function getStepDefinitions(lang, workflow) {
|
|
2203
2310
|
const workflowPolicy = resolveWorkflowPolicy(workflow);
|
|
2311
|
+
const prePrReviewPolicy = resolvePrePrReviewPolicy(workflow);
|
|
2204
2312
|
return [
|
|
2205
2313
|
{
|
|
2206
2314
|
step: 1,
|
|
@@ -2401,10 +2509,10 @@ function getStepDefinitions(lang, workflow) {
|
|
|
2401
2509
|
step: 9,
|
|
2402
2510
|
name: tr(lang, "steps", "branchCreate"),
|
|
2403
2511
|
checklist: {
|
|
2404
|
-
done: (f) => !workflowPolicy.requireBranch || f.git.onExpectedBranch || isImplementationDone(f) || isFeatureDone(f, workflowPolicy)
|
|
2512
|
+
done: (f) => !workflowPolicy.requireBranch || f.git.onExpectedBranch || isImplementationDone(f) || isFeatureDone(f, workflowPolicy, prePrReviewPolicy)
|
|
2405
2513
|
},
|
|
2406
2514
|
current: {
|
|
2407
|
-
when: (f) => workflowPolicy.requireBranch && !!f.issueNumber && f.tasks.total > 0 && f.tasks.done < f.tasks.total && !isFeatureDone(f, workflowPolicy) && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
|
|
2515
|
+
when: (f) => workflowPolicy.requireBranch && !!f.issueNumber && f.tasks.total > 0 && f.tasks.done < f.tasks.total && !isFeatureDone(f, workflowPolicy, prePrReviewPolicy) && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
|
|
2408
2516
|
actions: (f) => {
|
|
2409
2517
|
if (!f.git.projectBranchAvailable || !f.git.projectGitCwd) {
|
|
2410
2518
|
return [
|
|
@@ -2499,6 +2607,34 @@ function getStepDefinitions(lang, workflow) {
|
|
|
2499
2607
|
}
|
|
2500
2608
|
];
|
|
2501
2609
|
}
|
|
2610
|
+
if (f.git.projectHasUncommittedChanges) {
|
|
2611
|
+
if (!f.git.projectGitCwd) {
|
|
2612
|
+
return [
|
|
2613
|
+
{
|
|
2614
|
+
type: "instruction",
|
|
2615
|
+
category: "task_execute",
|
|
2616
|
+
message: tr(lang, "messages", "standaloneNeedsProjectRoot")
|
|
2617
|
+
}
|
|
2618
|
+
];
|
|
2619
|
+
}
|
|
2620
|
+
return [
|
|
2621
|
+
{
|
|
2622
|
+
type: "command",
|
|
2623
|
+
category: "task_execute",
|
|
2624
|
+
requiresUserCheck: true,
|
|
2625
|
+
scope: "project",
|
|
2626
|
+
cwd: f.git.projectGitCwd,
|
|
2627
|
+
cmd: f.issueNumber ? tr(lang, "messages", "projectCommitIssueUpdate", {
|
|
2628
|
+
projectGitCwd: f.git.projectGitCwd,
|
|
2629
|
+
issueNumber: f.issueNumber,
|
|
2630
|
+
folderName: f.folderName
|
|
2631
|
+
}) : tr(lang, "messages", "projectCommitUpdate", {
|
|
2632
|
+
projectGitCwd: f.git.projectGitCwd,
|
|
2633
|
+
folderName: f.folderName
|
|
2634
|
+
})
|
|
2635
|
+
}
|
|
2636
|
+
];
|
|
2637
|
+
}
|
|
2502
2638
|
return [
|
|
2503
2639
|
{
|
|
2504
2640
|
type: "instruction",
|
|
@@ -2587,6 +2723,61 @@ function getStepDefinitions(lang, workflow) {
|
|
|
2587
2723
|
},
|
|
2588
2724
|
{
|
|
2589
2725
|
step: 12,
|
|
2726
|
+
name: tr(lang, "steps", "prePrReview"),
|
|
2727
|
+
checklist: {
|
|
2728
|
+
done: (f) => !prePrReviewPolicy.enabled || f.docs.prePrReviewFieldExists && f.prePrReview.status === "Done"
|
|
2729
|
+
},
|
|
2730
|
+
current: {
|
|
2731
|
+
when: (f) => prePrReviewPolicy.enabled && workflowPolicy.requirePr && f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done && isCompletionChecklistDone(f) && !f.git.docsHasUncommittedChanges && !f.git.projectHasUncommittedChanges && (!isPrMetadataConfigured(f) || !f.pr.link) && (!f.docs.prePrReviewFieldExists || f.prePrReview.status !== "Done"),
|
|
2732
|
+
actions: (f) => {
|
|
2733
|
+
if (!prePrReviewPolicy.enabled) return [];
|
|
2734
|
+
if (!f.docs.prePrReviewFieldExists) {
|
|
2735
|
+
return [
|
|
2736
|
+
{
|
|
2737
|
+
type: "instruction",
|
|
2738
|
+
category: "pr_metadata_migrate",
|
|
2739
|
+
requiresUserCheck: true,
|
|
2740
|
+
message: tr(lang, "messages", "prePrReviewFieldMissing")
|
|
2741
|
+
}
|
|
2742
|
+
];
|
|
2743
|
+
}
|
|
2744
|
+
if (!prePrReviewPolicy.skills.length) {
|
|
2745
|
+
return [
|
|
2746
|
+
{
|
|
2747
|
+
type: "instruction",
|
|
2748
|
+
category: "pre_pr_review",
|
|
2749
|
+
requiresUserCheck: true,
|
|
2750
|
+
message: tr(lang, "messages", "prePrReviewRun", {
|
|
2751
|
+
skills: "code-review-excellence",
|
|
2752
|
+
fallback: prePrReviewPolicy.fallback,
|
|
2753
|
+
findingsPolicy: getFindingsPolicyText(
|
|
2754
|
+
lang,
|
|
2755
|
+
prePrReviewPolicy.blockOnFindings
|
|
2756
|
+
)
|
|
2757
|
+
})
|
|
2758
|
+
}
|
|
2759
|
+
];
|
|
2760
|
+
}
|
|
2761
|
+
return [
|
|
2762
|
+
{
|
|
2763
|
+
type: "instruction",
|
|
2764
|
+
category: "pre_pr_review",
|
|
2765
|
+
requiresUserCheck: true,
|
|
2766
|
+
message: tr(lang, "messages", "prePrReviewRun", {
|
|
2767
|
+
skills: formatSkillList(prePrReviewPolicy.skills),
|
|
2768
|
+
fallback: prePrReviewPolicy.fallback,
|
|
2769
|
+
findingsPolicy: getFindingsPolicyText(
|
|
2770
|
+
lang,
|
|
2771
|
+
prePrReviewPolicy.blockOnFindings
|
|
2772
|
+
)
|
|
2773
|
+
})
|
|
2774
|
+
}
|
|
2775
|
+
];
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
},
|
|
2779
|
+
{
|
|
2780
|
+
step: 13,
|
|
2590
2781
|
name: tr(lang, "steps", "prCreate"),
|
|
2591
2782
|
checklist: {
|
|
2592
2783
|
done: (f) => !workflowPolicy.requirePr || isPrMetadataConfigured(f) && !!f.pr.link
|
|
@@ -2616,7 +2807,7 @@ function getStepDefinitions(lang, workflow) {
|
|
|
2616
2807
|
}
|
|
2617
2808
|
},
|
|
2618
2809
|
{
|
|
2619
|
-
step:
|
|
2810
|
+
step: 14,
|
|
2620
2811
|
name: tr(lang, "steps", "codeReview"),
|
|
2621
2812
|
checklist: {
|
|
2622
2813
|
done: (f) => !workflowPolicy.requireReview || isPrMetadataConfigured(f) && f.pr.status === "Approved"
|
|
@@ -2654,11 +2845,13 @@ function getStepDefinitions(lang, workflow) {
|
|
|
2654
2845
|
}
|
|
2655
2846
|
},
|
|
2656
2847
|
{
|
|
2657
|
-
step:
|
|
2848
|
+
step: 15,
|
|
2658
2849
|
name: tr(lang, "steps", "featureDone"),
|
|
2659
|
-
checklist: {
|
|
2850
|
+
checklist: {
|
|
2851
|
+
done: (f) => isFeatureDone(f, workflowPolicy, prePrReviewPolicy)
|
|
2852
|
+
},
|
|
2660
2853
|
current: {
|
|
2661
|
-
when: (f) => isFeatureDone(f, workflowPolicy),
|
|
2854
|
+
when: (f) => isFeatureDone(f, workflowPolicy, prePrReviewPolicy),
|
|
2662
2855
|
actions: () => [
|
|
2663
2856
|
{
|
|
2664
2857
|
type: "instruction",
|
|
@@ -2889,6 +3082,14 @@ function parseDocStatus(value) {
|
|
|
2889
3082
|
if (normalized === "review") return "Review";
|
|
2890
3083
|
return "Approved";
|
|
2891
3084
|
}
|
|
3085
|
+
function parsePrePrReviewStatus(value) {
|
|
3086
|
+
if (!value) return void 0;
|
|
3087
|
+
const trimmed = value.trim();
|
|
3088
|
+
if (!trimmed || trimmed.includes("|")) return void 0;
|
|
3089
|
+
if (/^done$/i.test(trimmed)) return "Done";
|
|
3090
|
+
if (/^pending$/i.test(trimmed)) return "Pending";
|
|
3091
|
+
return void 0;
|
|
3092
|
+
}
|
|
2892
3093
|
function parseIssueNumber(value) {
|
|
2893
3094
|
if (!value) return void 0;
|
|
2894
3095
|
const match = value.match(/#?(\d+)/);
|
|
@@ -2906,13 +3107,13 @@ function parsePrLink(value) {
|
|
|
2906
3107
|
return trimmed;
|
|
2907
3108
|
}
|
|
2908
3109
|
function normalizeGitPath(value) {
|
|
2909
|
-
return value.split(
|
|
3110
|
+
return value.split(path6.sep).join("/");
|
|
2910
3111
|
}
|
|
2911
3112
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
2912
|
-
const relativeDocsDir =
|
|
3113
|
+
const relativeDocsDir = path6.relative(projectGitCwd, docsDir);
|
|
2913
3114
|
if (!relativeDocsDir) return [];
|
|
2914
|
-
if (
|
|
2915
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
3115
|
+
if (path6.isAbsolute(relativeDocsDir)) return [];
|
|
3116
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path6.sep}`)) {
|
|
2916
3117
|
return [];
|
|
2917
3118
|
}
|
|
2918
3119
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(/\/+$/, "");
|
|
@@ -2944,16 +3145,16 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
|
|
|
2944
3145
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
2945
3146
|
candidates.map((candidate) => {
|
|
2946
3147
|
if (!candidate) return "";
|
|
2947
|
-
if (!
|
|
2948
|
-
const relative =
|
|
3148
|
+
if (!path6.isAbsolute(candidate)) return candidate;
|
|
3149
|
+
const relative = path6.relative(projectGitCwd, candidate);
|
|
2949
3150
|
if (!relative) return "";
|
|
2950
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
3151
|
+
if (relative === ".." || relative.startsWith(`..${path6.sep}`)) return "";
|
|
2951
3152
|
return relative;
|
|
2952
3153
|
}).filter(Boolean)
|
|
2953
3154
|
);
|
|
2954
3155
|
const existing = [];
|
|
2955
3156
|
for (const candidate of normalizedCandidates) {
|
|
2956
|
-
if (await fs2.pathExists(
|
|
3157
|
+
if (await fs2.pathExists(path6.join(projectGitCwd, candidate))) {
|
|
2957
3158
|
existing.push(candidate);
|
|
2958
3159
|
}
|
|
2959
3160
|
}
|
|
@@ -3015,13 +3216,14 @@ function isPrMetadataConfigured2(feature) {
|
|
|
3015
3216
|
async function parseFeature(featurePath, type, context, options) {
|
|
3016
3217
|
const lang = options.lang;
|
|
3017
3218
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
3018
|
-
const
|
|
3219
|
+
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
3220
|
+
const folderName = path6.basename(featurePath);
|
|
3019
3221
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
3020
3222
|
const id = match?.[1];
|
|
3021
3223
|
const slug = match?.[2] || folderName;
|
|
3022
|
-
const specPath =
|
|
3023
|
-
const planPath =
|
|
3024
|
-
const tasksPath =
|
|
3224
|
+
const specPath = path6.join(featurePath, "spec.md");
|
|
3225
|
+
const planPath = path6.join(featurePath, "plan.md");
|
|
3226
|
+
const tasksPath = path6.join(featurePath, "tasks.md");
|
|
3025
3227
|
let specStatus;
|
|
3026
3228
|
let issueNumber;
|
|
3027
3229
|
const specExists = await fs2.pathExists(specPath);
|
|
@@ -3046,10 +3248,12 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
3046
3248
|
let tasksDocStatus;
|
|
3047
3249
|
let tasksDocStatusFieldExists = false;
|
|
3048
3250
|
let completionChecklist;
|
|
3251
|
+
let prePrReviewStatus;
|
|
3049
3252
|
let prLink;
|
|
3050
3253
|
let prStatus;
|
|
3051
3254
|
let prFieldExists = false;
|
|
3052
3255
|
let prStatusFieldExists = false;
|
|
3256
|
+
let prePrReviewFieldExists = false;
|
|
3053
3257
|
if (tasksExists) {
|
|
3054
3258
|
const content = await fs2.readFile(tasksPath, "utf-8");
|
|
3055
3259
|
const { summary, activeTask: active, nextTodoTask: nextTodo } = parseTasks(content);
|
|
@@ -3073,6 +3277,15 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
3073
3277
|
const prStatusValue = extractFirstSpecValue(content, ["PR \uC0C1\uD0DC", "PR Status"]);
|
|
3074
3278
|
prStatusFieldExists = hasAnySpecKey(content, ["PR \uC0C1\uD0DC", "PR Status"]);
|
|
3075
3279
|
prStatus = parseDocStatus(prStatusValue);
|
|
3280
|
+
const prePrReviewValue = extractFirstSpecValue(content, [
|
|
3281
|
+
"PR \uC804 \uB9AC\uBDF0",
|
|
3282
|
+
"Pre-PR Review"
|
|
3283
|
+
]);
|
|
3284
|
+
prePrReviewFieldExists = hasAnySpecKey(content, [
|
|
3285
|
+
"PR \uC804 \uB9AC\uBDF0",
|
|
3286
|
+
"Pre-PR Review"
|
|
3287
|
+
]);
|
|
3288
|
+
prePrReviewStatus = parsePrePrReviewStatus(prePrReviewValue);
|
|
3076
3289
|
}
|
|
3077
3290
|
const warnings = [];
|
|
3078
3291
|
if (context.projectBranchAvailable === false) {
|
|
@@ -3084,7 +3297,7 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
3084
3297
|
slug,
|
|
3085
3298
|
folderName
|
|
3086
3299
|
);
|
|
3087
|
-
const relativeFeaturePathFromDocs =
|
|
3300
|
+
const relativeFeaturePathFromDocs = path6.relative(context.docsDir, featurePath);
|
|
3088
3301
|
const docsPathIgnored = isGitPathIgnored(
|
|
3089
3302
|
context.docsGitCwd,
|
|
3090
3303
|
relativeFeaturePathFromDocs
|
|
@@ -3128,6 +3341,9 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
3128
3341
|
if (tasksExists && workflowPolicy.requirePr && (!prFieldExists || !prStatusFieldExists)) {
|
|
3129
3342
|
warnings.push(tr(lang, "warnings", "legacyTasksPrFields"));
|
|
3130
3343
|
}
|
|
3344
|
+
if (tasksExists && prePrReviewPolicy.enabled && !prePrReviewFieldExists) {
|
|
3345
|
+
warnings.push(tr(lang, "warnings", "legacyTasksPrePrReviewField"));
|
|
3346
|
+
}
|
|
3131
3347
|
if (tasksExists && !tasksDocStatusFieldExists) {
|
|
3132
3348
|
warnings.push(tr(lang, "warnings", "legacyTasksDocStatusField"));
|
|
3133
3349
|
}
|
|
@@ -3139,7 +3355,7 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
3139
3355
|
}
|
|
3140
3356
|
const tasksDocApproved = !tasksDocStatusFieldExists || tasksDocStatus === "Approved";
|
|
3141
3357
|
const implementationDone = tasksExists && tasksSummary.total > 0 && tasksSummary.total === tasksSummary.done && isCompletionChecklistDone2({ completionChecklist }) && tasksDocApproved;
|
|
3142
|
-
const workflowDone = implementationDone && !docsHasUncommittedChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink) && (!workflowPolicy.requireReview || prStatus === "Approved");
|
|
3358
|
+
const workflowDone = implementationDone && !docsHasUncommittedChanges && !projectHasUncommittedChanges && specStatus === "Approved" && planStatus === "Approved" && (!workflowPolicy.requireIssue || !!issueNumber) && (!workflowPolicy.requirePr || isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink) && (!workflowPolicy.requireReview || prStatus === "Approved") && (!prePrReviewPolicy.enabled || prePrReviewFieldExists && prePrReviewStatus === "Done");
|
|
3143
3359
|
if (implementationDone && !workflowDone) {
|
|
3144
3360
|
if (specStatus !== "Approved") {
|
|
3145
3361
|
warnings.push(tr(lang, "warnings", "workflowSpecNotApproved"));
|
|
@@ -3162,6 +3378,13 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
3162
3378
|
}
|
|
3163
3379
|
}
|
|
3164
3380
|
}
|
|
3381
|
+
if (prePrReviewPolicy.enabled) {
|
|
3382
|
+
if (!prePrReviewFieldExists) {
|
|
3383
|
+
warnings.push(tr(lang, "warnings", "workflowPrePrReviewMissing"));
|
|
3384
|
+
} else if (prePrReviewStatus !== "Done") {
|
|
3385
|
+
warnings.push(tr(lang, "warnings", "workflowPrePrReviewNotDone"));
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3165
3388
|
}
|
|
3166
3389
|
const featureState = {
|
|
3167
3390
|
id,
|
|
@@ -3181,6 +3404,9 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
3181
3404
|
activeTask,
|
|
3182
3405
|
nextTodoTask,
|
|
3183
3406
|
completionChecklist,
|
|
3407
|
+
prePrReview: {
|
|
3408
|
+
status: prePrReviewStatus
|
|
3409
|
+
},
|
|
3184
3410
|
pr: { link: prLink, status: prStatus },
|
|
3185
3411
|
git: {
|
|
3186
3412
|
docsBranch: context.docsBranch,
|
|
@@ -3201,7 +3427,8 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
3201
3427
|
tasksExists,
|
|
3202
3428
|
tasksDocStatusFieldExists,
|
|
3203
3429
|
prFieldExists,
|
|
3204
|
-
prStatusFieldExists
|
|
3430
|
+
prStatusFieldExists,
|
|
3431
|
+
prePrReviewFieldExists
|
|
3205
3432
|
}
|
|
3206
3433
|
};
|
|
3207
3434
|
const { currentStep, actions, nextAction } = resolveFeatureProgress(
|
|
@@ -3335,13 +3562,13 @@ async function runStatus(options) {
|
|
|
3335
3562
|
);
|
|
3336
3563
|
}
|
|
3337
3564
|
const { docsDir, projectType, projectName, lang } = config;
|
|
3338
|
-
const featuresDir =
|
|
3565
|
+
const featuresDir = path6.join(docsDir, "features");
|
|
3339
3566
|
const scan = await scanFeatures(config);
|
|
3340
3567
|
const features = [];
|
|
3341
3568
|
const idMap = /* @__PURE__ */ new Map();
|
|
3342
3569
|
for (const f of scan.features) {
|
|
3343
3570
|
const id = f.id || "UNKNOWN";
|
|
3344
|
-
const relPath =
|
|
3571
|
+
const relPath = path6.relative(docsDir, f.path);
|
|
3345
3572
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
3346
3573
|
idMap.get(id).push(relPath);
|
|
3347
3574
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -3422,7 +3649,7 @@ async function runStatus(options) {
|
|
|
3422
3649
|
}
|
|
3423
3650
|
console.log();
|
|
3424
3651
|
if (options.write) {
|
|
3425
|
-
const outputPath =
|
|
3652
|
+
const outputPath = path6.join(featuresDir, "status.md");
|
|
3426
3653
|
const date = getLocalDateString();
|
|
3427
3654
|
const content = [
|
|
3428
3655
|
"# Feature Status",
|
|
@@ -3450,7 +3677,7 @@ function escapeRegExp2(value) {
|
|
|
3450
3677
|
}
|
|
3451
3678
|
async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
|
|
3452
3679
|
try {
|
|
3453
|
-
const specPath =
|
|
3680
|
+
const specPath = path6.join(featureDir, "spec.md");
|
|
3454
3681
|
if (!await fs2.pathExists(specPath)) return fallbackSlug;
|
|
3455
3682
|
const content = await fs2.readFile(specPath, "utf-8");
|
|
3456
3683
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
@@ -3468,7 +3695,7 @@ async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderNa
|
|
|
3468
3695
|
return fallbackSlug || fallbackFolderName;
|
|
3469
3696
|
}
|
|
3470
3697
|
function updateCommand(program2) {
|
|
3471
|
-
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--skills", "
|
|
3698
|
+
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--skills", "Cleanup legacy agents/skills copies (CLI-managed)").option("--templates", "Cleanup legacy feature-base copies (CLI-managed)").option(
|
|
3472
3699
|
"-f, --force",
|
|
3473
3700
|
"Force overwrite even if docs has uncommitted changes"
|
|
3474
3701
|
).action(async (options) => {
|
|
@@ -3507,7 +3734,6 @@ async function runUpdate(options) {
|
|
|
3507
3734
|
getDocsLockPath(docsDir),
|
|
3508
3735
|
async () => {
|
|
3509
3736
|
const templatesDir = getTemplatesDir();
|
|
3510
|
-
const sourceDir = path13.join(templatesDir, lang, toTemplateProjectType(projectType));
|
|
3511
3737
|
const docsLockPath = getDocsLockPath(docsDir);
|
|
3512
3738
|
const forceOverwrite = !!options.force || await isDocsWorktreeCleanOrThrow(docsDir, lang, [docsLockPath]);
|
|
3513
3739
|
const hasExplicitSelection = !!(options.agents || options.skills || options.templates);
|
|
@@ -3522,78 +3748,93 @@ async function runUpdate(options) {
|
|
|
3522
3748
|
console.log();
|
|
3523
3749
|
let updatedCount = 0;
|
|
3524
3750
|
if (updateAgents) {
|
|
3525
|
-
|
|
3526
|
-
chalk6.blue(
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
);
|
|
3530
|
-
const commonAgentsBase = path13.join(templatesDir, lang, "common", "agents");
|
|
3531
|
-
const typeAgentsBase = path13.join(
|
|
3532
|
-
templatesDir,
|
|
3533
|
-
lang,
|
|
3534
|
-
toTemplateProjectType(projectType),
|
|
3535
|
-
"agents"
|
|
3536
|
-
);
|
|
3537
|
-
const targetAgentsBase = path13.join(docsDir, "agents");
|
|
3538
|
-
const commonAgents = agentsMode === "skills" ? path13.join(commonAgentsBase, "skills") : commonAgentsBase;
|
|
3539
|
-
const typeAgents = agentsMode === "skills" ? path13.join(typeAgentsBase, "skills") : typeAgentsBase;
|
|
3540
|
-
const targetAgents = agentsMode === "skills" ? path13.join(targetAgentsBase, "skills") : targetAgentsBase;
|
|
3541
|
-
const featurePath = projectType === "multi" ? isDefaultFullstackComponents(config.components || []) ? "docs/features/{be|fe}" : "docs/features/{component}" : "docs/features";
|
|
3542
|
-
const projectName = config.projectName ?? "{{projectName}}";
|
|
3543
|
-
const commonReplacements = {
|
|
3544
|
-
"{{projectName}}": projectName,
|
|
3545
|
-
"{{featurePath}}": featurePath
|
|
3546
|
-
};
|
|
3547
|
-
const typeReplacements = {
|
|
3548
|
-
"{{projectName}}": projectName
|
|
3549
|
-
};
|
|
3550
|
-
if (await fs2.pathExists(commonAgents)) {
|
|
3551
|
-
const count = await updateFolder(
|
|
3552
|
-
commonAgents,
|
|
3553
|
-
targetAgents,
|
|
3554
|
-
forceOverwrite,
|
|
3555
|
-
commonReplacements,
|
|
3556
|
-
lang
|
|
3751
|
+
if (agentsMode === "skills") {
|
|
3752
|
+
console.log(chalk6.blue(tr(lang, "cli", "update.updatingSkills")));
|
|
3753
|
+
console.log(
|
|
3754
|
+
chalk6.gray(tr(lang, "cli", "update.engineManagedSkillsBuiltin"))
|
|
3557
3755
|
);
|
|
3558
|
-
|
|
3756
|
+
console.log(chalk6.green(` \u2705 ${tr(lang, "cli", "update.skillsUpdated")}`));
|
|
3757
|
+
} else {
|
|
3758
|
+
console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
3559
3759
|
}
|
|
3560
|
-
if (
|
|
3561
|
-
const
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3760
|
+
if (agentsMode === "all") {
|
|
3761
|
+
const commonAgentsBase = path6.join(templatesDir, lang, "common", "agents");
|
|
3762
|
+
const typeAgentsBase = path6.join(
|
|
3763
|
+
templatesDir,
|
|
3764
|
+
lang,
|
|
3765
|
+
toTemplateProjectType(projectType),
|
|
3766
|
+
"agents"
|
|
3567
3767
|
);
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
console.log(chalk6.blue(tr(lang, "cli", "update.updatingFeatureBase")));
|
|
3578
|
-
const sourceFeatureBase = path13.join(sourceDir, "features", "feature-base");
|
|
3579
|
-
const targetFeatureBase = path13.join(docsDir, "features", "feature-base");
|
|
3580
|
-
if (await fs2.pathExists(sourceFeatureBase)) {
|
|
3581
|
-
const replacements = {
|
|
3582
|
-
"{{projectName}}": config.projectName ?? "{{projectName}}"
|
|
3768
|
+
const targetAgentsBase = path6.join(docsDir, "agents");
|
|
3769
|
+
const commonAgents = agentsMode === "skills" ? path6.join(commonAgentsBase, "skills") : commonAgentsBase;
|
|
3770
|
+
const typeAgents = agentsMode === "skills" ? path6.join(typeAgentsBase, "skills") : typeAgentsBase;
|
|
3771
|
+
const targetAgents = agentsMode === "skills" ? path6.join(targetAgentsBase, "skills") : targetAgentsBase;
|
|
3772
|
+
const featurePath = projectType === "multi" ? isDefaultFullstackComponents(config.components || []) ? "docs/features/{be|fe}" : "docs/features/{component}" : "docs/features";
|
|
3773
|
+
const projectName = config.projectName ?? "{{projectName}}";
|
|
3774
|
+
const commonReplacements = {
|
|
3775
|
+
"{{projectName}}": projectName,
|
|
3776
|
+
"{{featurePath}}": featurePath
|
|
3583
3777
|
};
|
|
3584
|
-
const
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3778
|
+
const typeReplacements = {
|
|
3779
|
+
"{{projectName}}": projectName
|
|
3780
|
+
};
|
|
3781
|
+
if (await fs2.pathExists(commonAgents)) {
|
|
3782
|
+
const count = await updateFolder(
|
|
3783
|
+
commonAgents,
|
|
3784
|
+
targetAgents,
|
|
3785
|
+
forceOverwrite,
|
|
3786
|
+
commonReplacements,
|
|
3787
|
+
lang,
|
|
3788
|
+
{
|
|
3789
|
+
protectedFiles: /* @__PURE__ */ new Set([
|
|
3790
|
+
"custom.md",
|
|
3791
|
+
"constitution.md",
|
|
3792
|
+
...ENGINE_MANAGED_AGENT_FILES
|
|
3793
|
+
]),
|
|
3794
|
+
skipDirectories: new Set(ENGINE_MANAGED_AGENT_DIRS)
|
|
3795
|
+
}
|
|
3796
|
+
);
|
|
3797
|
+
updatedCount += count;
|
|
3798
|
+
}
|
|
3799
|
+
if (await fs2.pathExists(typeAgents)) {
|
|
3800
|
+
const count = await updateFolder(
|
|
3801
|
+
typeAgents,
|
|
3802
|
+
targetAgents,
|
|
3803
|
+
forceOverwrite,
|
|
3804
|
+
typeReplacements,
|
|
3805
|
+
lang,
|
|
3806
|
+
{
|
|
3807
|
+
protectedFiles: /* @__PURE__ */ new Set([
|
|
3808
|
+
"custom.md",
|
|
3809
|
+
"constitution.md",
|
|
3810
|
+
...ENGINE_MANAGED_AGENT_FILES
|
|
3811
|
+
]),
|
|
3812
|
+
skipDirectories: new Set(ENGINE_MANAGED_AGENT_DIRS)
|
|
3813
|
+
}
|
|
3814
|
+
);
|
|
3815
|
+
updatedCount += count;
|
|
3816
|
+
}
|
|
3592
3817
|
console.log(
|
|
3593
|
-
chalk6.green(
|
|
3818
|
+
chalk6.green(
|
|
3819
|
+
` \u2705 ${agentsMode === "skills" ? tr(lang, "cli", "update.skillsUpdated") : tr(lang, "cli", "update.agentsUpdated")}`
|
|
3820
|
+
)
|
|
3594
3821
|
);
|
|
3595
3822
|
}
|
|
3596
3823
|
}
|
|
3824
|
+
if (updateTemplates) {
|
|
3825
|
+
console.log(chalk6.blue(tr(lang, "cli", "update.updatingFeatureBase")));
|
|
3826
|
+
console.log(chalk6.gray(tr(lang, "cli", "update.engineManagedFeatureBaseBuiltin")));
|
|
3827
|
+
}
|
|
3828
|
+
const pruned = await pruneEngineManagedDocs(docsDir);
|
|
3829
|
+
if (pruned.length > 0) {
|
|
3830
|
+
console.log(
|
|
3831
|
+
chalk6.gray(
|
|
3832
|
+
` - ${tr(lang, "cli", "update.engineManagedPruned", {
|
|
3833
|
+
count: pruned.length
|
|
3834
|
+
})}`
|
|
3835
|
+
)
|
|
3836
|
+
);
|
|
3837
|
+
}
|
|
3597
3838
|
console.log();
|
|
3598
3839
|
console.log(
|
|
3599
3840
|
chalk6.green(`\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`)
|
|
@@ -3602,14 +3843,15 @@ async function runUpdate(options) {
|
|
|
3602
3843
|
{ owner: "update" }
|
|
3603
3844
|
);
|
|
3604
3845
|
}
|
|
3605
|
-
async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG) {
|
|
3606
|
-
const protectedFiles = /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
|
|
3846
|
+
async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG, options = {}) {
|
|
3847
|
+
const protectedFiles = options.protectedFiles ?? /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
|
|
3848
|
+
const skipDirectories = options.skipDirectories ?? /* @__PURE__ */ new Set();
|
|
3607
3849
|
await fs2.ensureDir(targetDir);
|
|
3608
3850
|
const files = await fs2.readdir(sourceDir);
|
|
3609
3851
|
let updatedCount = 0;
|
|
3610
3852
|
for (const file of files) {
|
|
3611
|
-
const sourcePath =
|
|
3612
|
-
const targetPath =
|
|
3853
|
+
const sourcePath = path6.join(sourceDir, file);
|
|
3854
|
+
const targetPath = path6.join(targetDir, file);
|
|
3613
3855
|
const stat = await fs2.stat(sourcePath);
|
|
3614
3856
|
if (stat.isFile()) {
|
|
3615
3857
|
if (protectedFiles.has(file)) {
|
|
@@ -3642,12 +3884,16 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
3642
3884
|
updatedCount++;
|
|
3643
3885
|
}
|
|
3644
3886
|
} else if (stat.isDirectory()) {
|
|
3887
|
+
if (skipDirectories.has(file)) {
|
|
3888
|
+
continue;
|
|
3889
|
+
}
|
|
3645
3890
|
const subCount = await updateFolder(
|
|
3646
3891
|
sourcePath,
|
|
3647
3892
|
targetPath,
|
|
3648
3893
|
force,
|
|
3649
3894
|
replacements,
|
|
3650
|
-
lang
|
|
3895
|
+
lang,
|
|
3896
|
+
options
|
|
3651
3897
|
);
|
|
3652
3898
|
updatedCount += subCount;
|
|
3653
3899
|
}
|
|
@@ -3688,7 +3934,7 @@ function extractPorcelainPaths(line) {
|
|
|
3688
3934
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
3689
3935
|
const top = getGitTopLevel2(docsDir);
|
|
3690
3936
|
if (!top) return null;
|
|
3691
|
-
const rel =
|
|
3937
|
+
const rel = path6.relative(top, docsDir) || ".";
|
|
3692
3938
|
try {
|
|
3693
3939
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
3694
3940
|
cwd: top,
|
|
@@ -3700,7 +3946,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
3700
3946
|
}
|
|
3701
3947
|
const ignoredRelPaths = new Set(
|
|
3702
3948
|
ignoredAbsPaths.map(
|
|
3703
|
-
(absPath) => normalizeGitPath2(
|
|
3949
|
+
(absPath) => normalizeGitPath2(path6.relative(top, absPath) || ".")
|
|
3704
3950
|
)
|
|
3705
3951
|
);
|
|
3706
3952
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -3757,7 +4003,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
3757
4003
|
}
|
|
3758
4004
|
async function runConfig(options) {
|
|
3759
4005
|
const cwd = process.cwd();
|
|
3760
|
-
const targetCwd = options.dir ?
|
|
4006
|
+
const targetCwd = options.dir ? path6.resolve(cwd, options.dir) : cwd;
|
|
3761
4007
|
const config = await getConfig(targetCwd);
|
|
3762
4008
|
if (!config) {
|
|
3763
4009
|
throw createCliError(
|
|
@@ -3765,7 +4011,7 @@ async function runConfig(options) {
|
|
|
3765
4011
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
3766
4012
|
);
|
|
3767
4013
|
}
|
|
3768
|
-
const configPath =
|
|
4014
|
+
const configPath = path6.join(config.docsDir, ".lee-spec-kit.json");
|
|
3769
4015
|
if (!options.projectRoot) {
|
|
3770
4016
|
console.log();
|
|
3771
4017
|
console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
@@ -3869,6 +4115,22 @@ async function runConfig(options) {
|
|
|
3869
4115
|
{ owner: "config" }
|
|
3870
4116
|
);
|
|
3871
4117
|
}
|
|
4118
|
+
var REMOTE_ACTION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
4119
|
+
"issue_create",
|
|
4120
|
+
"pr_create",
|
|
4121
|
+
"pr_status_update",
|
|
4122
|
+
"code_review"
|
|
4123
|
+
]);
|
|
4124
|
+
var LOCAL_ACTION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
4125
|
+
"docs_commit",
|
|
4126
|
+
"branch_create",
|
|
4127
|
+
"task_execute"
|
|
4128
|
+
]);
|
|
4129
|
+
var REMOTE_COMMAND_PATTERN = /\b(?:git\s+push|git\s+merge|gh\s+(?:issue|pr)\b)/i;
|
|
4130
|
+
function resolveComponentOption(options) {
|
|
4131
|
+
const component = (options.component || options.repo || "").trim().toLowerCase();
|
|
4132
|
+
return component || void 0;
|
|
4133
|
+
}
|
|
3872
4134
|
function getActionLabel(index) {
|
|
3873
4135
|
let n = index + 1;
|
|
3874
4136
|
let label = "";
|
|
@@ -3879,11 +4141,35 @@ function getActionLabel(index) {
|
|
|
3879
4141
|
}
|
|
3880
4142
|
return label;
|
|
3881
4143
|
}
|
|
4144
|
+
function resolveActionOperationType(action) {
|
|
4145
|
+
if (action.operationType) return action.operationType;
|
|
4146
|
+
if (action.type === "command") {
|
|
4147
|
+
if (REMOTE_COMMAND_PATTERN.test(action.cmd)) return "remote";
|
|
4148
|
+
return "local";
|
|
4149
|
+
}
|
|
4150
|
+
if (action.category && REMOTE_ACTION_CATEGORIES.has(action.category)) {
|
|
4151
|
+
return "remote";
|
|
4152
|
+
}
|
|
4153
|
+
if (action.category && LOCAL_ACTION_CATEGORIES.has(action.category)) {
|
|
4154
|
+
return "local";
|
|
4155
|
+
}
|
|
4156
|
+
return "manual";
|
|
4157
|
+
}
|
|
4158
|
+
function annotateActionOperationType(action) {
|
|
4159
|
+
return {
|
|
4160
|
+
...action,
|
|
4161
|
+
operationType: resolveActionOperationType(action)
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
4164
|
+
function annotateActions(actions) {
|
|
4165
|
+
return actions.map((action) => annotateActionOperationType(action));
|
|
4166
|
+
}
|
|
3882
4167
|
function getActionSummary(action) {
|
|
3883
4168
|
if (action.category === "docs_commit") return "Commit docs updates";
|
|
3884
4169
|
if (action.category === "issue_create") return "Create and record issue";
|
|
3885
4170
|
if (action.category === "branch_create") return "Create feature branch";
|
|
3886
4171
|
if (action.category === "pr_create") return "Create PR and record link";
|
|
4172
|
+
if (action.category === "pre_pr_review") return "Run pre-PR self review";
|
|
3887
4173
|
if (action.category === "pr_status_update") return "Update PR status";
|
|
3888
4174
|
if (action.category === "code_review") return "Process code review feedback";
|
|
3889
4175
|
if (action.category === "task_execute") return "Proceed with task execution";
|
|
@@ -3898,6 +4184,12 @@ function getActionSummary(action) {
|
|
|
3898
4184
|
}
|
|
3899
4185
|
return action.message;
|
|
3900
4186
|
}
|
|
4187
|
+
function formatActionSummary(action) {
|
|
4188
|
+
if (action.type === "command") {
|
|
4189
|
+
return `(${action.scope}) ${action.cmd}`;
|
|
4190
|
+
}
|
|
4191
|
+
return action.message;
|
|
4192
|
+
}
|
|
3901
4193
|
function toActionOptions(actions) {
|
|
3902
4194
|
return actions.map((action, index) => {
|
|
3903
4195
|
const label = getActionLabel(index);
|
|
@@ -3922,6 +4214,7 @@ function buildActionSnapshot(actionOptions) {
|
|
|
3922
4214
|
cwd: action.cwd,
|
|
3923
4215
|
cmd: action.cmd,
|
|
3924
4216
|
category: action.category,
|
|
4217
|
+
operationType: action.operationType,
|
|
3925
4218
|
requiresUserCheck: !!action.requiresUserCheck
|
|
3926
4219
|
};
|
|
3927
4220
|
}
|
|
@@ -3930,6 +4223,7 @@ function buildActionSnapshot(actionOptions) {
|
|
|
3930
4223
|
type: action.type,
|
|
3931
4224
|
message: action.message,
|
|
3932
4225
|
category: action.category,
|
|
4226
|
+
operationType: action.operationType,
|
|
3933
4227
|
requiresUserCheck: !!action.requiresUserCheck
|
|
3934
4228
|
};
|
|
3935
4229
|
});
|
|
@@ -3944,20 +4238,21 @@ function getContextVersion(feature, actionOptions) {
|
|
|
3944
4238
|
});
|
|
3945
4239
|
return createHash("sha256").update(payload).digest("hex").slice(0, 12);
|
|
3946
4240
|
}
|
|
3947
|
-
function
|
|
3948
|
-
const
|
|
3949
|
-
if (!
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
return actionOptions.map((o) => o.label).join(", ");
|
|
4241
|
+
function matchesFeatureSelector(f, selector) {
|
|
4242
|
+
const s = selector.trim();
|
|
4243
|
+
if (!s) return false;
|
|
4244
|
+
if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
|
|
4245
|
+
if (f.slug.toLowerCase() === s.toLowerCase()) return true;
|
|
4246
|
+
if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
|
|
4247
|
+
return false;
|
|
3955
4248
|
}
|
|
3956
|
-
function
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
return
|
|
4249
|
+
function detectFromBranch(branchName, features) {
|
|
4250
|
+
const match = branchName.match(/^feat\/\d+-(.+)$/);
|
|
4251
|
+
if (!match) return [];
|
|
4252
|
+
const detected = match[1];
|
|
4253
|
+
return features.filter(
|
|
4254
|
+
(f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
|
|
4255
|
+
);
|
|
3961
4256
|
}
|
|
3962
4257
|
function toSelectionStatus(features, selectionMode, openFeatures, targetFeatures) {
|
|
3963
4258
|
const isNoOpen = selectionMode === "open" && features.length > 0 && openFeatures.length === 0;
|
|
@@ -3974,15 +4269,9 @@ function toReasonCode(status) {
|
|
|
3974
4269
|
if (status === "multiple_active") return "MULTIPLE_ACTIVE_FEATURES";
|
|
3975
4270
|
return "NO_MATCHED_FEATURES";
|
|
3976
4271
|
}
|
|
3977
|
-
async function
|
|
3978
|
-
if (!config) {
|
|
3979
|
-
throw createCliError(
|
|
3980
|
-
"CONFIG_NOT_FOUND",
|
|
3981
|
-
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
3982
|
-
);
|
|
3983
|
-
}
|
|
4272
|
+
async function resolveContextSelection(config, featureName, options) {
|
|
3984
4273
|
const { features, branches, warnings } = await scanFeatures(config);
|
|
3985
|
-
const selectedComponent = (options
|
|
4274
|
+
const selectedComponent = resolveComponentOption(options);
|
|
3986
4275
|
const scopedFeatures = selectedComponent ? features.filter((f) => f.type === selectedComponent) : features;
|
|
3987
4276
|
const doneFeatures = scopedFeatures.filter((f) => f.completion.workflowDone);
|
|
3988
4277
|
const openFeatures = scopedFeatures.filter((f) => !f.completion.workflowDone);
|
|
@@ -3994,6 +4283,7 @@ async function resolveContextState(config, featureName, options) {
|
|
|
3994
4283
|
);
|
|
3995
4284
|
let targetFeatures = [];
|
|
3996
4285
|
let selectionMode = "explicit";
|
|
4286
|
+
let selectionFallback = "none";
|
|
3997
4287
|
if (featureName) {
|
|
3998
4288
|
targetFeatures = scopedFeatures.filter(
|
|
3999
4289
|
(f) => matchesFeatureSelector(f, featureName)
|
|
@@ -4026,15 +4316,19 @@ async function resolveContextState(config, featureName, options) {
|
|
|
4026
4316
|
}
|
|
4027
4317
|
if (targetFeatures.length > 0) {
|
|
4028
4318
|
selectionMode = "branch";
|
|
4319
|
+
selectionFallback = "none";
|
|
4029
4320
|
} else if (options.all) {
|
|
4030
4321
|
targetFeatures = scopedFeatures;
|
|
4031
4322
|
selectionMode = "all";
|
|
4323
|
+
selectionFallback = "all_features";
|
|
4032
4324
|
} else if (options.done) {
|
|
4033
4325
|
targetFeatures = doneFeatures;
|
|
4034
4326
|
selectionMode = "done";
|
|
4327
|
+
selectionFallback = "done_features";
|
|
4035
4328
|
} else {
|
|
4036
4329
|
targetFeatures = openFeatures;
|
|
4037
4330
|
selectionMode = "open";
|
|
4331
|
+
selectionFallback = "open_features";
|
|
4038
4332
|
}
|
|
4039
4333
|
}
|
|
4040
4334
|
const status = toSelectionStatus(
|
|
@@ -4044,7 +4338,7 @@ async function resolveContextState(config, featureName, options) {
|
|
|
4044
4338
|
targetFeatures
|
|
4045
4339
|
);
|
|
4046
4340
|
const matchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
|
|
4047
|
-
const actions = matchedFeature?.actions ?? [];
|
|
4341
|
+
const actions = annotateActions(matchedFeature?.actions ?? []);
|
|
4048
4342
|
const actionOptions = toActionOptions(actions);
|
|
4049
4343
|
const contextVersion = getContextVersion(matchedFeature, actionOptions);
|
|
4050
4344
|
return {
|
|
@@ -4056,6 +4350,7 @@ async function resolveContextState(config, featureName, options) {
|
|
|
4056
4350
|
inProgressFeatures,
|
|
4057
4351
|
readyToCloseFeatures,
|
|
4058
4352
|
selectionMode,
|
|
4353
|
+
selectionFallback,
|
|
4059
4354
|
targetFeatures,
|
|
4060
4355
|
status,
|
|
4061
4356
|
matchedFeature,
|
|
@@ -4064,6 +4359,32 @@ async function resolveContextState(config, featureName, options) {
|
|
|
4064
4359
|
contextVersion
|
|
4065
4360
|
};
|
|
4066
4361
|
}
|
|
4362
|
+
|
|
4363
|
+
// src/commands/context.ts
|
|
4364
|
+
async function resolveContextState(config, featureName, options) {
|
|
4365
|
+
if (!config) {
|
|
4366
|
+
throw createCliError(
|
|
4367
|
+
"CONFIG_NOT_FOUND",
|
|
4368
|
+
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
4369
|
+
);
|
|
4370
|
+
}
|
|
4371
|
+
return resolveContextSelection(config, featureName, options);
|
|
4372
|
+
}
|
|
4373
|
+
function parseApprovalLabel(input) {
|
|
4374
|
+
const match = input.trim().match(/^([A-Z]+)(?:\s+OK)?$/i);
|
|
4375
|
+
if (!match) return null;
|
|
4376
|
+
return match[1].toUpperCase();
|
|
4377
|
+
}
|
|
4378
|
+
function listLabels(actionOptions) {
|
|
4379
|
+
if (actionOptions.length === 0) return "-";
|
|
4380
|
+
return actionOptions.map((o) => o.label).join(", ");
|
|
4381
|
+
}
|
|
4382
|
+
function formatActionSummary2(action) {
|
|
4383
|
+
if (action.type === "command") {
|
|
4384
|
+
return `(${action.scope}) ${action.cmd}`;
|
|
4385
|
+
}
|
|
4386
|
+
return action.message;
|
|
4387
|
+
}
|
|
4067
4388
|
function executeCommandAction(cmd, jsonMode, cwd) {
|
|
4068
4389
|
const shellPath = process.env.SHELL || (process.platform === "win32" ? process.env.ComSpec || "cmd.exe" : "/bin/sh");
|
|
4069
4390
|
if (jsonMode) {
|
|
@@ -4086,7 +4407,7 @@ function getCommandExecutionLockPath(action, config) {
|
|
|
4086
4407
|
if (action.scope === "docs") {
|
|
4087
4408
|
return getDocsLockPath(config.docsDir);
|
|
4088
4409
|
}
|
|
4089
|
-
return
|
|
4410
|
+
return path6.join(action.cwd, ".lee-spec-kit.project.lock");
|
|
4090
4411
|
}
|
|
4091
4412
|
function contextCommand(program2) {
|
|
4092
4413
|
program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option("--repo <repo>", "Component name for multi projects").option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option("--approve <reply>", "Approve one labeled option: A or A OK").option("--execute", "Execute approved option when it is a command").option(
|
|
@@ -4122,23 +4443,7 @@ function contextCommand(program2) {
|
|
|
4122
4443
|
}
|
|
4123
4444
|
);
|
|
4124
4445
|
}
|
|
4125
|
-
function
|
|
4126
|
-
const s = selector.trim();
|
|
4127
|
-
if (!s) return false;
|
|
4128
|
-
if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
|
|
4129
|
-
if (f.slug.toLowerCase() === s.toLowerCase()) return true;
|
|
4130
|
-
if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
|
|
4131
|
-
return false;
|
|
4132
|
-
}
|
|
4133
|
-
function detectFromBranch(branchName, features) {
|
|
4134
|
-
const match = branchName.match(/^feat\/\d+-(.+)$/);
|
|
4135
|
-
if (!match) return [];
|
|
4136
|
-
const detected = match[1];
|
|
4137
|
-
return features.filter(
|
|
4138
|
-
(f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
|
|
4139
|
-
);
|
|
4140
|
-
}
|
|
4141
|
-
function getListLabel(f, stepsMap, lang, workflowPolicy) {
|
|
4446
|
+
function getListLabel(f, stepsMap, lang, workflowPolicy, prePrReviewPolicy) {
|
|
4142
4447
|
if (f.completion.implementationDone && !f.completion.workflowDone) {
|
|
4143
4448
|
if (f.git.docsHasUncommittedChanges) {
|
|
4144
4449
|
return tr(lang, "cli", "context.list.docsCommitNeeded");
|
|
@@ -4152,6 +4457,12 @@ function getListLabel(f, stepsMap, lang, workflowPolicy) {
|
|
|
4152
4457
|
if (workflowPolicy.requirePr && (!f.docs.prFieldExists || !f.docs.prStatusFieldExists)) {
|
|
4153
4458
|
return tr(lang, "cli", "context.list.addPrMetadata");
|
|
4154
4459
|
}
|
|
4460
|
+
if (prePrReviewPolicy.enabled && !f.docs.prePrReviewFieldExists) {
|
|
4461
|
+
return tr(lang, "cli", "context.list.addPrePrReviewField");
|
|
4462
|
+
}
|
|
4463
|
+
if (prePrReviewPolicy.enabled && f.prePrReview.status !== "Done") {
|
|
4464
|
+
return tr(lang, "cli", "context.list.completePrePrReview");
|
|
4465
|
+
}
|
|
4155
4466
|
if (workflowPolicy.requirePr && !f.pr.link) {
|
|
4156
4467
|
return tr(lang, "cli", "context.list.recordPrLink");
|
|
4157
4468
|
}
|
|
@@ -4186,6 +4497,7 @@ async function runContext(featureName, options) {
|
|
|
4186
4497
|
const config = await getConfig(cwd);
|
|
4187
4498
|
const lang = config?.lang ?? "en";
|
|
4188
4499
|
const workflowPolicy = resolveWorkflowPolicy(config?.workflow);
|
|
4500
|
+
const prePrReviewPolicy = resolvePrePrReviewPolicy(config?.workflow);
|
|
4189
4501
|
if (!config) {
|
|
4190
4502
|
throw createCliError(
|
|
4191
4503
|
"CONFIG_NOT_FOUND",
|
|
@@ -4231,10 +4543,12 @@ async function runContext(featureName, options) {
|
|
|
4231
4543
|
return;
|
|
4232
4544
|
}
|
|
4233
4545
|
if (options.json) {
|
|
4546
|
+
const primaryAction = state.actionOptions[0] ?? null;
|
|
4234
4547
|
const result = {
|
|
4235
4548
|
status: state.status,
|
|
4236
4549
|
reasonCode: toReasonCode(state.status),
|
|
4237
4550
|
selectionMode: state.selectionMode,
|
|
4551
|
+
selectionFallback: state.selectionFallback,
|
|
4238
4552
|
branches: state.branches,
|
|
4239
4553
|
warnings: state.warnings,
|
|
4240
4554
|
matchedFeature: state.matchedFeature,
|
|
@@ -4246,10 +4560,16 @@ async function runContext(featureName, options) {
|
|
|
4246
4560
|
readyToCloseCandidates: state.selectionMode === "open" ? state.readyToCloseFeatures : [],
|
|
4247
4561
|
actions: state.actions,
|
|
4248
4562
|
actionOptions: state.actionOptions,
|
|
4563
|
+
primaryActionLabel: primaryAction?.label ?? null,
|
|
4564
|
+
primaryActionType: primaryAction?.action.type ?? null,
|
|
4565
|
+
primaryActionCategory: primaryAction?.action.category ?? null,
|
|
4566
|
+
primaryActionOperationType: primaryAction?.action.operationType ?? null,
|
|
4249
4567
|
workflowPolicy,
|
|
4568
|
+
prePrReviewPolicy,
|
|
4250
4569
|
checkPolicy: {
|
|
4251
|
-
docPath: "
|
|
4570
|
+
docPath: "builtin://agents/policy",
|
|
4252
4571
|
hint: tr(lang, "cli", "context.checkPolicyHint"),
|
|
4572
|
+
policyOnly: true,
|
|
4253
4573
|
token: "<LABEL>",
|
|
4254
4574
|
acceptedTokens: ["<LABEL>", "<LABEL> OK"],
|
|
4255
4575
|
tokenPattern: "^([A-Z]+)(?:\\s+OK)?$",
|
|
@@ -4268,7 +4588,8 @@ async function runContext(featureName, options) {
|
|
|
4268
4588
|
label: o.label,
|
|
4269
4589
|
summary: o.summary,
|
|
4270
4590
|
approvalPrompt: o.approvalPrompt,
|
|
4271
|
-
requiresUserCheck: !!o.action.requiresUserCheck
|
|
4591
|
+
requiresUserCheck: !!o.action.requiresUserCheck,
|
|
4592
|
+
operationType: o.action.operationType
|
|
4272
4593
|
}))
|
|
4273
4594
|
},
|
|
4274
4595
|
prPolicy: {
|
|
@@ -4378,7 +4699,13 @@ async function runContext(featureName, options) {
|
|
|
4378
4699
|
)
|
|
4379
4700
|
);
|
|
4380
4701
|
state.inProgressFeatures.forEach((f2) => {
|
|
4381
|
-
const stepName2 = getListLabel(
|
|
4702
|
+
const stepName2 = getListLabel(
|
|
4703
|
+
f2,
|
|
4704
|
+
stepsMap,
|
|
4705
|
+
lang,
|
|
4706
|
+
workflowPolicy,
|
|
4707
|
+
prePrReviewPolicy
|
|
4708
|
+
);
|
|
4382
4709
|
const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
4383
4710
|
console.log(
|
|
4384
4711
|
` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
|
|
@@ -4391,7 +4718,13 @@ async function runContext(featureName, options) {
|
|
|
4391
4718
|
)
|
|
4392
4719
|
);
|
|
4393
4720
|
state.readyToCloseFeatures.forEach((f2) => {
|
|
4394
|
-
const stepName2 = getListLabel(
|
|
4721
|
+
const stepName2 = getListLabel(
|
|
4722
|
+
f2,
|
|
4723
|
+
stepsMap,
|
|
4724
|
+
lang,
|
|
4725
|
+
workflowPolicy,
|
|
4726
|
+
prePrReviewPolicy
|
|
4727
|
+
);
|
|
4395
4728
|
const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
4396
4729
|
console.log(
|
|
4397
4730
|
` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
|
|
@@ -4402,7 +4735,13 @@ async function runContext(featureName, options) {
|
|
|
4402
4735
|
console.log(chalk6.blue(title));
|
|
4403
4736
|
console.log();
|
|
4404
4737
|
state.targetFeatures.forEach((f2) => {
|
|
4405
|
-
const stepName2 = getListLabel(
|
|
4738
|
+
const stepName2 = getListLabel(
|
|
4739
|
+
f2,
|
|
4740
|
+
stepsMap,
|
|
4741
|
+
lang,
|
|
4742
|
+
workflowPolicy,
|
|
4743
|
+
prePrReviewPolicy
|
|
4744
|
+
);
|
|
4406
4745
|
const typeStr = config.projectType === "multi" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
4407
4746
|
console.log(
|
|
4408
4747
|
` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
|
|
@@ -4443,7 +4782,7 @@ async function runContext(featureName, options) {
|
|
|
4443
4782
|
if (f.issueNumber) {
|
|
4444
4783
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
4445
4784
|
}
|
|
4446
|
-
console.log(` \u2022 Path: ${
|
|
4785
|
+
console.log(` \u2022 Path: ${path6.relative(cwd, f.path)}`);
|
|
4447
4786
|
if (f.git.projectBranch) {
|
|
4448
4787
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
4449
4788
|
}
|
|
@@ -4476,7 +4815,7 @@ async function runContext(featureName, options) {
|
|
|
4476
4815
|
console.log();
|
|
4477
4816
|
return;
|
|
4478
4817
|
}
|
|
4479
|
-
const actionOptions =
|
|
4818
|
+
const actionOptions = state.actionOptions;
|
|
4480
4819
|
console.log(chalk6.green(chalk6.bold("\u{1F449} Next Options (Atomic):")));
|
|
4481
4820
|
let hasDocsCommand = false;
|
|
4482
4821
|
actionOptions.forEach(({ label, action }) => {
|
|
@@ -4562,7 +4901,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
4562
4901
|
}
|
|
4563
4902
|
console.log();
|
|
4564
4903
|
console.log(chalk6.green(`\u2705 Approved option: ${parsedLabel}`));
|
|
4565
|
-
console.log(chalk6.gray(` - Action: ${
|
|
4904
|
+
console.log(chalk6.gray(` - Action: ${formatActionSummary2(selectedAction)}`));
|
|
4566
4905
|
if (selectedAction.type === "command") {
|
|
4567
4906
|
console.log(chalk6.gray(" - Run with: --execute"));
|
|
4568
4907
|
} else {
|
|
@@ -4669,7 +5008,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
4669
5008
|
]);
|
|
4670
5009
|
function formatPath(cwd, p) {
|
|
4671
5010
|
if (!p) return "";
|
|
4672
|
-
return
|
|
5011
|
+
return path6.isAbsolute(p) ? path6.relative(cwd, p) : p;
|
|
4673
5012
|
}
|
|
4674
5013
|
function detectPlaceholders(content) {
|
|
4675
5014
|
const patterns = [
|
|
@@ -4812,7 +5151,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
4812
5151
|
const placeholderContext = {
|
|
4813
5152
|
projectName: config.projectName,
|
|
4814
5153
|
featureName: f.slug,
|
|
4815
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
5154
|
+
featurePath: f.docs.featurePathFromDocs || path6.relative(config.docsDir, f.path),
|
|
4816
5155
|
repoType: f.type,
|
|
4817
5156
|
featureNumber
|
|
4818
5157
|
};
|
|
@@ -4822,7 +5161,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
4822
5161
|
"tasks.md"
|
|
4823
5162
|
];
|
|
4824
5163
|
for (const file of files) {
|
|
4825
|
-
const fullPath =
|
|
5164
|
+
const fullPath = path6.join(f.path, file);
|
|
4826
5165
|
if (!await fs2.pathExists(fullPath)) continue;
|
|
4827
5166
|
const original = await fs2.readFile(fullPath, "utf-8");
|
|
4828
5167
|
let next = original;
|
|
@@ -4867,7 +5206,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
4867
5206
|
const issues = [];
|
|
4868
5207
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
4869
5208
|
for (const dir of requiredDirs) {
|
|
4870
|
-
const p =
|
|
5209
|
+
const p = path6.join(config.docsDir, dir);
|
|
4871
5210
|
if (!await fs2.pathExists(p)) {
|
|
4872
5211
|
issues.push({
|
|
4873
5212
|
level: "error",
|
|
@@ -4877,7 +5216,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
4877
5216
|
});
|
|
4878
5217
|
}
|
|
4879
5218
|
}
|
|
4880
|
-
const configPath =
|
|
5219
|
+
const configPath = path6.join(config.docsDir, ".lee-spec-kit.json");
|
|
4881
5220
|
if (!await fs2.pathExists(configPath)) {
|
|
4882
5221
|
issues.push({
|
|
4883
5222
|
level: "warn",
|
|
@@ -4900,7 +5239,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
4900
5239
|
}
|
|
4901
5240
|
const idMap = /* @__PURE__ */ new Map();
|
|
4902
5241
|
for (const f of features) {
|
|
4903
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
5242
|
+
const rel = f.docs.featurePathFromDocs || path6.relative(config.docsDir, f.path);
|
|
4904
5243
|
const id = f.id || "UNKNOWN";
|
|
4905
5244
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
4906
5245
|
idMap.get(id).push(rel);
|
|
@@ -4908,7 +5247,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
4908
5247
|
if (!isInitialTemplateState) {
|
|
4909
5248
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
4910
5249
|
for (const file of featureDocs) {
|
|
4911
|
-
const p =
|
|
5250
|
+
const p = path6.join(f.path, file);
|
|
4912
5251
|
if (!await fs2.pathExists(p)) continue;
|
|
4913
5252
|
const content = await fs2.readFile(p, "utf-8");
|
|
4914
5253
|
const placeholders = detectPlaceholders(content);
|
|
@@ -4923,7 +5262,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
4923
5262
|
});
|
|
4924
5263
|
}
|
|
4925
5264
|
if (decisionsPlaceholderMode !== "off") {
|
|
4926
|
-
const decisionsPath =
|
|
5265
|
+
const decisionsPath = path6.join(f.path, "decisions.md");
|
|
4927
5266
|
if (await fs2.pathExists(decisionsPath)) {
|
|
4928
5267
|
const content = await fs2.readFile(decisionsPath, "utf-8");
|
|
4929
5268
|
const placeholders = detectPlaceholders(content);
|
|
@@ -4952,7 +5291,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
4952
5291
|
level: "warn",
|
|
4953
5292
|
code: "spec_status_unset",
|
|
4954
5293
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
4955
|
-
path: formatPath(cwd,
|
|
5294
|
+
path: formatPath(cwd, path6.join(f.path, "spec.md"))
|
|
4956
5295
|
});
|
|
4957
5296
|
}
|
|
4958
5297
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -4960,7 +5299,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
4960
5299
|
level: "warn",
|
|
4961
5300
|
code: "plan_status_unset",
|
|
4962
5301
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
4963
|
-
path: formatPath(cwd,
|
|
5302
|
+
path: formatPath(cwd, path6.join(f.path, "plan.md"))
|
|
4964
5303
|
});
|
|
4965
5304
|
}
|
|
4966
5305
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -4968,7 +5307,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
4968
5307
|
level: "warn",
|
|
4969
5308
|
code: "tasks_empty",
|
|
4970
5309
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
4971
|
-
path: formatPath(cwd,
|
|
5310
|
+
path: formatPath(cwd, path6.join(f.path, "tasks.md"))
|
|
4972
5311
|
});
|
|
4973
5312
|
}
|
|
4974
5313
|
if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
|
|
@@ -4976,7 +5315,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
4976
5315
|
level: "warn",
|
|
4977
5316
|
code: "tasks_doc_status_missing",
|
|
4978
5317
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
4979
|
-
path: formatPath(cwd,
|
|
5318
|
+
path: formatPath(cwd, path6.join(f.path, "tasks.md"))
|
|
4980
5319
|
});
|
|
4981
5320
|
}
|
|
4982
5321
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -4984,7 +5323,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
4984
5323
|
level: "warn",
|
|
4985
5324
|
code: "tasks_doc_status_unset",
|
|
4986
5325
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
4987
|
-
path: formatPath(cwd,
|
|
5326
|
+
path: formatPath(cwd, path6.join(f.path, "tasks.md"))
|
|
4988
5327
|
});
|
|
4989
5328
|
}
|
|
4990
5329
|
}
|
|
@@ -5008,7 +5347,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
5008
5347
|
level: "warn",
|
|
5009
5348
|
code: "missing_feature_id",
|
|
5010
5349
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
5011
|
-
path: formatPath(cwd,
|
|
5350
|
+
path: formatPath(cwd, path6.join(config.docsDir, p))
|
|
5012
5351
|
});
|
|
5013
5352
|
}
|
|
5014
5353
|
return issues;
|
|
@@ -5130,7 +5469,7 @@ function doctorCommand(program2) {
|
|
|
5130
5469
|
}
|
|
5131
5470
|
console.log();
|
|
5132
5471
|
console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
|
|
5133
|
-
console.log(chalk6.gray(`- Docs: ${
|
|
5472
|
+
console.log(chalk6.gray(`- Docs: ${path6.relative(cwd, docsDir)}`));
|
|
5134
5473
|
console.log(chalk6.gray(`- Type: ${projectType}`));
|
|
5135
5474
|
console.log(chalk6.gray(`- Lang: ${lang}`));
|
|
5136
5475
|
console.log();
|
|
@@ -5230,208 +5569,6 @@ function doctorCommand(program2) {
|
|
|
5230
5569
|
}
|
|
5231
5570
|
});
|
|
5232
5571
|
}
|
|
5233
|
-
function resolveComponentOption(options) {
|
|
5234
|
-
const component = (options.component || options.repo || "").trim().toLowerCase();
|
|
5235
|
-
return component || void 0;
|
|
5236
|
-
}
|
|
5237
|
-
function getActionLabel2(index) {
|
|
5238
|
-
let n = index + 1;
|
|
5239
|
-
let label = "";
|
|
5240
|
-
while (n > 0) {
|
|
5241
|
-
const rem = (n - 1) % 26;
|
|
5242
|
-
label = String.fromCharCode(65 + rem) + label;
|
|
5243
|
-
n = Math.floor((n - 1) / 26);
|
|
5244
|
-
}
|
|
5245
|
-
return label;
|
|
5246
|
-
}
|
|
5247
|
-
function getActionSummary2(action) {
|
|
5248
|
-
if (action.category === "docs_commit") return "Commit docs updates";
|
|
5249
|
-
if (action.category === "issue_create") return "Create and record issue";
|
|
5250
|
-
if (action.category === "branch_create") return "Create feature branch";
|
|
5251
|
-
if (action.category === "pr_create") return "Create PR and record link";
|
|
5252
|
-
if (action.category === "pr_status_update") return "Update PR status";
|
|
5253
|
-
if (action.category === "code_review") return "Process code review feedback";
|
|
5254
|
-
if (action.category === "task_execute") return "Proceed with task execution";
|
|
5255
|
-
if (action.category === "feature_done") return "Feature is complete";
|
|
5256
|
-
if (action.category === "spec_approve") return "Request spec approval";
|
|
5257
|
-
if (action.category === "plan_approve") return "Request plan approval";
|
|
5258
|
-
if (action.category === "tasks_approve") return "Request tasks approval";
|
|
5259
|
-
if (action.category === "pr_metadata_migrate") return "Update tasks.md to latest PR fields";
|
|
5260
|
-
if (action.category === "fallback") return "Re-check context and rerun";
|
|
5261
|
-
if (action.type === "command") {
|
|
5262
|
-
return action.scope === "docs" ? "Run docs command" : "Run project command";
|
|
5263
|
-
}
|
|
5264
|
-
return action.message;
|
|
5265
|
-
}
|
|
5266
|
-
function formatActionSummary2(action) {
|
|
5267
|
-
if (action.type === "command") {
|
|
5268
|
-
return `(${action.scope}) ${action.cmd}`;
|
|
5269
|
-
}
|
|
5270
|
-
return action.message;
|
|
5271
|
-
}
|
|
5272
|
-
function toActionOptions2(actions) {
|
|
5273
|
-
return actions.map((action, index) => {
|
|
5274
|
-
const label = getActionLabel2(index);
|
|
5275
|
-
const summary = getActionSummary2(action);
|
|
5276
|
-
const detail = formatActionSummary2(action);
|
|
5277
|
-
return {
|
|
5278
|
-
label,
|
|
5279
|
-
summary,
|
|
5280
|
-
detail,
|
|
5281
|
-
approvalPrompt: `${label}: ${summary}`,
|
|
5282
|
-
action
|
|
5283
|
-
};
|
|
5284
|
-
});
|
|
5285
|
-
}
|
|
5286
|
-
function buildActionSnapshot2(actionOptions) {
|
|
5287
|
-
return actionOptions.map(({ label, action }) => {
|
|
5288
|
-
if (action.type === "command") {
|
|
5289
|
-
return {
|
|
5290
|
-
label,
|
|
5291
|
-
type: action.type,
|
|
5292
|
-
scope: action.scope,
|
|
5293
|
-
cwd: action.cwd,
|
|
5294
|
-
cmd: action.cmd,
|
|
5295
|
-
category: action.category,
|
|
5296
|
-
requiresUserCheck: !!action.requiresUserCheck
|
|
5297
|
-
};
|
|
5298
|
-
}
|
|
5299
|
-
return {
|
|
5300
|
-
label,
|
|
5301
|
-
type: action.type,
|
|
5302
|
-
message: action.message,
|
|
5303
|
-
category: action.category,
|
|
5304
|
-
requiresUserCheck: !!action.requiresUserCheck
|
|
5305
|
-
};
|
|
5306
|
-
});
|
|
5307
|
-
}
|
|
5308
|
-
function getContextVersion2(feature, actionOptions) {
|
|
5309
|
-
if (!feature) return null;
|
|
5310
|
-
const payload = JSON.stringify({
|
|
5311
|
-
id: feature.id || "",
|
|
5312
|
-
folderName: feature.folderName,
|
|
5313
|
-
currentStep: feature.currentStep,
|
|
5314
|
-
actionSnapshot: buildActionSnapshot2(actionOptions)
|
|
5315
|
-
});
|
|
5316
|
-
return createHash("sha256").update(payload).digest("hex").slice(0, 12);
|
|
5317
|
-
}
|
|
5318
|
-
function matchesFeatureSelector2(f, selector) {
|
|
5319
|
-
const s = selector.trim();
|
|
5320
|
-
if (!s) return false;
|
|
5321
|
-
if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
|
|
5322
|
-
if (f.slug.toLowerCase() === s.toLowerCase()) return true;
|
|
5323
|
-
if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
|
|
5324
|
-
return false;
|
|
5325
|
-
}
|
|
5326
|
-
function detectFromBranch2(branchName, features) {
|
|
5327
|
-
const match = branchName.match(/^feat\/\d+-(.+)$/);
|
|
5328
|
-
if (!match) return [];
|
|
5329
|
-
const detected = match[1];
|
|
5330
|
-
return features.filter(
|
|
5331
|
-
(f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
|
|
5332
|
-
);
|
|
5333
|
-
}
|
|
5334
|
-
function toSelectionStatus2(features, selectionMode, openFeatures, targetFeatures) {
|
|
5335
|
-
const isNoOpen = selectionMode === "open" && features.length > 0 && openFeatures.length === 0;
|
|
5336
|
-
if (features.length === 0) return "no_features";
|
|
5337
|
-
if (isNoOpen) return "no_open";
|
|
5338
|
-
if (targetFeatures.length === 1) return "single_matched";
|
|
5339
|
-
if (targetFeatures.length > 1) return "multiple_active";
|
|
5340
|
-
return "no_match";
|
|
5341
|
-
}
|
|
5342
|
-
function toReasonCode2(status) {
|
|
5343
|
-
if (status === "no_features") return "NO_FEATURES";
|
|
5344
|
-
if (status === "no_open") return "NO_OPEN_FEATURES";
|
|
5345
|
-
if (status === "single_matched") return "SINGLE_MATCHED";
|
|
5346
|
-
if (status === "multiple_active") return "MULTIPLE_ACTIVE_FEATURES";
|
|
5347
|
-
return "NO_MATCHED_FEATURES";
|
|
5348
|
-
}
|
|
5349
|
-
async function resolveContextSelection(config, featureName, options) {
|
|
5350
|
-
const { features, branches, warnings } = await scanFeatures(config);
|
|
5351
|
-
const selectedComponent = resolveComponentOption(options);
|
|
5352
|
-
const scopedFeatures = selectedComponent ? features.filter((f) => f.type === selectedComponent) : features;
|
|
5353
|
-
const doneFeatures = scopedFeatures.filter((f) => f.completion.workflowDone);
|
|
5354
|
-
const openFeatures = scopedFeatures.filter((f) => !f.completion.workflowDone);
|
|
5355
|
-
const inProgressFeatures = openFeatures.filter(
|
|
5356
|
-
(f) => !f.completion.implementationDone
|
|
5357
|
-
);
|
|
5358
|
-
const readyToCloseFeatures = openFeatures.filter(
|
|
5359
|
-
(f) => f.completion.implementationDone
|
|
5360
|
-
);
|
|
5361
|
-
let targetFeatures = [];
|
|
5362
|
-
let selectionMode = "explicit";
|
|
5363
|
-
if (featureName) {
|
|
5364
|
-
targetFeatures = scopedFeatures.filter(
|
|
5365
|
-
(f) => matchesFeatureSelector2(f, featureName)
|
|
5366
|
-
);
|
|
5367
|
-
selectionMode = "explicit";
|
|
5368
|
-
} else {
|
|
5369
|
-
if (config.projectType === "single") {
|
|
5370
|
-
const branchName = branches.project.single || "";
|
|
5371
|
-
targetFeatures = detectFromBranch2(branchName, scopedFeatures);
|
|
5372
|
-
} else if (selectedComponent) {
|
|
5373
|
-
const branchName = branches.project[selectedComponent] || "";
|
|
5374
|
-
targetFeatures = detectFromBranch2(
|
|
5375
|
-
branchName,
|
|
5376
|
-
scopedFeatures
|
|
5377
|
-
);
|
|
5378
|
-
} else {
|
|
5379
|
-
const matches = [];
|
|
5380
|
-
const componentKeys = [...new Set(scopedFeatures.map((f) => f.type))].filter((key) => key !== "single");
|
|
5381
|
-
for (const component of componentKeys) {
|
|
5382
|
-
const branchName = branches.project[component] || "";
|
|
5383
|
-
if (!branchName) continue;
|
|
5384
|
-
matches.push(
|
|
5385
|
-
...detectFromBranch2(
|
|
5386
|
-
branchName,
|
|
5387
|
-
scopedFeatures.filter((f) => f.type === component)
|
|
5388
|
-
)
|
|
5389
|
-
);
|
|
5390
|
-
}
|
|
5391
|
-
targetFeatures = matches;
|
|
5392
|
-
}
|
|
5393
|
-
if (targetFeatures.length > 0) {
|
|
5394
|
-
selectionMode = "branch";
|
|
5395
|
-
} else if (options.all) {
|
|
5396
|
-
targetFeatures = scopedFeatures;
|
|
5397
|
-
selectionMode = "all";
|
|
5398
|
-
} else if (options.done) {
|
|
5399
|
-
targetFeatures = doneFeatures;
|
|
5400
|
-
selectionMode = "done";
|
|
5401
|
-
} else {
|
|
5402
|
-
targetFeatures = openFeatures;
|
|
5403
|
-
selectionMode = "open";
|
|
5404
|
-
}
|
|
5405
|
-
}
|
|
5406
|
-
const status = toSelectionStatus2(
|
|
5407
|
-
scopedFeatures,
|
|
5408
|
-
selectionMode,
|
|
5409
|
-
openFeatures,
|
|
5410
|
-
targetFeatures
|
|
5411
|
-
);
|
|
5412
|
-
const matchedFeature = targetFeatures.length === 1 ? targetFeatures[0] : null;
|
|
5413
|
-
const actions = matchedFeature?.actions ?? [];
|
|
5414
|
-
const actionOptions = toActionOptions2(actions);
|
|
5415
|
-
const contextVersion = getContextVersion2(matchedFeature, actionOptions);
|
|
5416
|
-
return {
|
|
5417
|
-
features: scopedFeatures,
|
|
5418
|
-
branches,
|
|
5419
|
-
warnings,
|
|
5420
|
-
doneFeatures,
|
|
5421
|
-
openFeatures,
|
|
5422
|
-
inProgressFeatures,
|
|
5423
|
-
readyToCloseFeatures,
|
|
5424
|
-
selectionMode,
|
|
5425
|
-
targetFeatures,
|
|
5426
|
-
status,
|
|
5427
|
-
matchedFeature,
|
|
5428
|
-
actions,
|
|
5429
|
-
actionOptions,
|
|
5430
|
-
contextVersion
|
|
5431
|
-
};
|
|
5432
|
-
}
|
|
5433
|
-
|
|
5434
|
-
// src/commands/view.ts
|
|
5435
5572
|
function resolveComponentOption2(options) {
|
|
5436
5573
|
if (options.repo && options.component && options.repo.trim().toLowerCase() !== options.component.trim().toLowerCase()) {
|
|
5437
5574
|
throw createCliError(
|
|
@@ -5489,8 +5626,9 @@ async function runView(featureName, options) {
|
|
|
5489
5626
|
if (options.json) {
|
|
5490
5627
|
const payload = {
|
|
5491
5628
|
status: state.status,
|
|
5492
|
-
reasonCode:
|
|
5629
|
+
reasonCode: toReasonCode(state.status),
|
|
5493
5630
|
selectionMode: state.selectionMode,
|
|
5631
|
+
selectionFallback: state.selectionFallback,
|
|
5494
5632
|
counts: {
|
|
5495
5633
|
features: state.features.length,
|
|
5496
5634
|
open: state.openFeatures.length,
|
|
@@ -5510,7 +5648,7 @@ async function runView(featureName, options) {
|
|
|
5510
5648
|
}
|
|
5511
5649
|
console.log();
|
|
5512
5650
|
console.log(chalk6.bold("\u{1F4CA} Workflow View"));
|
|
5513
|
-
console.log(chalk6.gray(`- Docs: ${
|
|
5651
|
+
console.log(chalk6.gray(`- Docs: ${path6.relative(cwd, config.docsDir)}`));
|
|
5514
5652
|
console.log(
|
|
5515
5653
|
chalk6.gray(
|
|
5516
5654
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
@@ -5537,7 +5675,7 @@ async function runView(featureName, options) {
|
|
|
5537
5675
|
}
|
|
5538
5676
|
if (!state.matchedFeature) {
|
|
5539
5677
|
console.log();
|
|
5540
|
-
console.log(chalk6.blue(`Selection: ${state.status} (${
|
|
5678
|
+
console.log(chalk6.blue(`Selection: ${state.status} (${toReasonCode(state.status)})`));
|
|
5541
5679
|
const rows = state.targetFeatures.length > 0 ? state.targetFeatures : state.features;
|
|
5542
5680
|
for (const f2 of rows) {
|
|
5543
5681
|
const statusText = f2.completion.workflowDone ? chalk6.green("WORKFLOW_DONE") : f2.completion.implementationDone ? chalk6.cyan("DONE") : chalk6.yellow("IN_PROGRESS");
|
|
@@ -5725,16 +5863,18 @@ async function runFlow(featureName, options) {
|
|
|
5725
5863
|
context: {
|
|
5726
5864
|
before: {
|
|
5727
5865
|
status: before.status,
|
|
5728
|
-
reasonCode:
|
|
5866
|
+
reasonCode: toReasonCode(before.status),
|
|
5729
5867
|
selectionMode: before.selectionMode,
|
|
5868
|
+
selectionFallback: before.selectionFallback,
|
|
5730
5869
|
matchedFeature: before.matchedFeature,
|
|
5731
5870
|
actionOptions: before.actionOptions,
|
|
5732
5871
|
contextVersion: before.contextVersion
|
|
5733
5872
|
},
|
|
5734
5873
|
after: {
|
|
5735
5874
|
status: after.status,
|
|
5736
|
-
reasonCode:
|
|
5875
|
+
reasonCode: toReasonCode(after.status),
|
|
5737
5876
|
selectionMode: after.selectionMode,
|
|
5877
|
+
selectionFallback: after.selectionFallback,
|
|
5738
5878
|
matchedFeature: after.matchedFeature,
|
|
5739
5879
|
actionOptions: after.actionOptions,
|
|
5740
5880
|
contextVersion: after.contextVersion
|
|
@@ -5753,7 +5893,7 @@ async function runFlow(featureName, options) {
|
|
|
5753
5893
|
console.log(chalk6.bold("\u{1F501} Flow Summary"));
|
|
5754
5894
|
console.log(
|
|
5755
5895
|
chalk6.gray(
|
|
5756
|
-
`- Before: ${before.status} (${
|
|
5896
|
+
`- Before: ${before.status} (${toReasonCode(before.status)}) / After: ${after.status} (${toReasonCode(after.status)})`
|
|
5757
5897
|
)
|
|
5758
5898
|
);
|
|
5759
5899
|
if (approvalResult && typeof approvalResult === "object") {
|
|
@@ -5865,7 +6005,7 @@ function ensureSections(body, sections, kind) {
|
|
|
5865
6005
|
}
|
|
5866
6006
|
function ensureDocsExist(docsDir, relativePaths) {
|
|
5867
6007
|
const missing = relativePaths.filter(
|
|
5868
|
-
(relativePath) => !fs2.existsSync(
|
|
6008
|
+
(relativePath) => !fs2.existsSync(path6.join(docsDir, relativePath))
|
|
5869
6009
|
);
|
|
5870
6010
|
if (missing.length > 0) {
|
|
5871
6011
|
throw createCliError(
|
|
@@ -5875,8 +6015,8 @@ function ensureDocsExist(docsDir, relativePaths) {
|
|
|
5875
6015
|
}
|
|
5876
6016
|
}
|
|
5877
6017
|
function toBodyFilePath(raw, fallbackName) {
|
|
5878
|
-
const selected = raw?.trim() ||
|
|
5879
|
-
return
|
|
6018
|
+
const selected = raw?.trim() || path6.join(os.tmpdir(), fallbackName);
|
|
6019
|
+
return path6.resolve(selected);
|
|
5880
6020
|
}
|
|
5881
6021
|
async function resolveFeatureOrThrow(featureName, options) {
|
|
5882
6022
|
const config = await getConfig(process.cwd());
|
|
@@ -6049,7 +6189,7 @@ function ensureCleanWorktree(cwd) {
|
|
|
6049
6189
|
}
|
|
6050
6190
|
}
|
|
6051
6191
|
function commitAndPushPath(cwd, absPath, message) {
|
|
6052
|
-
const relativePath =
|
|
6192
|
+
const relativePath = path6.relative(cwd, absPath) || absPath;
|
|
6053
6193
|
const status = runProcessOrThrow(
|
|
6054
6194
|
"git",
|
|
6055
6195
|
["status", "--porcelain=v1", "--", relativePath],
|
|
@@ -6177,7 +6317,7 @@ function githubCommand(program2) {
|
|
|
6177
6317
|
options.bodyFile,
|
|
6178
6318
|
`lee-spec-kit.issue.${feature.folderName}.md`
|
|
6179
6319
|
);
|
|
6180
|
-
await fs2.ensureDir(
|
|
6320
|
+
await fs2.ensureDir(path6.dirname(bodyFile));
|
|
6181
6321
|
await fs2.writeFile(bodyFile, body, "utf-8");
|
|
6182
6322
|
let issueUrl;
|
|
6183
6323
|
if (options.create) {
|
|
@@ -6275,7 +6415,7 @@ function githubCommand(program2) {
|
|
|
6275
6415
|
options.bodyFile,
|
|
6276
6416
|
`lee-spec-kit.pr.${feature.folderName}.md`
|
|
6277
6417
|
);
|
|
6278
|
-
await fs2.ensureDir(
|
|
6418
|
+
await fs2.ensureDir(path6.dirname(bodyFile));
|
|
6279
6419
|
await fs2.writeFile(bodyFile, body, "utf-8");
|
|
6280
6420
|
const retryCount = toRetryCount(options.retry);
|
|
6281
6421
|
let prUrl = options.pr?.trim() || "";
|
|
@@ -6313,7 +6453,7 @@ function githubCommand(program2) {
|
|
|
6313
6453
|
}
|
|
6314
6454
|
if (prUrl && options.syncTasks !== false) {
|
|
6315
6455
|
const synced = syncTasksPrMetadata(
|
|
6316
|
-
|
|
6456
|
+
path6.join(config.docsDir, paths.tasksPath),
|
|
6317
6457
|
prUrl,
|
|
6318
6458
|
"Review"
|
|
6319
6459
|
);
|
|
@@ -6452,11 +6592,11 @@ ${version}
|
|
|
6452
6592
|
}
|
|
6453
6593
|
return `${ascii}${footer}`;
|
|
6454
6594
|
}
|
|
6455
|
-
var CACHE_FILE =
|
|
6595
|
+
var CACHE_FILE = path6.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
6456
6596
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
6457
6597
|
function getCurrentVersion() {
|
|
6458
6598
|
try {
|
|
6459
|
-
const packageJsonPath =
|
|
6599
|
+
const packageJsonPath = path6.join(__dirname$1, "..", "package.json");
|
|
6460
6600
|
if (fs2.existsSync(packageJsonPath)) {
|
|
6461
6601
|
const pkg = fs2.readJsonSync(packageJsonPath);
|
|
6462
6602
|
return pkg.version;
|
|
@@ -6552,7 +6692,7 @@ function shouldCheckForUpdates() {
|
|
|
6552
6692
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
6553
6693
|
function getCliVersion() {
|
|
6554
6694
|
try {
|
|
6555
|
-
const packageJsonPath =
|
|
6695
|
+
const packageJsonPath = path6.join(__dirname$1, "..", "package.json");
|
|
6556
6696
|
if (fs2.existsSync(packageJsonPath)) {
|
|
6557
6697
|
const pkg = fs2.readJsonSync(packageJsonPath);
|
|
6558
6698
|
if (pkg?.version) return String(pkg.version);
|