lee-spec-kit 0.4.2 → 0.4.4
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.md +26 -3
- package/dist/index.js +1524 -726
- package/package.json +1 -1
- package/templates/en/common/scripts/README.md +11 -0
- package/templates/ko/common/scripts/README.md +11 -0
package/dist/index.js
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
import path4 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { program } from 'commander';
|
|
5
|
-
import
|
|
5
|
+
import fs8 from 'fs-extra';
|
|
6
6
|
import prompts from 'prompts';
|
|
7
7
|
import chalk6 from 'chalk';
|
|
8
8
|
import { glob } from 'glob';
|
|
9
|
-
import { spawn, execSync } from 'child_process';
|
|
9
|
+
import { spawn, execSync, execFileSync } from 'child_process';
|
|
10
10
|
import os from 'os';
|
|
11
11
|
|
|
12
12
|
var getFilename = () => fileURLToPath(import.meta.url);
|
|
13
13
|
var getDirname = () => path4.dirname(getFilename());
|
|
14
14
|
var __dirname$1 = /* @__PURE__ */ getDirname();
|
|
15
15
|
async function copyTemplates(src, dest) {
|
|
16
|
-
await
|
|
16
|
+
await fs8.copy(src, dest, {
|
|
17
17
|
overwrite: true,
|
|
18
18
|
errorOnExist: false
|
|
19
19
|
});
|
|
@@ -21,19 +21,19 @@ async function copyTemplates(src, dest) {
|
|
|
21
21
|
async function replaceInFiles(dir, replacements) {
|
|
22
22
|
const files = await glob("**/*.md", { cwd: dir, absolute: true });
|
|
23
23
|
for (const file of files) {
|
|
24
|
-
let content = await
|
|
24
|
+
let content = await fs8.readFile(file, "utf-8");
|
|
25
25
|
for (const [search, replace] of Object.entries(replacements)) {
|
|
26
26
|
content = content.replaceAll(search, replace);
|
|
27
27
|
}
|
|
28
|
-
await
|
|
28
|
+
await fs8.writeFile(file, content, "utf-8");
|
|
29
29
|
}
|
|
30
30
|
const shFiles = await glob("**/*.sh", { cwd: dir, absolute: true });
|
|
31
31
|
for (const file of shFiles) {
|
|
32
|
-
let content = await
|
|
32
|
+
let content = await fs8.readFile(file, "utf-8");
|
|
33
33
|
for (const [search, replace] of Object.entries(replacements)) {
|
|
34
34
|
content = content.replaceAll(search, replace);
|
|
35
35
|
}
|
|
36
|
-
await
|
|
36
|
+
await fs8.writeFile(file, content, "utf-8");
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
@@ -43,6 +43,368 @@ function getTemplatesDir() {
|
|
|
43
43
|
return path4.join(rootDir, "templates");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// src/utils/i18n.ts
|
|
47
|
+
var DEFAULT_LANG = "en";
|
|
48
|
+
function normalizeLang(lang) {
|
|
49
|
+
if (lang === "ko" || lang === "en") return lang;
|
|
50
|
+
return DEFAULT_LANG;
|
|
51
|
+
}
|
|
52
|
+
function formatTemplate(template, vars) {
|
|
53
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
54
|
+
const value = vars[key];
|
|
55
|
+
return value === void 0 ? `{${key}}` : String(value);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
var I18N = {
|
|
59
|
+
ko: {
|
|
60
|
+
cli: {
|
|
61
|
+
"common.errorLabel": "\uC624\uB958:",
|
|
62
|
+
"common.canceled": "\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
63
|
+
"common.configNotFound": "\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.",
|
|
64
|
+
"common.docsNotFound": "docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
|
|
65
|
+
"status.noFeatures": "Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
66
|
+
"status.duplicateIds": "\uC911\uBCF5 Feature ID \uBC1C\uACAC:",
|
|
67
|
+
"status.missingIds": "Feature ID\uAC00 \uC5C6\uB294 \uD56D\uBAA9:",
|
|
68
|
+
"status.wrote": "\u2705 {path} \uC0DD\uC131 \uC644\uB8CC",
|
|
69
|
+
"feature.selectRepo": "\uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
70
|
+
"feature.folderExists": "\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: {path}",
|
|
71
|
+
"feature.baseNotFound": "feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
72
|
+
"feature.created": "\u2705 Feature \uD3F4\uB354 \uC0DD\uC131 \uC644\uB8CC: {path}",
|
|
73
|
+
"feature.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
|
|
74
|
+
"feature.nextSteps1": " 1. {path}/spec.md \uC791\uC131",
|
|
75
|
+
"feature.nextSteps2": " 2. \uC0AC\uC6A9\uC790 \uB9AC\uBDF0 \uC694\uCCAD",
|
|
76
|
+
"feature.nextSteps3": " 3. \uC2B9\uC778 \uD6C4 plan.md \uC791\uC131",
|
|
77
|
+
"config.currentTitle": "\u{1F4CB} \uD604\uC7AC \uC124\uC815:",
|
|
78
|
+
"config.pathLabel": "\uACBD\uB85C",
|
|
79
|
+
"config.projectRootStandaloneOnly": "\u26A0\uFE0F projectRoot\uB294 standalone \uBAA8\uB4DC\uC5D0\uC11C\uB9CC \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.",
|
|
80
|
+
"config.selectRepoToUpdate": "\uC218\uC815\uD560 \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
81
|
+
"config.fullstackRepoRequired": "Fullstack \uD504\uB85C\uC81D\uD2B8\uB294 --repo fe \uB610\uB294 --repo be\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.",
|
|
82
|
+
"config.projectRootSet": "\u2705 {repo} projectRoot \uC124\uC815 \uC644\uB8CC: {path}",
|
|
83
|
+
"config.projectRootSetSingle": "\u2705 projectRoot \uC124\uC815 \uC644\uB8CC: {path}",
|
|
84
|
+
"update.start": "\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...",
|
|
85
|
+
"update.langLabel": "\uC5B8\uC5B4",
|
|
86
|
+
"update.typeLabel": "\uD0C0\uC785",
|
|
87
|
+
"update.updatingAgents": "\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
|
|
88
|
+
"update.agentsUpdated": "agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
|
|
89
|
+
"update.updatingFeatureBase": "\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
|
|
90
|
+
"update.filesUpdated": "{count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
|
|
91
|
+
"update.updatedTotal": "\uCD1D {count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!",
|
|
92
|
+
"update.changeDetected": "\uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)",
|
|
93
|
+
"update.fileUpdated": "{file} \uC5C5\uB370\uC774\uD2B8",
|
|
94
|
+
"doctor.title": "\u{1F50E} \uBB38\uC11C \uC9C4\uB2E8",
|
|
95
|
+
"doctor.envWarnings": "\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:",
|
|
96
|
+
"doctor.noIssues": "\u2705 \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
|
|
97
|
+
"doctor.errorsTitle": "\uC624\uB958",
|
|
98
|
+
"doctor.warningsTitle": "\uACBD\uACE0",
|
|
99
|
+
"doctor.tipJson": "Tip: \uC5D0\uC774\uC804\uD2B8\uC6A9 JSON \uCD9C\uB825: npx lee-spec-kit doctor --json{strictFlag}",
|
|
100
|
+
"doctor.issue.missingRequiredDir": "\uD544\uC218 \uD3F4\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: {dir}",
|
|
101
|
+
"doctor.issue.missingConfig": "\uC124\uC815 \uD30C\uC77C(.lee-spec-kit.json)\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC77C\uBD80 \uAE30\uB2A5\uC774 \uD3F4\uB354 \uAD6C\uC870 \uCD94\uC815\uC73C\uB85C \uB3D9\uC791\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
102
|
+
"doctor.issue.noFeatures": "Feature \uD3F4\uB354\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. (feature-base\uB9CC \uC874\uC7AC\uD558\uAC70\uB098 \uC544\uC9C1 feature\uB97C \uB9CC\uB4E4\uC9C0 \uC54A\uC558\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.)",
|
|
103
|
+
"doctor.issue.placeholdersLeft": "\uD50C\uB808\uC774\uC2A4\uD640\uB354\uAC00 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4: {placeholders}",
|
|
104
|
+
"doctor.issue.missingSpec": "spec.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
105
|
+
"doctor.issue.specStatusUnset": "spec.md\uC758 Status(\uC0C1\uD0DC)\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uD15C\uD50C\uB9BF \uADF8\uB300\uB85C\uC77C \uC218 \uC788\uC74C)",
|
|
106
|
+
"doctor.issue.planStatusUnset": "plan.md\uC758 Status(\uC0C1\uD0DC)\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uD15C\uD50C\uB9BF \uADF8\uB300\uB85C\uC77C \uC218 \uC788\uC74C)",
|
|
107
|
+
"doctor.issue.tasksEmpty": "tasks.md\uC5D0 \uD0DC\uC2A4\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
108
|
+
"doctor.issue.duplicateFeatureId": "\uC911\uBCF5 Feature ID \uAC10\uC9C0: {id} ({count}\uAC1C)",
|
|
109
|
+
"doctor.issue.missingFeatureId": "Feature \uD3F4\uB354\uBA85\uC774 F001-... \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. (ID\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC74C)",
|
|
110
|
+
"context.noActiveFeatures": "\u26A0\uFE0F \uC9C4\uD589 \uC911\uC778 Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
111
|
+
"context.envWarnings": "\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:",
|
|
112
|
+
"context.openFallbackSummary": "(\uBE0C\uB79C\uCE58\uB85C Feature\uB97C \uD2B9\uC815\uD558\uC9C0 \uBABB\uD574 \uBBF8\uC644\uB8CC Feature\uB9CC \uD45C\uC2DC\uD569\uB2C8\uB2E4. \uC9C4\uD589 \uC911: {inProgress}\uAC1C / \uC885\uB8CC \uB300\uAE30: {readyToClose}\uAC1C / \uC644\uB8CC: {done}\uAC1C)",
|
|
113
|
+
"context.sectionInProgress": "\uC9C4\uD589 \uC911",
|
|
114
|
+
"context.sectionReadyToClose": "\uC885\uB8CC \uC900\uBE44",
|
|
115
|
+
"context.tipDetails": "Tip: \uD2B9\uC815 Feature\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uBCF4\uB824\uBA74:",
|
|
116
|
+
"context.tipShowAll": "\uC804\uCCB4 \uBCF4\uAE30",
|
|
117
|
+
"context.tipShowDone": "\uC644\uB8CC\uB9CC \uBCF4\uAE30",
|
|
118
|
+
"context.okRequired": "[OK \uD544\uC694] ",
|
|
119
|
+
"context.list.docsCommitNeeded": "\uBB38\uC11C \uCEE4\uBC0B \uD544\uC694",
|
|
120
|
+
"context.list.issueNumberNeeded": "\uC774\uC288 \uBC88\uD638 \uAE30\uB85D \uD544\uC694",
|
|
121
|
+
"context.list.addPrMetadata": "PR \uBA54\uD0C0\uB370\uC774\uD130(PR/PR \uC0C1\uD0DC) \uCD94\uAC00",
|
|
122
|
+
"context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
|
|
123
|
+
"context.list.setPrStatus": "PR \uC0C1\uD0DC \uC124\uC815",
|
|
124
|
+
"context.list.prStatusToApproved": "PR \uC0C1\uD0DC {status} \u2192 Approved",
|
|
125
|
+
"context.list.approveSpec": "spec \uC2B9\uC778 \uD544\uC694",
|
|
126
|
+
"context.list.approvePlan": "plan \uC2B9\uC778 \uD544\uC694",
|
|
127
|
+
"init.selectLangPrompt": "\uBB38\uC11C \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
128
|
+
"init.currentDirectoryLabel": "\u{1F4CD} \uD604\uC7AC \uC704\uCE58",
|
|
129
|
+
"init.gitDetected": "\u2705 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0\uB428",
|
|
130
|
+
"init.insideProjectRoot": "\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uB0B4\uC5D0\uC11C \uC2E4\uD589\uD558\uACE0 \uACC4\uC2ED\uB2C8\uB2E4.",
|
|
131
|
+
"init.modeEmbeddedDesc": "\u2022 embedded: \uC5EC\uAE30\uC5D0 ./docs \uD3F4\uB354\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 \uAD00\uB9AC\uB429\uB2C8\uB2E4.",
|
|
132
|
+
"init.modeStandaloneDesc": "\u2022 standalone: \uBCC4\uB3C4 \uD3F4\uB354\uC5D0\uC11C \uB3C5\uB9BD docs \uB808\uD3EC\uB85C \uAD00\uB9AC\uD558\uB824\uBA74,",
|
|
133
|
+
"init.modeStandaloneMove": " \uD574\uB2F9 \uD3F4\uB354\uB85C \uC774\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD574\uC8FC\uC138\uC694.",
|
|
134
|
+
"init.gitNotDetected": "\u26A0\uFE0F Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
|
|
135
|
+
"init.gitNotDetectedDetail": "\uC0C8\uB85C\uC6B4 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC0DD\uC131\uB429\uB2C8\uB2E4.",
|
|
136
|
+
"init.prompt.projectName": "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694:",
|
|
137
|
+
"init.prompt.projectType": "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
138
|
+
"init.choice.projectType.single.title": "Single - \uB2E8\uC77C \uB808\uD3EC \uD504\uB85C\uC81D\uD2B8",
|
|
139
|
+
"init.choice.projectType.single.desc": "features/ \uD3F4\uB354 \uD558\uB098\uB85C \uAD00\uB9AC",
|
|
140
|
+
"init.choice.projectType.fullstack.title": "Fullstack - FE/BE \uBD84\uB9AC \uD504\uB85C\uC81D\uD2B8",
|
|
141
|
+
"init.choice.projectType.fullstack.desc": "features/be/, features/fe/ \uBD84\uB9AC \uAD00\uB9AC",
|
|
142
|
+
"init.prompt.docsMode": "Docs \uAD00\uB9AC \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
143
|
+
"init.choice.docsRepo.embedded.title": "embedded - \uD504\uB85C\uC81D\uD2B8 \uB0B4 \uD3EC\uD568 (./docs)",
|
|
144
|
+
"init.choice.docsRepo.embedded.desc": "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4",
|
|
145
|
+
"init.choice.docsRepo.standalone.title": "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
|
|
146
|
+
"init.choice.docsRepo.standalone.desc": "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4",
|
|
147
|
+
"init.prompt.feRepoPath": "Frontend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
148
|
+
"init.prompt.beRepoPath": "Backend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
149
|
+
"init.prompt.projectRepoPath": "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
150
|
+
"init.validation.enterPath": "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694",
|
|
151
|
+
"init.prompt.pushMode": "Docs push \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
|
|
152
|
+
"init.choice.push.local": "local - \uB85C\uCEEC\uC5D0\uC11C\uB9CC \uAD00\uB9AC (push \uC548 \uD568)",
|
|
153
|
+
"init.choice.push.remote": "remote - \uC6D0\uACA9\uC5D0\uB3C4 push",
|
|
154
|
+
"init.prompt.remoteUrl": "\uC6D0\uACA9 \uB808\uD3EC URL\uC744 \uC785\uB825\uD558\uC138\uC694:",
|
|
155
|
+
"init.validation.enterUrl": "URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694",
|
|
156
|
+
"init.prompt.overwrite": "{dir} \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
157
|
+
"init.log.creatingDocs": "\u{1F4C1} docs \uAD6C\uC870 \uC0DD\uC131 \uC911...",
|
|
158
|
+
"init.log.projectLabel": "\uD504\uB85C\uC81D\uD2B8",
|
|
159
|
+
"init.log.typeLabel": "\uD0C0\uC785",
|
|
160
|
+
"init.log.langLabel": "\uC5B8\uC5B4",
|
|
161
|
+
"init.log.pathLabel": "\uACBD\uB85C",
|
|
162
|
+
"init.log.docsCreated": "\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!",
|
|
163
|
+
"init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
|
|
164
|
+
"init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
|
|
165
|
+
"init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
|
|
166
|
+
"init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
|
|
167
|
+
"init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
|
|
168
|
+
"init.warn.stagedChangesSkip": '\u26A0\uFE0F \uD604\uC7AC Git index\uC5D0 \uC774\uBBF8 stage\uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. (--dir "." \uC778 \uACBD\uC6B0 \uCEE4\uBC0B \uBC94\uC704\uB97C \uC548\uC804\uD558\uAC8C \uC81C\uD55C\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uCEE4\uBC0B\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4)',
|
|
169
|
+
"init.warn.commitManually": " \uC218\uB3D9\uC73C\uB85C \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD55C \uB4A4 \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694.",
|
|
170
|
+
"init.log.gitRemoteSet": "\u2705 Git remote \uC124\uC815 \uC644\uB8CC: {remote}",
|
|
171
|
+
"init.warn.gitRemoteExists": "\u26A0\uFE0F Git remote\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4.",
|
|
172
|
+
"init.log.gitInitialCommitDone": "\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!",
|
|
173
|
+
"init.warn.skipGitInit": "\u26A0\uFE0F Git \uCD08\uAE30\uD654\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4 (\uC218\uB3D9\uC73C\uB85C \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694)",
|
|
174
|
+
"init.error.templateNotFound": "\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {path}"
|
|
175
|
+
},
|
|
176
|
+
steps: {
|
|
177
|
+
featureFolder: "Feature \uD3F4\uB354 \uC0DD\uC131",
|
|
178
|
+
specWrite: "spec.md \uC791\uC131",
|
|
179
|
+
specApprove: "spec.md \uC2B9\uC778",
|
|
180
|
+
planWrite: "plan.md \uC791\uC131",
|
|
181
|
+
planApprove: "plan.md \uC2B9\uC778",
|
|
182
|
+
tasksWrite: "tasks.md \uC791\uC131",
|
|
183
|
+
docsCommitPlanning: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
|
|
184
|
+
issueCreate: "GitHub Issue \uC0DD\uC131",
|
|
185
|
+
branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
|
|
186
|
+
tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
|
|
187
|
+
prCreate: "PR \uC0DD\uC131",
|
|
188
|
+
codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
|
|
189
|
+
featureDone: "Feature \uC644\uB8CC"
|
|
190
|
+
},
|
|
191
|
+
messages: {
|
|
192
|
+
specCreate: "spec.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/spec.md \uCC38\uACE0)",
|
|
193
|
+
specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
194
|
+
specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
195
|
+
planCreate: "plan.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/plan.md \uCC38\uACE0)",
|
|
196
|
+
planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
197
|
+
planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
198
|
+
tasksCreate: "tasks.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694. (features/feature-base/tasks.md \uCC38\uACE0)",
|
|
199
|
+
tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
|
|
200
|
+
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
|
|
201
|
+
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. (skills/create-issue.md \uCC38\uACE0)",
|
|
202
|
+
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
|
|
203
|
+
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
204
|
+
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
205
|
+
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.',
|
|
206
|
+
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})",
|
|
207
|
+
finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
|
|
208
|
+
startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
|
|
209
|
+
checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)",
|
|
210
|
+
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? (OK \uD544\uC694)",
|
|
211
|
+
prCreate: "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694. (skills/create-pr.md \uCC38\uACE0)",
|
|
212
|
+
prFillStatus: "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Draft/Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694. (merge \uD6C4 Approved\uB85C \uC5C5\uB370\uC774\uD2B8)",
|
|
213
|
+
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)",
|
|
214
|
+
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.",
|
|
215
|
+
featureDone: "PR\uC774 Approved\uC774\uACE0 \uBAA8\uB4E0 \uD0DC\uC2A4\uD06C/\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uB8CC \uC0C1\uD0DC\uC785\uB2C8\uB2E4.",
|
|
216
|
+
fallbackRerunContext: "\uC0C1\uD0DC\uB97C \uD310\uBCC4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694."
|
|
217
|
+
},
|
|
218
|
+
warnings: {
|
|
219
|
+
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.)",
|
|
220
|
+
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)",
|
|
221
|
+
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)",
|
|
222
|
+
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.",
|
|
223
|
+
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.)",
|
|
224
|
+
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.)",
|
|
225
|
+
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.)",
|
|
226
|
+
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 Draft/Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694.)",
|
|
227
|
+
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.)"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
en: {
|
|
231
|
+
cli: {
|
|
232
|
+
"common.errorLabel": "Error:",
|
|
233
|
+
"common.canceled": "Operation canceled.",
|
|
234
|
+
"common.configNotFound": "Config file not found. Run `init` first.",
|
|
235
|
+
"common.docsNotFound": "docs folder not found. Run `init` first.",
|
|
236
|
+
"status.noFeatures": "No features found.",
|
|
237
|
+
"status.duplicateIds": "Duplicate Feature IDs found:",
|
|
238
|
+
"status.missingIds": "Entries missing Feature ID:",
|
|
239
|
+
"status.wrote": "\u2705 Wrote {path}",
|
|
240
|
+
"feature.selectRepo": "Select a repository:",
|
|
241
|
+
"feature.folderExists": "Folder already exists: {path}",
|
|
242
|
+
"feature.baseNotFound": "feature-base template not found.",
|
|
243
|
+
"feature.created": "\u2705 Feature folder created: {path}",
|
|
244
|
+
"feature.nextStepsTitle": "Next steps:",
|
|
245
|
+
"feature.nextSteps1": " 1. Write {path}/spec.md",
|
|
246
|
+
"feature.nextSteps2": " 2. Ask for review",
|
|
247
|
+
"feature.nextSteps3": " 3. After approval, write plan.md",
|
|
248
|
+
"config.currentTitle": "\u{1F4CB} Current config:",
|
|
249
|
+
"config.pathLabel": "Path",
|
|
250
|
+
"config.projectRootStandaloneOnly": "\u26A0\uFE0F projectRoot can only be set in standalone mode.",
|
|
251
|
+
"config.selectRepoToUpdate": "Select a repository to update:",
|
|
252
|
+
"config.fullstackRepoRequired": "For fullstack projects, you must specify `--repo fe` or `--repo be`.",
|
|
253
|
+
"config.projectRootSet": "\u2705 {repo} projectRoot set: {path}",
|
|
254
|
+
"config.projectRootSetSingle": "\u2705 projectRoot set: {path}",
|
|
255
|
+
"update.start": "\u{1F4E6} Starting template update...",
|
|
256
|
+
"update.langLabel": "Lang",
|
|
257
|
+
"update.typeLabel": "Type",
|
|
258
|
+
"update.updatingAgents": "\u{1F4C1} Updating agents/ folder...",
|
|
259
|
+
"update.agentsUpdated": "agents/ updated",
|
|
260
|
+
"update.updatingFeatureBase": "\u{1F4C1} Updating features/feature-base/ folder...",
|
|
261
|
+
"update.filesUpdated": "{count} files updated",
|
|
262
|
+
"update.updatedTotal": "Updated {count} files!",
|
|
263
|
+
"update.changeDetected": "changes detected (use --force to overwrite)",
|
|
264
|
+
"update.fileUpdated": "{file} updated",
|
|
265
|
+
"doctor.title": "\u{1F50E} Docs Doctor",
|
|
266
|
+
"doctor.envWarnings": "\u26A0\uFE0F Environment warnings:",
|
|
267
|
+
"doctor.noIssues": "\u2705 No issues found.",
|
|
268
|
+
"doctor.errorsTitle": "Errors",
|
|
269
|
+
"doctor.warningsTitle": "Warnings",
|
|
270
|
+
"doctor.tipJson": "Tip: Agent JSON output: npx lee-spec-kit doctor --json{strictFlag}",
|
|
271
|
+
"doctor.issue.missingRequiredDir": "Missing required directory: {dir}",
|
|
272
|
+
"doctor.issue.missingConfig": "Missing .lee-spec-kit.json. Some commands may rely on folder-structure heuristics.",
|
|
273
|
+
"doctor.issue.noFeatures": "No feature folders found. (Only feature-base exists, or no features created yet.)",
|
|
274
|
+
"doctor.issue.placeholdersLeft": "Leftover placeholders detected: {placeholders}",
|
|
275
|
+
"doctor.issue.missingSpec": "Missing spec.md.",
|
|
276
|
+
"doctor.issue.specStatusUnset": "spec.md Status is not set. (May still be a template)",
|
|
277
|
+
"doctor.issue.planStatusUnset": "plan.md Status is not set. (May still be a template)",
|
|
278
|
+
"doctor.issue.tasksEmpty": "tasks.md has no tasks.",
|
|
279
|
+
"doctor.issue.duplicateFeatureId": "Duplicate Feature ID detected: {id} ({count})",
|
|
280
|
+
"doctor.issue.missingFeatureId": "Feature folder name is not in F001-... format. (Cannot extract ID)",
|
|
281
|
+
"context.noActiveFeatures": "\u26A0\uFE0F No active features found.",
|
|
282
|
+
"context.envWarnings": "\u26A0\uFE0F Environment warnings:",
|
|
283
|
+
"context.openFallbackSummary": "(Could not detect a feature from the branch, so showing only open features. In Progress: {inProgress} / Ready To Close: {readyToClose} / Done: {done})",
|
|
284
|
+
"context.sectionInProgress": "In Progress",
|
|
285
|
+
"context.sectionReadyToClose": "Ready To Close",
|
|
286
|
+
"context.tipDetails": "Tip: To view details for a feature:",
|
|
287
|
+
"context.tipShowAll": "Show all",
|
|
288
|
+
"context.tipShowDone": "Show done only",
|
|
289
|
+
"context.okRequired": "[OK required] ",
|
|
290
|
+
"context.list.docsCommitNeeded": "Commit docs changes",
|
|
291
|
+
"context.list.issueNumberNeeded": "Fill issue number in docs",
|
|
292
|
+
"context.list.addPrMetadata": "Add PR metadata (PR/PR Status)",
|
|
293
|
+
"context.list.recordPrLink": "Record PR link",
|
|
294
|
+
"context.list.setPrStatus": "Set PR Status",
|
|
295
|
+
"context.list.prStatusToApproved": "PR Status {status} \u2192 Approved",
|
|
296
|
+
"context.list.approveSpec": "Approve spec",
|
|
297
|
+
"context.list.approvePlan": "Approve plan",
|
|
298
|
+
"init.selectLangPrompt": "Select docs language:",
|
|
299
|
+
"init.currentDirectoryLabel": "\u{1F4CD} Current directory",
|
|
300
|
+
"init.gitDetected": "\u2705 Git repository detected",
|
|
301
|
+
"init.insideProjectRoot": "You are running inside your project root.",
|
|
302
|
+
"init.modeEmbeddedDesc": "\u2022 embedded: creates ./docs here and manages it with the project.",
|
|
303
|
+
"init.modeStandaloneDesc": "\u2022 standalone: to manage docs as a separate repo,",
|
|
304
|
+
"init.modeStandaloneMove": " move to that folder and run again.",
|
|
305
|
+
"init.gitNotDetected": "\u26A0\uFE0F Git repository not detected.",
|
|
306
|
+
"init.gitNotDetectedDetail": "A new Git repo will be initialized.",
|
|
307
|
+
"init.prompt.projectName": "Enter project name:",
|
|
308
|
+
"init.prompt.projectType": "Select project type:",
|
|
309
|
+
"init.choice.projectType.single.title": "Single - single repo project",
|
|
310
|
+
"init.choice.projectType.single.desc": "Manage with a single features/ folder",
|
|
311
|
+
"init.choice.projectType.fullstack.title": "Fullstack - split FE/BE repos",
|
|
312
|
+
"init.choice.projectType.fullstack.desc": "Manage with features/be/ and features/fe/",
|
|
313
|
+
"init.prompt.docsMode": "Select docs mode:",
|
|
314
|
+
"init.choice.docsRepo.embedded.title": "embedded - inside the project (./docs)",
|
|
315
|
+
"init.choice.docsRepo.embedded.desc": "Pushed together with the project",
|
|
316
|
+
"init.choice.docsRepo.standalone.title": "standalone - separate docs repo",
|
|
317
|
+
"init.choice.docsRepo.standalone.desc": "Configure push settings separately",
|
|
318
|
+
"init.prompt.feRepoPath": "Enter frontend repository path:",
|
|
319
|
+
"init.prompt.beRepoPath": "Enter backend repository path:",
|
|
320
|
+
"init.prompt.projectRepoPath": "Enter project repository path:",
|
|
321
|
+
"init.validation.enterPath": "Please enter a path",
|
|
322
|
+
"init.prompt.pushMode": "Select docs push mode:",
|
|
323
|
+
"init.choice.push.local": "local - manage locally (no push)",
|
|
324
|
+
"init.choice.push.remote": "remote - push to remote",
|
|
325
|
+
"init.prompt.remoteUrl": "Enter remote repository URL:",
|
|
326
|
+
"init.validation.enterUrl": "Please enter a URL",
|
|
327
|
+
"init.prompt.overwrite": "{dir} already exists. Overwrite?",
|
|
328
|
+
"init.log.creatingDocs": "\u{1F4C1} Creating docs structure...",
|
|
329
|
+
"init.log.projectLabel": "Project",
|
|
330
|
+
"init.log.typeLabel": "Type",
|
|
331
|
+
"init.log.langLabel": "Lang",
|
|
332
|
+
"init.log.pathLabel": "Path",
|
|
333
|
+
"init.log.docsCreated": "\u2705 Docs structure created!",
|
|
334
|
+
"init.log.nextStepsTitle": "Next steps:",
|
|
335
|
+
"init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
|
|
336
|
+
"init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
|
|
337
|
+
"init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
|
|
338
|
+
"init.log.gitInit": "\u{1F4E6} Initializing Git...",
|
|
339
|
+
"init.warn.stagedChangesSkip": '\u26A0\uFE0F There are already staged changes in the Git index. (With --dir ".", commit scope cannot be safely restricted, so auto-commit is skipped.)',
|
|
340
|
+
"init.warn.commitManually": " Review the changes and commit manually.",
|
|
341
|
+
"init.log.gitRemoteSet": "\u2705 Git remote set: {remote}",
|
|
342
|
+
"init.warn.gitRemoteExists": "\u26A0\uFE0F Git remote already exists.",
|
|
343
|
+
"init.log.gitInitialCommitDone": "\u2705 Initial Git commit created!",
|
|
344
|
+
"init.warn.skipGitInit": "\u26A0\uFE0F Skipping Git initialization (please commit manually)",
|
|
345
|
+
"init.error.templateNotFound": "Template not found: {path}"
|
|
346
|
+
},
|
|
347
|
+
steps: {
|
|
348
|
+
featureFolder: "Create feature folder",
|
|
349
|
+
specWrite: "Write spec.md",
|
|
350
|
+
specApprove: "Approve spec.md",
|
|
351
|
+
planWrite: "Write plan.md",
|
|
352
|
+
planApprove: "Approve plan.md",
|
|
353
|
+
tasksWrite: "Write tasks.md",
|
|
354
|
+
docsCommitPlanning: "Commit docs (sync)",
|
|
355
|
+
issueCreate: "Create GitHub Issue",
|
|
356
|
+
branchCreate: "Create branch",
|
|
357
|
+
tasksExecute: "Execute tasks",
|
|
358
|
+
prCreate: "Create PR",
|
|
359
|
+
codeReview: "Code review",
|
|
360
|
+
featureDone: "Feature done"
|
|
361
|
+
},
|
|
362
|
+
messages: {
|
|
363
|
+
specCreate: "Create spec.md by copying the template. (See features/feature-base/spec.md)",
|
|
364
|
+
specImprove: "Improve spec.md and change Status to Review.",
|
|
365
|
+
specApproval: "Share spec.md with the user and get approval (OK).",
|
|
366
|
+
planCreate: "Create plan.md by copying the template. (See features/feature-base/plan.md)",
|
|
367
|
+
planImprove: "Improve plan.md and change Status to Review.",
|
|
368
|
+
planApproval: "Share plan.md with the user and get approval (OK).",
|
|
369
|
+
tasksCreate: "Create tasks.md by copying the template. (See features/feature-base/tasks.md)",
|
|
370
|
+
tasksNeedAtLeastOne: "Write at least 1 task in tasks.md.",
|
|
371
|
+
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
|
|
372
|
+
issueCreateAndWrite: "Create a GitHub Issue, fill the issue number in spec.md/tasks.md, then prepare a docs commit. (See skills/create-issue.md)",
|
|
373
|
+
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
|
|
374
|
+
standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
|
|
375
|
+
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
376
|
+
tasksAllDoneButNoChecklist: 'All tasks are DONE, but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
|
|
377
|
+
tasksAllDoneButChecklist: "All tasks are DONE, but the completion checklist is not fully checked. ({checked}/{total})",
|
|
378
|
+
finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
|
|
379
|
+
startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
|
|
380
|
+
checkTaskStatuses: "Check task statuses. ({done}/{total}) (See skills/execute-task.md)",
|
|
381
|
+
prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (OK required)",
|
|
382
|
+
prCreate: "Create a PR and record the PR link in tasks.md. (See skills/create-pr.md)",
|
|
383
|
+
prFillStatus: "Set PR Status in tasks.md to Draft/Review/Approved. (After merge, update it to Approved.)",
|
|
384
|
+
prResolveReview: "Resolve review comments and update PR Status. (PR Status: Review \u2192 Approved)",
|
|
385
|
+
prRequestReview: "Request review and update PR Status to Review.",
|
|
386
|
+
featureDone: "PR is Approved and all tasks/completion criteria are satisfied. This feature is done.",
|
|
387
|
+
fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
|
|
388
|
+
},
|
|
389
|
+
warnings: {
|
|
390
|
+
projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
|
|
391
|
+
docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
|
|
392
|
+
docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.)",
|
|
393
|
+
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
|
|
394
|
+
workflowSpecNotApproved: "Implementation is done but spec.md Status is not Approved. (Update spec.md Status to Approved.)",
|
|
395
|
+
workflowPlanNotApproved: "Implementation is done but plan.md Status is not Approved. (Update plan.md Status to Approved.)",
|
|
396
|
+
workflowPrLinkMissing: "Implementation is done but PR link is missing. (Fill the PR field in tasks.md.)",
|
|
397
|
+
workflowPrStatusMissing: "Implementation is done but PR Status is missing. (Set PR Status to Draft/Review/Approved in tasks.md.)",
|
|
398
|
+
workflowPrStatusNotApproved: "Implementation is done but PR Status is not Approved. (After merge, update PR Status to Approved in tasks.md.)"
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function tr(lang, category, key, vars = {}) {
|
|
403
|
+
const safeLang = normalizeLang(lang);
|
|
404
|
+
const template = I18N[safeLang]?.[category]?.[key] ?? I18N[DEFAULT_LANG]?.[category]?.[key] ?? I18N.ko?.[category]?.[key] ?? `${category}.${key}`;
|
|
405
|
+
return formatTemplate(template, vars);
|
|
406
|
+
}
|
|
407
|
+
|
|
46
408
|
// src/utils/validation.ts
|
|
47
409
|
var VALID_PROJECT_TYPES = ["single", "fullstack"];
|
|
48
410
|
var VALID_LANGUAGES = ["ko", "en"];
|
|
@@ -149,15 +511,19 @@ function checkGitRepo(cwd) {
|
|
|
149
511
|
}
|
|
150
512
|
}
|
|
151
513
|
function initCommand(program2) {
|
|
152
|
-
program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option("-t, --type <type>", "Project type: single | fullstack").option("-l, --lang <lang>", "Language: ko | en (default:
|
|
514
|
+
program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option("-t, --type <type>", "Project type: single | fullstack").option("-l, --lang <lang>", "Language: ko | en (default: en)").option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
|
|
153
515
|
try {
|
|
154
516
|
await runInit(options);
|
|
155
517
|
} catch (error) {
|
|
156
518
|
if (error instanceof Error && error.message === "canceled") {
|
|
157
|
-
|
|
519
|
+
const lang = options.lang ?? DEFAULT_LANG;
|
|
520
|
+
console.log(
|
|
521
|
+
chalk6.yellow(`
|
|
522
|
+
${tr(lang, "cli", "common.canceled")}`)
|
|
523
|
+
);
|
|
158
524
|
process.exit(0);
|
|
159
525
|
}
|
|
160
|
-
console.error(chalk6.red("
|
|
526
|
+
console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
|
|
161
527
|
process.exit(1);
|
|
162
528
|
}
|
|
163
529
|
});
|
|
@@ -167,7 +533,7 @@ async function runInit(options) {
|
|
|
167
533
|
const defaultName = path4.basename(cwd);
|
|
168
534
|
let projectName = options.name || defaultName;
|
|
169
535
|
let projectType = options.type;
|
|
170
|
-
let lang = options.lang || "
|
|
536
|
+
let lang = options.lang || "en";
|
|
171
537
|
let docsRepo = "embedded";
|
|
172
538
|
let pushDocs;
|
|
173
539
|
let docsRemote;
|
|
@@ -175,24 +541,62 @@ async function runInit(options) {
|
|
|
175
541
|
const targetDir = path4.resolve(cwd, options.dir || "./docs");
|
|
176
542
|
const isInsideGitRepo = checkGitRepo(cwd);
|
|
177
543
|
if (!options.yes) {
|
|
544
|
+
if (!options.lang) {
|
|
545
|
+
const langResponse = await prompts(
|
|
546
|
+
[
|
|
547
|
+
{
|
|
548
|
+
type: "select",
|
|
549
|
+
name: "lang",
|
|
550
|
+
message: tr(DEFAULT_LANG, "cli", "init.selectLangPrompt"),
|
|
551
|
+
choices: [
|
|
552
|
+
{ title: "English (en)", value: "en" },
|
|
553
|
+
{ title: "\uD55C\uAD6D\uC5B4 (ko)", value: "ko" }
|
|
554
|
+
],
|
|
555
|
+
initial: 0
|
|
556
|
+
}
|
|
557
|
+
],
|
|
558
|
+
{
|
|
559
|
+
onCancel: () => {
|
|
560
|
+
throw new Error("canceled");
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
);
|
|
564
|
+
lang = langResponse.lang || lang;
|
|
565
|
+
}
|
|
178
566
|
console.log();
|
|
179
|
-
console.log(
|
|
567
|
+
console.log(
|
|
568
|
+
chalk6.blue(`${tr(lang, "cli", "init.currentDirectoryLabel")}: ${cwd}`)
|
|
569
|
+
);
|
|
180
570
|
if (isInsideGitRepo) {
|
|
181
|
-
console.log(chalk6.green("
|
|
571
|
+
console.log(chalk6.green(tr(lang, "cli", "init.gitDetected")));
|
|
182
572
|
console.log();
|
|
183
|
-
console.log(chalk6.gray("\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uB0B4\uC5D0\uC11C \uC2E4\uD589\uD558\uACE0 \uACC4\uC2ED\uB2C8\uB2E4."));
|
|
184
573
|
console.log(
|
|
185
574
|
chalk6.gray(
|
|
186
|
-
"
|
|
575
|
+
tr(lang, "cli", "init.insideProjectRoot")
|
|
187
576
|
)
|
|
188
577
|
);
|
|
189
578
|
console.log(
|
|
190
|
-
chalk6.gray(
|
|
579
|
+
chalk6.gray(
|
|
580
|
+
tr(lang, "cli", "init.modeEmbeddedDesc")
|
|
581
|
+
)
|
|
582
|
+
);
|
|
583
|
+
console.log(
|
|
584
|
+
chalk6.gray(
|
|
585
|
+
tr(lang, "cli", "init.modeStandaloneDesc")
|
|
586
|
+
)
|
|
587
|
+
);
|
|
588
|
+
console.log(
|
|
589
|
+
chalk6.gray(
|
|
590
|
+
tr(lang, "cli", "init.modeStandaloneMove")
|
|
591
|
+
)
|
|
191
592
|
);
|
|
192
|
-
console.log(chalk6.gray(" \uD574\uB2F9 \uD3F4\uB354\uB85C \uC774\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD574\uC8FC\uC138\uC694."));
|
|
193
593
|
} else {
|
|
194
|
-
console.log(
|
|
195
|
-
|
|
594
|
+
console.log(
|
|
595
|
+
chalk6.yellow(
|
|
596
|
+
tr(lang, "cli", "init.gitNotDetected")
|
|
597
|
+
)
|
|
598
|
+
);
|
|
599
|
+
console.log(chalk6.gray(tr(lang, "cli", "init.gitNotDetectedDetail")));
|
|
196
600
|
}
|
|
197
601
|
console.log();
|
|
198
602
|
const response = await prompts(
|
|
@@ -200,51 +604,41 @@ async function runInit(options) {
|
|
|
200
604
|
{
|
|
201
605
|
type: options.name ? null : "text",
|
|
202
606
|
name: "projectName",
|
|
203
|
-
message: "
|
|
607
|
+
message: tr(lang, "cli", "init.prompt.projectName"),
|
|
204
608
|
initial: defaultName
|
|
205
609
|
},
|
|
206
610
|
{
|
|
207
611
|
type: options.type ? null : "select",
|
|
208
612
|
name: "projectType",
|
|
209
|
-
message: "
|
|
613
|
+
message: tr(lang, "cli", "init.prompt.projectType"),
|
|
210
614
|
choices: [
|
|
211
615
|
{
|
|
212
|
-
title: "
|
|
616
|
+
title: tr(lang, "cli", "init.choice.projectType.single.title"),
|
|
213
617
|
value: "single",
|
|
214
|
-
description: "
|
|
618
|
+
description: tr(lang, "cli", "init.choice.projectType.single.desc")
|
|
215
619
|
},
|
|
216
620
|
{
|
|
217
|
-
title: "
|
|
621
|
+
title: tr(lang, "cli", "init.choice.projectType.fullstack.title"),
|
|
218
622
|
value: "fullstack",
|
|
219
|
-
description: "
|
|
623
|
+
description: tr(lang, "cli", "init.choice.projectType.fullstack.desc")
|
|
220
624
|
}
|
|
221
625
|
],
|
|
222
626
|
initial: 0
|
|
223
627
|
},
|
|
224
|
-
{
|
|
225
|
-
type: options.lang ? null : "select",
|
|
226
|
-
name: "lang",
|
|
227
|
-
message: "\uBB38\uC11C \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
228
|
-
choices: [
|
|
229
|
-
{ title: "\uD55C\uAD6D\uC5B4 (ko)", value: "ko" },
|
|
230
|
-
{ title: "English (en)", value: "en" }
|
|
231
|
-
],
|
|
232
|
-
initial: 0
|
|
233
|
-
},
|
|
234
628
|
{
|
|
235
629
|
type: "select",
|
|
236
630
|
name: "docsRepo",
|
|
237
|
-
message: "
|
|
631
|
+
message: tr(lang, "cli", "init.prompt.docsMode"),
|
|
238
632
|
choices: [
|
|
239
633
|
{
|
|
240
|
-
title: "embedded
|
|
634
|
+
title: tr(lang, "cli", "init.choice.docsRepo.embedded.title"),
|
|
241
635
|
value: "embedded",
|
|
242
|
-
description: "
|
|
636
|
+
description: tr(lang, "cli", "init.choice.docsRepo.embedded.desc")
|
|
243
637
|
},
|
|
244
638
|
{
|
|
245
|
-
title: "
|
|
639
|
+
title: tr(lang, "cli", "init.choice.docsRepo.standalone.title"),
|
|
246
640
|
value: "standalone",
|
|
247
|
-
description: "
|
|
641
|
+
description: tr(lang, "cli", "init.choice.docsRepo.standalone.desc")
|
|
248
642
|
}
|
|
249
643
|
],
|
|
250
644
|
initial: 0
|
|
@@ -258,7 +652,6 @@ async function runInit(options) {
|
|
|
258
652
|
);
|
|
259
653
|
projectName = response.projectName || projectName;
|
|
260
654
|
projectType = response.projectType || projectType;
|
|
261
|
-
lang = response.lang || lang;
|
|
262
655
|
docsRepo = response.docsRepo || "embedded";
|
|
263
656
|
if (docsRepo === "standalone") {
|
|
264
657
|
const resolvedType = projectType || response.projectType || "single";
|
|
@@ -268,14 +661,14 @@ async function runInit(options) {
|
|
|
268
661
|
{
|
|
269
662
|
type: "text",
|
|
270
663
|
name: "feRoot",
|
|
271
|
-
message: "
|
|
272
|
-
validate: (value) => value.trim() ? true : "
|
|
664
|
+
message: tr(lang, "cli", "init.prompt.feRepoPath"),
|
|
665
|
+
validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
|
|
273
666
|
},
|
|
274
667
|
{
|
|
275
668
|
type: "text",
|
|
276
669
|
name: "beRoot",
|
|
277
|
-
message: "
|
|
278
|
-
validate: (value) => value.trim() ? true : "
|
|
670
|
+
message: tr(lang, "cli", "init.prompt.beRepoPath"),
|
|
671
|
+
validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
|
|
279
672
|
}
|
|
280
673
|
],
|
|
281
674
|
{
|
|
@@ -294,8 +687,8 @@ async function runInit(options) {
|
|
|
294
687
|
{
|
|
295
688
|
type: "text",
|
|
296
689
|
name: "projectRoot",
|
|
297
|
-
message: "
|
|
298
|
-
validate: (value) => value.trim() ? true : "
|
|
690
|
+
message: tr(lang, "cli", "init.prompt.projectRepoPath"),
|
|
691
|
+
validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
|
|
299
692
|
}
|
|
300
693
|
],
|
|
301
694
|
{
|
|
@@ -311,14 +704,14 @@ async function runInit(options) {
|
|
|
311
704
|
{
|
|
312
705
|
type: "select",
|
|
313
706
|
name: "pushDocs",
|
|
314
|
-
message: "
|
|
707
|
+
message: tr(lang, "cli", "init.prompt.pushMode"),
|
|
315
708
|
choices: [
|
|
316
709
|
{
|
|
317
|
-
title: "
|
|
710
|
+
title: tr(lang, "cli", "init.choice.push.local"),
|
|
318
711
|
value: false
|
|
319
712
|
},
|
|
320
713
|
{
|
|
321
|
-
title: "
|
|
714
|
+
title: tr(lang, "cli", "init.choice.push.remote"),
|
|
322
715
|
value: true
|
|
323
716
|
}
|
|
324
717
|
],
|
|
@@ -338,8 +731,8 @@ async function runInit(options) {
|
|
|
338
731
|
{
|
|
339
732
|
type: "text",
|
|
340
733
|
name: "docsRemote",
|
|
341
|
-
message: "
|
|
342
|
-
validate: (value) => value.trim() ? true : "
|
|
734
|
+
message: tr(lang, "cli", "init.prompt.remoteUrl"),
|
|
735
|
+
validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterUrl")
|
|
343
736
|
}
|
|
344
737
|
],
|
|
345
738
|
{
|
|
@@ -358,36 +751,42 @@ async function runInit(options) {
|
|
|
358
751
|
assertValid(validateSafeName(projectName), "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984");
|
|
359
752
|
assertValid(validateProjectType(projectType), "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785");
|
|
360
753
|
assertValid(validateLanguage(lang), "\uC5B8\uC5B4");
|
|
361
|
-
if (await
|
|
362
|
-
const files = await
|
|
754
|
+
if (await fs8.pathExists(targetDir)) {
|
|
755
|
+
const files = await fs8.readdir(targetDir);
|
|
363
756
|
if (files.length > 0) {
|
|
364
757
|
const { overwrite } = await prompts({
|
|
365
758
|
type: "confirm",
|
|
366
759
|
name: "overwrite",
|
|
367
|
-
message:
|
|
760
|
+
message: tr(lang, "cli", "init.prompt.overwrite", { dir: targetDir }),
|
|
368
761
|
initial: false
|
|
369
762
|
});
|
|
370
763
|
if (!overwrite) {
|
|
371
|
-
console.log(chalk6.yellow("
|
|
764
|
+
console.log(chalk6.yellow(tr(lang, "cli", "common.canceled")));
|
|
372
765
|
return;
|
|
373
766
|
}
|
|
374
767
|
}
|
|
375
768
|
}
|
|
376
769
|
console.log();
|
|
377
|
-
console.log(chalk6.blue("
|
|
378
|
-
console.log(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
console.log(
|
|
770
|
+
console.log(chalk6.blue(tr(lang, "cli", "init.log.creatingDocs")));
|
|
771
|
+
console.log(
|
|
772
|
+
chalk6.gray(` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`)
|
|
773
|
+
);
|
|
774
|
+
console.log(
|
|
775
|
+
chalk6.gray(` ${tr(lang, "cli", "init.log.typeLabel")}: ${projectType}`)
|
|
776
|
+
);
|
|
777
|
+
console.log(chalk6.gray(` ${tr(lang, "cli", "init.log.langLabel")}: ${lang}`));
|
|
778
|
+
console.log(
|
|
779
|
+
chalk6.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
|
|
780
|
+
);
|
|
382
781
|
console.log();
|
|
383
782
|
const templatesDir = getTemplatesDir();
|
|
384
783
|
const commonPath = path4.join(templatesDir, lang, "common");
|
|
385
784
|
const typePath = path4.join(templatesDir, lang, projectType);
|
|
386
|
-
if (await
|
|
785
|
+
if (await fs8.pathExists(commonPath)) {
|
|
387
786
|
await copyTemplates(commonPath, targetDir);
|
|
388
787
|
}
|
|
389
|
-
if (!await
|
|
390
|
-
throw new Error(
|
|
788
|
+
if (!await fs8.pathExists(typePath)) {
|
|
789
|
+
throw new Error(tr(lang, "cli", "init.error.templateNotFound", { path: typePath }));
|
|
391
790
|
}
|
|
392
791
|
await copyTemplates(typePath, targetDir);
|
|
393
792
|
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
@@ -414,51 +813,74 @@ async function runInit(options) {
|
|
|
414
813
|
}
|
|
415
814
|
}
|
|
416
815
|
const configPath = path4.join(targetDir, ".lee-spec-kit.json");
|
|
417
|
-
await
|
|
418
|
-
console.log(chalk6.green("
|
|
816
|
+
await fs8.writeJson(configPath, config, { spaces: 2 });
|
|
817
|
+
console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
|
|
419
818
|
console.log();
|
|
420
|
-
await initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote);
|
|
421
|
-
console.log(chalk6.blue("
|
|
422
|
-
console.log(chalk6.gray(` 1. ${targetDir}/prd/README.md \uC791\uC131`));
|
|
819
|
+
await initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote);
|
|
820
|
+
console.log(chalk6.blue(tr(lang, "cli", "init.log.nextStepsTitle")));
|
|
423
821
|
console.log(
|
|
424
|
-
chalk6.gray("
|
|
822
|
+
chalk6.gray(tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir }))
|
|
425
823
|
);
|
|
824
|
+
console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps2")));
|
|
426
825
|
console.log();
|
|
427
826
|
}
|
|
428
|
-
async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
|
|
827
|
+
async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
429
828
|
try {
|
|
829
|
+
const runGit = (args, workdir) => {
|
|
830
|
+
execFileSync("git", args, { cwd: workdir, stdio: "ignore" });
|
|
831
|
+
};
|
|
832
|
+
const getCachedStagedFiles = (workdir) => {
|
|
833
|
+
try {
|
|
834
|
+
const out = execFileSync("git", ["diff", "--cached", "--name-only"], {
|
|
835
|
+
cwd: workdir,
|
|
836
|
+
encoding: "utf-8",
|
|
837
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
838
|
+
}).trim();
|
|
839
|
+
if (!out) return [];
|
|
840
|
+
return out.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
841
|
+
} catch {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
};
|
|
430
845
|
try {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
stdio: "ignore"
|
|
434
|
-
});
|
|
435
|
-
console.log(chalk6.blue("\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911..."));
|
|
846
|
+
runGit(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
847
|
+
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit")));
|
|
436
848
|
} catch {
|
|
437
|
-
console.log(chalk6.blue("
|
|
438
|
-
|
|
849
|
+
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
850
|
+
runGit(["init"], cwd);
|
|
439
851
|
}
|
|
440
852
|
const relativePath = path4.relative(cwd, targetDir);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
853
|
+
const stagedBeforeAdd = getCachedStagedFiles(cwd);
|
|
854
|
+
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
855
|
+
console.log(
|
|
856
|
+
chalk6.yellow(
|
|
857
|
+
tr(lang, "cli", "init.warn.stagedChangesSkip")
|
|
858
|
+
)
|
|
859
|
+
);
|
|
860
|
+
console.log(chalk6.gray(tr(lang, "cli", "init.warn.commitManually")));
|
|
861
|
+
console.log();
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
runGit(["add", relativePath], cwd);
|
|
865
|
+
runGit(
|
|
866
|
+
["commit", "-m", "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)", "--", relativePath],
|
|
867
|
+
cwd
|
|
868
|
+
);
|
|
446
869
|
if (docsRepo === "standalone" && pushDocs && docsRemote) {
|
|
447
870
|
try {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
console.log(chalk6.green(`\u2705 Git remote \uC124\uC815 \uC644\uB8CC: ${docsRemote}`));
|
|
871
|
+
runGit(["remote", "add", "origin", docsRemote], cwd);
|
|
872
|
+
console.log(
|
|
873
|
+
chalk6.green(tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote }))
|
|
874
|
+
);
|
|
453
875
|
} catch {
|
|
454
|
-
console.log(chalk6.yellow("
|
|
876
|
+
console.log(chalk6.yellow(tr(lang, "cli", "init.warn.gitRemoteExists")));
|
|
455
877
|
}
|
|
456
878
|
}
|
|
457
|
-
console.log(chalk6.green("
|
|
879
|
+
console.log(chalk6.green(tr(lang, "cli", "init.log.gitInitialCommitDone")));
|
|
458
880
|
console.log();
|
|
459
881
|
} catch {
|
|
460
882
|
console.log(
|
|
461
|
-
chalk6.yellow(
|
|
883
|
+
chalk6.yellow(tr(lang, "cli", "init.warn.skipGitInit"))
|
|
462
884
|
);
|
|
463
885
|
console.log();
|
|
464
886
|
}
|
|
@@ -492,9 +914,9 @@ async function getConfig(cwd) {
|
|
|
492
914
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
493
915
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
494
916
|
const configPath = path4.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
495
|
-
if (await
|
|
917
|
+
if (await fs8.pathExists(configPath)) {
|
|
496
918
|
try {
|
|
497
|
-
const configFile = await
|
|
919
|
+
const configFile = await fs8.readJson(configPath);
|
|
498
920
|
return {
|
|
499
921
|
docsDir: resolvedDocsDir,
|
|
500
922
|
projectName: configFile.projectName,
|
|
@@ -510,17 +932,15 @@ async function getConfig(cwd) {
|
|
|
510
932
|
}
|
|
511
933
|
const agentsPath = path4.join(resolvedDocsDir, "agents");
|
|
512
934
|
const featuresPath = path4.join(resolvedDocsDir, "features");
|
|
513
|
-
if (await
|
|
935
|
+
if (await fs8.pathExists(agentsPath) && await fs8.pathExists(featuresPath)) {
|
|
514
936
|
const bePath = path4.join(featuresPath, "be");
|
|
515
937
|
const fePath = path4.join(featuresPath, "fe");
|
|
516
|
-
const projectType = await
|
|
938
|
+
const projectType = await fs8.pathExists(bePath) || await fs8.pathExists(fePath) ? "fullstack" : "single";
|
|
517
939
|
const agentsMdPath = path4.join(agentsPath, "agents.md");
|
|
518
|
-
let lang = "
|
|
519
|
-
if (await
|
|
520
|
-
const content = await
|
|
521
|
-
if (
|
|
522
|
-
lang = "en";
|
|
523
|
-
}
|
|
940
|
+
let lang = "en";
|
|
941
|
+
if (await fs8.pathExists(agentsMdPath)) {
|
|
942
|
+
const content = await fs8.readFile(agentsMdPath, "utf-8");
|
|
943
|
+
if (/[가-힣]/.test(content)) lang = "ko";
|
|
524
944
|
}
|
|
525
945
|
return { docsDir: resolvedDocsDir, projectType, lang };
|
|
526
946
|
}
|
|
@@ -536,10 +956,13 @@ function featureCommand(program2) {
|
|
|
536
956
|
await runFeature(name, options);
|
|
537
957
|
} catch (error) {
|
|
538
958
|
if (error instanceof Error && error.message === "canceled") {
|
|
539
|
-
|
|
959
|
+
const config = await getConfig(process.cwd());
|
|
960
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
961
|
+
console.log(chalk6.yellow(`
|
|
962
|
+
${tr(lang, "cli", "common.canceled")}`));
|
|
540
963
|
process.exit(0);
|
|
541
964
|
}
|
|
542
|
-
console.error(chalk6.red("
|
|
965
|
+
console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
|
|
543
966
|
process.exit(1);
|
|
544
967
|
}
|
|
545
968
|
});
|
|
@@ -549,7 +972,9 @@ async function runFeature(name, options) {
|
|
|
549
972
|
const config = await getConfig(cwd);
|
|
550
973
|
if (!config) {
|
|
551
974
|
console.error(
|
|
552
|
-
chalk6.red(
|
|
975
|
+
chalk6.red(
|
|
976
|
+
tr(DEFAULT_LANG, "cli", "common.docsNotFound")
|
|
977
|
+
)
|
|
553
978
|
);
|
|
554
979
|
process.exit(1);
|
|
555
980
|
}
|
|
@@ -561,7 +986,7 @@ async function runFeature(name, options) {
|
|
|
561
986
|
{
|
|
562
987
|
type: "select",
|
|
563
988
|
name: "repo",
|
|
564
|
-
message: "
|
|
989
|
+
message: tr(lang, "cli", "feature.selectRepo"),
|
|
565
990
|
choices: [
|
|
566
991
|
{ title: "Backend (be)", value: "be" },
|
|
567
992
|
{ title: "Frontend (fe)", value: "fe" }
|
|
@@ -593,26 +1018,39 @@ async function runFeature(name, options) {
|
|
|
593
1018
|
}
|
|
594
1019
|
const featureFolderName = `${featureId}-${name}`;
|
|
595
1020
|
const featureDir = path4.join(featuresDir, featureFolderName);
|
|
596
|
-
if (await
|
|
597
|
-
console.error(
|
|
1021
|
+
if (await fs8.pathExists(featureDir)) {
|
|
1022
|
+
console.error(
|
|
1023
|
+
chalk6.red(
|
|
1024
|
+
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
1025
|
+
)
|
|
1026
|
+
);
|
|
598
1027
|
process.exit(1);
|
|
599
1028
|
}
|
|
600
1029
|
const featureBasePath = path4.join(docsDir, "features", "feature-base");
|
|
601
|
-
if (!await
|
|
602
|
-
console.error(
|
|
1030
|
+
if (!await fs8.pathExists(featureBasePath)) {
|
|
1031
|
+
console.error(
|
|
1032
|
+
chalk6.red(
|
|
1033
|
+
tr(lang, "cli", "feature.baseNotFound")
|
|
1034
|
+
)
|
|
1035
|
+
);
|
|
603
1036
|
process.exit(1);
|
|
604
1037
|
}
|
|
605
|
-
await
|
|
1038
|
+
await fs8.copy(featureBasePath, featureDir);
|
|
606
1039
|
const idNumber = featureId.replace("F", "");
|
|
607
1040
|
const repoName = projectType === "fullstack" && repo ? `{{projectName}}-${repo}` : "{{projectName}}";
|
|
608
1041
|
const replacements = {
|
|
1042
|
+
// ko placeholders
|
|
609
1043
|
"{\uAE30\uB2A5\uBA85}": name,
|
|
610
1044
|
"{\uBC88\uD638}": idNumber,
|
|
611
1045
|
"YYYY-MM-DD": (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
612
1046
|
"{be|fe}": repo || "",
|
|
613
|
-
"git-dungeon-{be|fe}": repoName,
|
|
614
1047
|
"{\uC774\uC288\uBC88\uD638}": "",
|
|
615
|
-
"{{description}}": options.desc || ""
|
|
1048
|
+
"{{description}}": options.desc || "",
|
|
1049
|
+
// en placeholders
|
|
1050
|
+
"{feature-name}": name,
|
|
1051
|
+
"{number}": idNumber,
|
|
1052
|
+
"{issue-number}": "",
|
|
1053
|
+
"{{projectName}}-{be|fe}": repoName
|
|
616
1054
|
};
|
|
617
1055
|
if (lang === "en") {
|
|
618
1056
|
replacements["\uAE30\uB2A5 ID"] = "Feature ID";
|
|
@@ -624,12 +1062,24 @@ async function runFeature(name, options) {
|
|
|
624
1062
|
}
|
|
625
1063
|
await replaceInFiles(featureDir, replacements);
|
|
626
1064
|
console.log();
|
|
627
|
-
console.log(
|
|
1065
|
+
console.log(
|
|
1066
|
+
chalk6.green(
|
|
1067
|
+
tr(lang, "cli", "feature.created", { path: featureDir })
|
|
1068
|
+
)
|
|
1069
|
+
);
|
|
628
1070
|
console.log();
|
|
629
|
-
console.log(chalk6.blue("
|
|
630
|
-
console.log(
|
|
631
|
-
|
|
632
|
-
|
|
1071
|
+
console.log(chalk6.blue(tr(lang, "cli", "feature.nextStepsTitle")));
|
|
1072
|
+
console.log(
|
|
1073
|
+
chalk6.gray(
|
|
1074
|
+
tr(lang, "cli", "feature.nextSteps1", { path: featureDir })
|
|
1075
|
+
)
|
|
1076
|
+
);
|
|
1077
|
+
console.log(chalk6.gray(tr(lang, "cli", "feature.nextSteps2")));
|
|
1078
|
+
console.log(
|
|
1079
|
+
chalk6.gray(
|
|
1080
|
+
tr(lang, "cli", "feature.nextSteps3")
|
|
1081
|
+
)
|
|
1082
|
+
);
|
|
633
1083
|
console.log();
|
|
634
1084
|
}
|
|
635
1085
|
async function getNextFeatureId(docsDir, projectType) {
|
|
@@ -643,8 +1093,8 @@ async function getNextFeatureId(docsDir, projectType) {
|
|
|
643
1093
|
scanDirs.push(featuresDir);
|
|
644
1094
|
}
|
|
645
1095
|
for (const dir of scanDirs) {
|
|
646
|
-
if (!await
|
|
647
|
-
const entries = await
|
|
1096
|
+
if (!await fs8.pathExists(dir)) continue;
|
|
1097
|
+
const entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
648
1098
|
for (const entry of entries) {
|
|
649
1099
|
if (!entry.isDirectory()) continue;
|
|
650
1100
|
const match = entry.name.match(/^F(\d+)-/);
|
|
@@ -658,537 +1108,56 @@ async function getNextFeatureId(docsDir, projectType) {
|
|
|
658
1108
|
const width = Math.max(3, String(next).length);
|
|
659
1109
|
return `F${String(next).padStart(width, "0")}`;
|
|
660
1110
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
} catch (error) {
|
|
666
|
-
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
667
|
-
process.exit(1);
|
|
668
|
-
}
|
|
669
|
-
});
|
|
1111
|
+
|
|
1112
|
+
// src/utils/context/steps.ts
|
|
1113
|
+
function isCompletionChecklistDone(feature) {
|
|
1114
|
+
return !!feature.completionChecklist && feature.completionChecklist.total > 0 && feature.completionChecklist.checked === feature.completionChecklist.total;
|
|
670
1115
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const repo = extractSpecValue(specContent, "\uB300\uC0C1 \uB808\uD3EC") || extractSpecValue(specContent, "Target Repo") || (scope ? `{{projectName}}-${scope}` : "{{projectName}}");
|
|
702
|
-
const issue = extractSpecValue(specContent, "\uC774\uC288 \uBC88\uD638") || extractSpecValue(specContent, "Issue Number") || "-";
|
|
703
|
-
const relPath = path4.relative(docsDir, featureDir);
|
|
704
|
-
if (!idMap.has(id)) {
|
|
705
|
-
idMap.set(id, []);
|
|
706
|
-
}
|
|
707
|
-
idMap.get(id).push(relPath);
|
|
708
|
-
const { total, done, doing, todo } = countTasks(tasksContent);
|
|
709
|
-
let status = "TODO";
|
|
710
|
-
if (total > 0 && done === total) {
|
|
711
|
-
status = "DONE";
|
|
712
|
-
} else if (doing > 0) {
|
|
713
|
-
status = "DOING";
|
|
714
|
-
} else if (todo > 0) {
|
|
715
|
-
status = "TODO";
|
|
716
|
-
} else if (total === 0) {
|
|
717
|
-
status = "NO_TASKS";
|
|
1116
|
+
function isImplementationDone(feature) {
|
|
1117
|
+
return feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature);
|
|
1118
|
+
}
|
|
1119
|
+
function isPrMetadataConfigured(feature) {
|
|
1120
|
+
return feature.docs.prFieldExists && feature.docs.prStatusFieldExists;
|
|
1121
|
+
}
|
|
1122
|
+
function isFeatureDone(feature) {
|
|
1123
|
+
return feature.specStatus === "Approved" && feature.planStatus === "Approved" && feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isPrMetadataConfigured(feature) && !!feature.pr.link && feature.pr.status === "Approved";
|
|
1124
|
+
}
|
|
1125
|
+
function getStepDefinitions(lang) {
|
|
1126
|
+
return [
|
|
1127
|
+
{
|
|
1128
|
+
step: 1,
|
|
1129
|
+
name: tr(lang, "steps", "featureFolder"),
|
|
1130
|
+
checklist: { done: () => true }
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
step: 2,
|
|
1134
|
+
name: tr(lang, "steps", "specWrite"),
|
|
1135
|
+
checklist: {
|
|
1136
|
+
done: (f) => f.specStatus === "Review" || f.specStatus === "Approved"
|
|
1137
|
+
},
|
|
1138
|
+
current: {
|
|
1139
|
+
when: (f) => !f.docs.specExists || !f.specStatus || f.specStatus === "Draft",
|
|
1140
|
+
actions: (f) => [
|
|
1141
|
+
{
|
|
1142
|
+
type: "instruction",
|
|
1143
|
+
message: !f.docs.specExists ? tr(lang, "messages", "specCreate") : tr(lang, "messages", "specImprove")
|
|
1144
|
+
}
|
|
1145
|
+
]
|
|
718
1146
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
734
|
-
if (options.strict) {
|
|
735
|
-
const duplicates = [...idMap.entries()].filter(
|
|
736
|
-
([, paths]) => paths.length > 1
|
|
737
|
-
);
|
|
738
|
-
if (duplicates.length > 0) {
|
|
739
|
-
console.error(chalk6.red("\uC911\uBCF5 Feature ID \uBC1C\uACAC:"));
|
|
740
|
-
for (const [id, paths] of duplicates) {
|
|
741
|
-
console.error(chalk6.red(` ${id}:`));
|
|
742
|
-
for (const p of paths) {
|
|
743
|
-
console.error(chalk6.red(` - ${p}`));
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
process.exit(1);
|
|
747
|
-
}
|
|
748
|
-
const unknowns = [...idMap.entries()].filter(([id]) => id === "UNKNOWN");
|
|
749
|
-
if (unknowns.length > 0) {
|
|
750
|
-
console.error(chalk6.red("Feature ID\uAC00 \uC5C6\uB294 \uD56D\uBAA9:"));
|
|
751
|
-
for (const [, paths] of unknowns) {
|
|
752
|
-
for (const p of paths) {
|
|
753
|
-
console.error(chalk6.red(` - ${p}`));
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
process.exit(1);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
features.sort((a, b) => a.id.localeCompare(b.id));
|
|
760
|
-
const header = "| ID | Name | Repo | Issue | Status | Progress | Path |";
|
|
761
|
-
const separator = "| --- | --- | --- | --- | --- | --- | --- |";
|
|
762
|
-
console.log();
|
|
763
|
-
console.log(header);
|
|
764
|
-
console.log(separator);
|
|
765
|
-
for (const f of features) {
|
|
766
|
-
const statusColor = f.status === "DONE" ? chalk6.green : f.status === "DOING" ? chalk6.yellow : chalk6.gray;
|
|
767
|
-
console.log(
|
|
768
|
-
`| ${f.id} | ${f.name} | ${f.repo} | ${f.issue} | ${statusColor(f.status)} | ${f.progress} | ${f.path} |`
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
console.log();
|
|
772
|
-
if (options.write) {
|
|
773
|
-
const outputPath = path4.join(featuresDir, "status.md");
|
|
774
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
775
|
-
const content = [
|
|
776
|
-
"# Feature Status",
|
|
777
|
-
"",
|
|
778
|
-
`- Generated: ${date}`,
|
|
779
|
-
"- Source: `tasks.md`, `spec.md`",
|
|
780
|
-
"",
|
|
781
|
-
header,
|
|
782
|
-
separator,
|
|
783
|
-
...features.map(
|
|
784
|
-
(f) => `| ${f.id} | ${f.name} | ${f.repo} | ${f.issue} | ${f.status} | ${f.progress} | ${f.path} |`
|
|
785
|
-
),
|
|
786
|
-
""
|
|
787
|
-
].join("\n");
|
|
788
|
-
await fs6.writeFile(outputPath, content, "utf-8");
|
|
789
|
-
console.log(chalk6.green(`\u2705 ${outputPath} \uC0DD\uC131 \uC644\uB8CC`));
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
function extractSpecValue(content, key) {
|
|
793
|
-
const regex = new RegExp(`^- \\*\\*${key}\\*\\*:\\s*(.*)$`, "m");
|
|
794
|
-
const match = content.match(regex);
|
|
795
|
-
return match ? match[1].trim() : "";
|
|
796
|
-
}
|
|
797
|
-
function countTasks(content) {
|
|
798
|
-
let total = 0;
|
|
799
|
-
let done = 0;
|
|
800
|
-
let doing = 0;
|
|
801
|
-
let todo = 0;
|
|
802
|
-
const lines = content.split("\n");
|
|
803
|
-
for (const line of lines) {
|
|
804
|
-
const match = line.match(/^- \[([A-Z]+)\]/);
|
|
805
|
-
if (match) {
|
|
806
|
-
total++;
|
|
807
|
-
const status = match[1];
|
|
808
|
-
if (status === "DONE") done++;
|
|
809
|
-
else if (status === "DOING" || status === "REVIEW") doing++;
|
|
810
|
-
else if (status === "TODO") todo++;
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
return { total, done, doing, todo };
|
|
814
|
-
}
|
|
815
|
-
function updateCommand(program2) {
|
|
816
|
-
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--templates", "Update feature-base/ folder only").option("-f, --force", "Force overwrite without confirmation").action(async (options) => {
|
|
817
|
-
try {
|
|
818
|
-
await runUpdate(options);
|
|
819
|
-
} catch (error) {
|
|
820
|
-
if (error instanceof Error && error.message === "canceled") {
|
|
821
|
-
console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
822
|
-
process.exit(0);
|
|
823
|
-
}
|
|
824
|
-
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
825
|
-
process.exit(1);
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
async function runUpdate(options) {
|
|
830
|
-
const cwd = process.cwd();
|
|
831
|
-
const config = await getConfig(cwd);
|
|
832
|
-
if (!config) {
|
|
833
|
-
console.error(
|
|
834
|
-
chalk6.red("docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.")
|
|
835
|
-
);
|
|
836
|
-
process.exit(1);
|
|
837
|
-
}
|
|
838
|
-
const { docsDir, projectType, lang } = config;
|
|
839
|
-
const templatesDir = getTemplatesDir();
|
|
840
|
-
const sourceDir = path4.join(templatesDir, lang, projectType);
|
|
841
|
-
const updateAgents = options.agents || !options.agents && !options.templates;
|
|
842
|
-
const updateTemplates = options.templates || !options.agents && !options.templates;
|
|
843
|
-
console.log(chalk6.blue("\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4..."));
|
|
844
|
-
console.log(chalk6.gray(` - \uC5B8\uC5B4: ${lang}`));
|
|
845
|
-
console.log(chalk6.gray(` - \uD0C0\uC785: ${projectType}`));
|
|
846
|
-
console.log();
|
|
847
|
-
let updatedCount = 0;
|
|
848
|
-
if (updateAgents) {
|
|
849
|
-
console.log(chalk6.blue("\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
|
|
850
|
-
const commonAgents = path4.join(templatesDir, lang, "common", "agents");
|
|
851
|
-
const typeAgents = path4.join(templatesDir, lang, projectType, "agents");
|
|
852
|
-
const targetAgents = path4.join(docsDir, "agents");
|
|
853
|
-
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
854
|
-
const replacements = {
|
|
855
|
-
"{{featurePath}}": featurePath
|
|
856
|
-
};
|
|
857
|
-
if (await fs6.pathExists(commonAgents)) {
|
|
858
|
-
const count = await updateFolder(
|
|
859
|
-
commonAgents,
|
|
860
|
-
targetAgents,
|
|
861
|
-
options.force,
|
|
862
|
-
replacements
|
|
863
|
-
);
|
|
864
|
-
updatedCount += count;
|
|
865
|
-
}
|
|
866
|
-
if (await fs6.pathExists(typeAgents)) {
|
|
867
|
-
const count = await updateFolder(typeAgents, targetAgents, options.force);
|
|
868
|
-
updatedCount += count;
|
|
869
|
-
}
|
|
870
|
-
console.log(chalk6.green(` \u2705 agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
|
|
871
|
-
}
|
|
872
|
-
if (updateTemplates) {
|
|
873
|
-
console.log(chalk6.blue("\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
|
|
874
|
-
const sourceFeatureBase = path4.join(sourceDir, "features", "feature-base");
|
|
875
|
-
const targetFeatureBase = path4.join(docsDir, "features", "feature-base");
|
|
876
|
-
if (await fs6.pathExists(sourceFeatureBase)) {
|
|
877
|
-
const count = await updateFolder(
|
|
878
|
-
sourceFeatureBase,
|
|
879
|
-
targetFeatureBase,
|
|
880
|
-
options.force
|
|
881
|
-
);
|
|
882
|
-
updatedCount += count;
|
|
883
|
-
console.log(chalk6.green(` \u2705 ${count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
console.log();
|
|
887
|
-
console.log(chalk6.green(`\u2705 \uCD1D ${updatedCount}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
|
|
888
|
-
}
|
|
889
|
-
async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
890
|
-
const protectedFiles = /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
|
|
891
|
-
await fs6.ensureDir(targetDir);
|
|
892
|
-
const files = await fs6.readdir(sourceDir);
|
|
893
|
-
let updatedCount = 0;
|
|
894
|
-
for (const file of files) {
|
|
895
|
-
const sourcePath = path4.join(sourceDir, file);
|
|
896
|
-
const targetPath = path4.join(targetDir, file);
|
|
897
|
-
const stat = await fs6.stat(sourcePath);
|
|
898
|
-
if (stat.isFile()) {
|
|
899
|
-
if (protectedFiles.has(file)) {
|
|
900
|
-
continue;
|
|
901
|
-
}
|
|
902
|
-
let sourceContent = await fs6.readFile(sourcePath, "utf-8");
|
|
903
|
-
if (replacements) {
|
|
904
|
-
for (const [key, value] of Object.entries(replacements)) {
|
|
905
|
-
sourceContent = sourceContent.replaceAll(key, value);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
let shouldUpdate = true;
|
|
909
|
-
if (await fs6.pathExists(targetPath)) {
|
|
910
|
-
const targetContent = await fs6.readFile(targetPath, "utf-8");
|
|
911
|
-
if (sourceContent === targetContent) {
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
if (!force) {
|
|
915
|
-
console.log(
|
|
916
|
-
chalk6.yellow(` \u26A0\uFE0F ${file} - \uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)`)
|
|
917
|
-
);
|
|
918
|
-
shouldUpdate = false;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
if (shouldUpdate) {
|
|
922
|
-
await fs6.writeFile(targetPath, sourceContent);
|
|
923
|
-
console.log(chalk6.gray(` \u{1F4C4} ${file} \uC5C5\uB370\uC774\uD2B8`));
|
|
924
|
-
updatedCount++;
|
|
925
|
-
}
|
|
926
|
-
} else if (stat.isDirectory()) {
|
|
927
|
-
const subCount = await updateFolder(
|
|
928
|
-
sourcePath,
|
|
929
|
-
targetPath,
|
|
930
|
-
force,
|
|
931
|
-
replacements
|
|
932
|
-
);
|
|
933
|
-
updatedCount += subCount;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
return updatedCount;
|
|
937
|
-
}
|
|
938
|
-
function configCommand(program2) {
|
|
939
|
-
program2.command("config").description("View or modify project configuration").option("--project-root <path>", "Set project root path").option("--repo <repo>", "Repository type for fullstack: fe | be").action(async (options) => {
|
|
940
|
-
try {
|
|
941
|
-
await runConfig(options);
|
|
942
|
-
} catch (error) {
|
|
943
|
-
if (error instanceof Error && error.message === "canceled") {
|
|
944
|
-
console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
945
|
-
process.exit(0);
|
|
946
|
-
}
|
|
947
|
-
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
948
|
-
process.exit(1);
|
|
949
|
-
}
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
async function runConfig(options) {
|
|
953
|
-
const cwd = process.cwd();
|
|
954
|
-
const config = await getConfig(cwd);
|
|
955
|
-
if (!config) {
|
|
956
|
-
console.log(
|
|
957
|
-
chalk6.red("\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.")
|
|
958
|
-
);
|
|
959
|
-
process.exit(1);
|
|
960
|
-
}
|
|
961
|
-
const configPath = path4.join(config.docsDir, ".lee-spec-kit.json");
|
|
962
|
-
if (!options.projectRoot) {
|
|
963
|
-
console.log();
|
|
964
|
-
console.log(chalk6.blue("\u{1F4CB} \uD604\uC7AC \uC124\uC815:"));
|
|
965
|
-
console.log();
|
|
966
|
-
console.log(chalk6.gray(` \uACBD\uB85C: ${configPath}`));
|
|
967
|
-
console.log();
|
|
968
|
-
const configFile2 = await fs6.readJson(configPath);
|
|
969
|
-
console.log(JSON.stringify(configFile2, null, 2));
|
|
970
|
-
console.log();
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
const configFile = await fs6.readJson(configPath);
|
|
974
|
-
if (configFile.docsRepo !== "standalone") {
|
|
975
|
-
console.log(
|
|
976
|
-
chalk6.yellow("\u26A0\uFE0F projectRoot\uB294 standalone \uBAA8\uB4DC\uC5D0\uC11C\uB9CC \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.")
|
|
977
|
-
);
|
|
978
|
-
return;
|
|
979
|
-
}
|
|
980
|
-
const projectType = configFile.projectType;
|
|
981
|
-
if (projectType === "fullstack") {
|
|
982
|
-
if (!options.repo) {
|
|
983
|
-
const response = await prompts(
|
|
984
|
-
[
|
|
985
|
-
{
|
|
986
|
-
type: "select",
|
|
987
|
-
name: "repo",
|
|
988
|
-
message: "\uC218\uC815\uD560 \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
989
|
-
choices: [
|
|
990
|
-
{ title: "Frontend (fe)", value: "fe" },
|
|
991
|
-
{ title: "Backend (be)", value: "be" }
|
|
992
|
-
]
|
|
993
|
-
}
|
|
994
|
-
],
|
|
995
|
-
{
|
|
996
|
-
onCancel: () => {
|
|
997
|
-
throw new Error("canceled");
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
);
|
|
1001
|
-
options.repo = response.repo;
|
|
1002
|
-
}
|
|
1003
|
-
if (!options.repo || !["fe", "be"].includes(options.repo)) {
|
|
1004
|
-
console.log(
|
|
1005
|
-
chalk6.red(
|
|
1006
|
-
"Fullstack \uD504\uB85C\uC81D\uD2B8\uB294 --repo fe \uB610\uB294 --repo be\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
|
|
1007
|
-
)
|
|
1008
|
-
);
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
const currentRoot = configFile.projectRoot || { fe: "", be: "" };
|
|
1012
|
-
if (typeof currentRoot === "string") {
|
|
1013
|
-
configFile.projectRoot = {
|
|
1014
|
-
fe: options.repo === "fe" ? options.projectRoot : "",
|
|
1015
|
-
be: options.repo === "be" ? options.projectRoot : ""
|
|
1016
|
-
};
|
|
1017
|
-
} else {
|
|
1018
|
-
currentRoot[options.repo] = options.projectRoot;
|
|
1019
|
-
configFile.projectRoot = currentRoot;
|
|
1020
|
-
}
|
|
1021
|
-
console.log(
|
|
1022
|
-
chalk6.green(
|
|
1023
|
-
`\u2705 ${options.repo.toUpperCase()} projectRoot \uC124\uC815 \uC644\uB8CC: ${options.projectRoot}`
|
|
1024
|
-
)
|
|
1025
|
-
);
|
|
1026
|
-
} else {
|
|
1027
|
-
configFile.projectRoot = options.projectRoot;
|
|
1028
|
-
console.log(
|
|
1029
|
-
chalk6.green(`\u2705 projectRoot \uC124\uC815 \uC644\uB8CC: ${options.projectRoot}`)
|
|
1030
|
-
);
|
|
1031
|
-
}
|
|
1032
|
-
await fs6.writeJson(configPath, configFile, { spaces: 2 });
|
|
1033
|
-
console.log();
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// src/utils/context/i18n.ts
|
|
1037
|
-
function formatTemplate(template, vars) {
|
|
1038
|
-
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
1039
|
-
const value = vars[key];
|
|
1040
|
-
return value === void 0 ? `{${key}}` : String(value);
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
var I18N = {
|
|
1044
|
-
ko: {
|
|
1045
|
-
steps: {
|
|
1046
|
-
featureFolder: "Feature \uD3F4\uB354 \uC0DD\uC131",
|
|
1047
|
-
specWrite: "spec.md \uC791\uC131",
|
|
1048
|
-
specApprove: "spec.md \uC2B9\uC778",
|
|
1049
|
-
planWrite: "plan.md \uC791\uC131",
|
|
1050
|
-
planApprove: "plan.md \uC2B9\uC778",
|
|
1051
|
-
tasksWrite: "tasks.md \uC791\uC131",
|
|
1052
|
-
docsCommitPlanning: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
|
|
1053
|
-
issueCreate: "GitHub Issue \uC0DD\uC131",
|
|
1054
|
-
branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
|
|
1055
|
-
tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
|
|
1056
|
-
prCreate: "PR \uC0DD\uC131",
|
|
1057
|
-
codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
|
|
1058
|
-
featureDone: "Feature \uC644\uB8CC"
|
|
1059
|
-
},
|
|
1060
|
-
messages: {
|
|
1061
|
-
specCreate: "spec.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/spec.md \uCC38\uACE0)",
|
|
1062
|
-
specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
1063
|
-
specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
1064
|
-
planCreate: "plan.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/plan.md \uCC38\uACE0)",
|
|
1065
|
-
planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
1066
|
-
planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
1067
|
-
tasksCreate: "tasks.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694. (features/feature-base/tasks.md \uCC38\uACE0)",
|
|
1068
|
-
tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
|
|
1069
|
-
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
|
|
1070
|
-
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. (skills/create-issue.md \uCC38\uACE0)",
|
|
1071
|
-
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
|
|
1072
|
-
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
1073
|
-
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
1074
|
-
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.',
|
|
1075
|
-
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})",
|
|
1076
|
-
finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
|
|
1077
|
-
startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
|
|
1078
|
-
checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)",
|
|
1079
|
-
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? (OK \uD544\uC694)",
|
|
1080
|
-
prCreate: "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694. (skills/create-pr.md \uCC38\uACE0)",
|
|
1081
|
-
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)",
|
|
1082
|
-
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.",
|
|
1083
|
-
featureDone: "PR\uC774 Approved\uC774\uACE0 \uBAA8\uB4E0 \uD0DC\uC2A4\uD06C/\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uB8CC \uC0C1\uD0DC\uC785\uB2C8\uB2E4.",
|
|
1084
|
-
fallbackRerunContext: "\uC0C1\uD0DC\uB97C \uD310\uBCC4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694."
|
|
1085
|
-
},
|
|
1086
|
-
warnings: {
|
|
1087
|
-
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.)",
|
|
1088
|
-
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)",
|
|
1089
|
-
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."
|
|
1090
|
-
}
|
|
1091
|
-
},
|
|
1092
|
-
en: {
|
|
1093
|
-
steps: {
|
|
1094
|
-
featureFolder: "Create feature folder",
|
|
1095
|
-
specWrite: "Write spec.md",
|
|
1096
|
-
specApprove: "Approve spec.md",
|
|
1097
|
-
planWrite: "Write plan.md",
|
|
1098
|
-
planApprove: "Approve plan.md",
|
|
1099
|
-
tasksWrite: "Write tasks.md",
|
|
1100
|
-
docsCommitPlanning: "Commit docs (sync)",
|
|
1101
|
-
issueCreate: "Create GitHub Issue",
|
|
1102
|
-
branchCreate: "Create branch",
|
|
1103
|
-
tasksExecute: "Execute tasks",
|
|
1104
|
-
prCreate: "Create PR",
|
|
1105
|
-
codeReview: "Code review",
|
|
1106
|
-
featureDone: "Feature done"
|
|
1107
|
-
},
|
|
1108
|
-
messages: {
|
|
1109
|
-
specCreate: "Copy the spec.md template and write it. (See features/feature-base/spec.md)",
|
|
1110
|
-
specImprove: "Improve spec.md and set Status to Review.",
|
|
1111
|
-
specApproval: "Share spec.md with the user and get approval (OK).",
|
|
1112
|
-
planCreate: "Copy the plan.md template and write it. (See features/feature-base/plan.md)",
|
|
1113
|
-
planImprove: "Improve plan.md and set Status to Review.",
|
|
1114
|
-
planApproval: "Share plan.md with the user and get approval (OK).",
|
|
1115
|
-
tasksCreate: "Copy the tasks.md template and write tasks. (See features/feature-base/tasks.md)",
|
|
1116
|
-
tasksNeedAtLeastOne: "Add at least one task to tasks.md.",
|
|
1117
|
-
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
|
|
1118
|
-
issueCreateAndWrite: "Create a GitHub Issue, then fill in the issue number in spec.md/tasks.md and prepare to commit docs. (See skills/create-issue.md)",
|
|
1119
|
-
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
|
|
1120
|
-
standaloneNeedsProjectRoot: "In standalone mode, projectRoot is required. (npx lee-spec-kit config --project-root ...)",
|
|
1121
|
-
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
1122
|
-
tasksAllDoneButNoChecklist: 'All tasks are DONE but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
|
|
1123
|
-
tasksAllDoneButChecklist: "All tasks are DONE but the completion checklist is not fully checked. ({checked}/{total})",
|
|
1124
|
-
finishDoingTask: 'Finish the active DOING/REVIEW task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
|
|
1125
|
-
startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
|
|
1126
|
-
checkTaskStatuses: "Check task statuses. ({done}/{total}) (See skills/execute-task.md)",
|
|
1127
|
-
prLegacyAsk: "Legacy tasks.md format detected (missing PR/PR Status fields). Update to the latest format? (OK required)",
|
|
1128
|
-
prCreate: "Create a PR and record the PR link in tasks.md. (See skills/create-pr.md)",
|
|
1129
|
-
prResolveReview: "Resolve review comments and update PR status. (PR Status: Review \u2192 Approved)",
|
|
1130
|
-
prRequestReview: "Request reviews and update PR status to Review.",
|
|
1131
|
-
featureDone: "PR is Approved and all tasks/completion criteria are satisfied. This feature is done.",
|
|
1132
|
-
fallbackRerunContext: "Unable to determine current state. Verify docs and run context again."
|
|
1133
|
-
},
|
|
1134
|
-
warnings: {
|
|
1135
|
-
projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
|
|
1136
|
-
docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
|
|
1137
|
-
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps."
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
};
|
|
1141
|
-
function tr(lang, category, key, vars = {}) {
|
|
1142
|
-
const template = I18N[lang][category][key] ?? I18N.ko[category][key] ?? `${category}.${key}`;
|
|
1143
|
-
return formatTemplate(template, vars);
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
// src/utils/context/steps.ts
|
|
1147
|
-
function isCompletionChecklistDone(feature) {
|
|
1148
|
-
return !!feature.completionChecklist && feature.completionChecklist.total > 0 && feature.completionChecklist.checked === feature.completionChecklist.total;
|
|
1149
|
-
}
|
|
1150
|
-
function isPrMetadataConfigured(feature) {
|
|
1151
|
-
return feature.docs.prFieldExists && feature.docs.prStatusFieldExists;
|
|
1152
|
-
}
|
|
1153
|
-
function isFeatureDone(feature) {
|
|
1154
|
-
return feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isPrMetadataConfigured(feature) && !!feature.pr.link && feature.pr.status === "Approved";
|
|
1155
|
-
}
|
|
1156
|
-
function getStepDefinitions(lang) {
|
|
1157
|
-
return [
|
|
1158
|
-
{
|
|
1159
|
-
step: 1,
|
|
1160
|
-
name: tr(lang, "steps", "featureFolder"),
|
|
1161
|
-
checklist: { done: () => true }
|
|
1162
|
-
},
|
|
1163
|
-
{
|
|
1164
|
-
step: 2,
|
|
1165
|
-
name: tr(lang, "steps", "specWrite"),
|
|
1166
|
-
checklist: {
|
|
1167
|
-
done: (f) => f.specStatus === "Review" || f.specStatus === "Approved"
|
|
1168
|
-
},
|
|
1169
|
-
current: {
|
|
1170
|
-
when: (f) => !f.docs.specExists || !f.specStatus || f.specStatus === "Draft",
|
|
1171
|
-
actions: (f) => [
|
|
1172
|
-
{
|
|
1173
|
-
type: "instruction",
|
|
1174
|
-
message: !f.docs.specExists ? tr(lang, "messages", "specCreate") : tr(lang, "messages", "specImprove")
|
|
1175
|
-
}
|
|
1176
|
-
]
|
|
1177
|
-
}
|
|
1178
|
-
},
|
|
1179
|
-
{
|
|
1180
|
-
step: 3,
|
|
1181
|
-
name: tr(lang, "steps", "specApprove"),
|
|
1182
|
-
checklist: { done: (f) => f.specStatus === "Approved" },
|
|
1183
|
-
current: {
|
|
1184
|
-
when: (f) => f.specStatus === "Review",
|
|
1185
|
-
actions: () => [
|
|
1186
|
-
{
|
|
1187
|
-
type: "instruction",
|
|
1188
|
-
requiresUserOk: true,
|
|
1189
|
-
message: tr(lang, "messages", "specApproval")
|
|
1190
|
-
}
|
|
1191
|
-
]
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
step: 3,
|
|
1150
|
+
name: tr(lang, "steps", "specApprove"),
|
|
1151
|
+
checklist: { done: (f) => f.specStatus === "Approved" },
|
|
1152
|
+
current: {
|
|
1153
|
+
when: (f) => f.specStatus === "Review",
|
|
1154
|
+
actions: () => [
|
|
1155
|
+
{
|
|
1156
|
+
type: "instruction",
|
|
1157
|
+
requiresUserOk: true,
|
|
1158
|
+
message: tr(lang, "messages", "specApproval")
|
|
1159
|
+
}
|
|
1160
|
+
]
|
|
1192
1161
|
}
|
|
1193
1162
|
},
|
|
1194
1163
|
{
|
|
@@ -1226,21 +1195,12 @@ function getStepDefinitions(lang) {
|
|
|
1226
1195
|
step: 6,
|
|
1227
1196
|
name: tr(lang, "steps", "tasksWrite"),
|
|
1228
1197
|
checklist: {
|
|
1229
|
-
done: (f) => f.docs.tasksExists && f.tasks.total > 0
|
|
1198
|
+
done: (f) => f.docs.tasksExists && f.tasks.total > 0,
|
|
1230
1199
|
detail: (f) => f.tasks.total > 0 ? `(${f.tasks.total})` : ""
|
|
1231
1200
|
},
|
|
1232
1201
|
current: {
|
|
1233
|
-
when: (f) => f.planStatus === "Approved" && (!f.docs.tasksExists || f.tasks.total === 0
|
|
1202
|
+
when: (f) => f.planStatus === "Approved" && (!f.docs.tasksExists || f.tasks.total === 0),
|
|
1234
1203
|
actions: (f) => {
|
|
1235
|
-
if (f.docs.tasksExists && f.tasks.total > 0 && (!f.docs.prFieldExists || !f.docs.prStatusFieldExists)) {
|
|
1236
|
-
return [
|
|
1237
|
-
{
|
|
1238
|
-
type: "instruction",
|
|
1239
|
-
requiresUserOk: true,
|
|
1240
|
-
message: tr(lang, "messages", "prLegacyAsk")
|
|
1241
|
-
}
|
|
1242
|
-
];
|
|
1243
|
-
}
|
|
1244
1204
|
if (!f.docs.tasksExists) {
|
|
1245
1205
|
return [
|
|
1246
1206
|
{
|
|
@@ -1262,10 +1222,10 @@ function getStepDefinitions(lang) {
|
|
|
1262
1222
|
step: 7,
|
|
1263
1223
|
name: tr(lang, "steps", "docsCommitPlanning"),
|
|
1264
1224
|
checklist: {
|
|
1265
|
-
done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" &&
|
|
1225
|
+
done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && f.git.docsEverCommitted
|
|
1266
1226
|
},
|
|
1267
1227
|
current: {
|
|
1268
|
-
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.activeTask && f.git.docsHasUncommittedChanges,
|
|
1228
|
+
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.activeTask && !f.git.docsEverCommitted && f.git.docsHasUncommittedChanges,
|
|
1269
1229
|
actions: (f) => {
|
|
1270
1230
|
if (f.issueNumber) {
|
|
1271
1231
|
return [
|
|
@@ -1323,7 +1283,7 @@ function getStepDefinitions(lang) {
|
|
|
1323
1283
|
name: tr(lang, "steps", "branchCreate"),
|
|
1324
1284
|
checklist: { done: (f) => f.git.onExpectedBranch },
|
|
1325
1285
|
current: {
|
|
1326
|
-
when: (f) => !!f.issueNumber && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
|
|
1286
|
+
when: (f) => !!f.issueNumber && !isImplementationDone(f) && !isFeatureDone(f) && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
|
|
1327
1287
|
actions: (f) => {
|
|
1328
1288
|
if (!f.git.projectBranchAvailable || !f.git.projectGitCwd) {
|
|
1329
1289
|
return [
|
|
@@ -1444,6 +1404,15 @@ function getStepDefinitions(lang) {
|
|
|
1444
1404
|
current: {
|
|
1445
1405
|
when: (f) => isPrMetadataConfigured(f) && !!f.pr.link && f.pr.status !== "Approved",
|
|
1446
1406
|
actions: (f) => {
|
|
1407
|
+
if (!f.pr.status) {
|
|
1408
|
+
return [
|
|
1409
|
+
{
|
|
1410
|
+
type: "instruction",
|
|
1411
|
+
requiresUserOk: true,
|
|
1412
|
+
message: tr(lang, "messages", "prFillStatus")
|
|
1413
|
+
}
|
|
1414
|
+
];
|
|
1415
|
+
}
|
|
1447
1416
|
if (f.pr.status === "Review") {
|
|
1448
1417
|
return [
|
|
1449
1418
|
{
|
|
@@ -1532,6 +1501,18 @@ function getGitStatusPorcelain(cwd, relativePaths) {
|
|
|
1532
1501
|
return void 0;
|
|
1533
1502
|
}
|
|
1534
1503
|
}
|
|
1504
|
+
function getLastCommitForPath(cwd, relativePath) {
|
|
1505
|
+
try {
|
|
1506
|
+
const out = execSync(`git rev-list -n 1 HEAD -- "${relativePath}"`, {
|
|
1507
|
+
cwd,
|
|
1508
|
+
encoding: "utf-8",
|
|
1509
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1510
|
+
}).trim();
|
|
1511
|
+
return out || void 0;
|
|
1512
|
+
} catch {
|
|
1513
|
+
return void 0;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1535
1516
|
function getGitTopLevel(cwd) {
|
|
1536
1517
|
try {
|
|
1537
1518
|
return execSync("git rev-parse --show-toplevel", {
|
|
@@ -1589,7 +1570,7 @@ function isExpectedFeatureBranch(branchName, issueNumber, slug, folderName) {
|
|
|
1589
1570
|
function escapeRegExp(value) {
|
|
1590
1571
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1591
1572
|
}
|
|
1592
|
-
function
|
|
1573
|
+
function extractSpecValue(content, key) {
|
|
1593
1574
|
const regex = new RegExp(
|
|
1594
1575
|
`^\\s*-\\s*\\*\\*${escapeRegExp(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
1595
1576
|
"m"
|
|
@@ -1599,7 +1580,7 @@ function extractSpecValue2(content, key) {
|
|
|
1599
1580
|
}
|
|
1600
1581
|
function extractFirstSpecValue(content, keys) {
|
|
1601
1582
|
for (const key of keys) {
|
|
1602
|
-
const value =
|
|
1583
|
+
const value = extractSpecValue(content, key);
|
|
1603
1584
|
if (value) return value;
|
|
1604
1585
|
}
|
|
1605
1586
|
return void 0;
|
|
@@ -1669,6 +1650,12 @@ function parseCompletionChecklist(content) {
|
|
|
1669
1650
|
}
|
|
1670
1651
|
return total > 0 ? { total, checked } : void 0;
|
|
1671
1652
|
}
|
|
1653
|
+
function isCompletionChecklistDone2(feature) {
|
|
1654
|
+
return !!feature.completionChecklist && feature.completionChecklist.total > 0 && feature.completionChecklist.checked === feature.completionChecklist.total;
|
|
1655
|
+
}
|
|
1656
|
+
function isPrMetadataConfigured2(feature) {
|
|
1657
|
+
return feature.docs.prFieldExists && feature.docs.prStatusFieldExists;
|
|
1658
|
+
}
|
|
1672
1659
|
async function parseFeature(featurePath, type, context, options) {
|
|
1673
1660
|
const lang = options.lang;
|
|
1674
1661
|
const folderName = path4.basename(featurePath);
|
|
@@ -1680,22 +1667,22 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
1680
1667
|
const tasksPath = path4.join(featurePath, "tasks.md");
|
|
1681
1668
|
let specStatus;
|
|
1682
1669
|
let issueNumber;
|
|
1683
|
-
const specExists = await
|
|
1670
|
+
const specExists = await fs8.pathExists(specPath);
|
|
1684
1671
|
if (specExists) {
|
|
1685
|
-
const content = await
|
|
1672
|
+
const content = await fs8.readFile(specPath, "utf-8");
|
|
1686
1673
|
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
1687
1674
|
specStatus = parseDocStatus(statusValue);
|
|
1688
1675
|
const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
|
|
1689
1676
|
issueNumber = parseIssueNumber(issueValue);
|
|
1690
1677
|
}
|
|
1691
1678
|
let planStatus;
|
|
1692
|
-
const planExists = await
|
|
1679
|
+
const planExists = await fs8.pathExists(planPath);
|
|
1693
1680
|
if (planExists) {
|
|
1694
|
-
const content = await
|
|
1681
|
+
const content = await fs8.readFile(planPath, "utf-8");
|
|
1695
1682
|
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
1696
1683
|
planStatus = parseDocStatus(statusValue);
|
|
1697
1684
|
}
|
|
1698
|
-
const tasksExists = await
|
|
1685
|
+
const tasksExists = await fs8.pathExists(tasksPath);
|
|
1699
1686
|
const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
|
|
1700
1687
|
let activeTask;
|
|
1701
1688
|
let nextTodoTask;
|
|
@@ -1705,7 +1692,7 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
1705
1692
|
let prFieldExists = false;
|
|
1706
1693
|
let prStatusFieldExists = false;
|
|
1707
1694
|
if (tasksExists) {
|
|
1708
|
-
const content = await
|
|
1695
|
+
const content = await fs8.readFile(tasksPath, "utf-8");
|
|
1709
1696
|
const { summary, activeTask: active, nextTodoTask: nextTodo } = parseTasks(content);
|
|
1710
1697
|
tasksSummary.total = summary.total;
|
|
1711
1698
|
tasksSummary.todo = summary.todo;
|
|
@@ -1738,18 +1725,47 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
1738
1725
|
const relativeFeaturePathFromDocs = path4.relative(context.docsDir, featurePath);
|
|
1739
1726
|
const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [relativeFeaturePathFromDocs]);
|
|
1740
1727
|
const docsHasUncommittedChanges = docsStatus === void 0 ? true : docsStatus.trim().length > 0;
|
|
1728
|
+
const docsLastCommit = getLastCommitForPath(
|
|
1729
|
+
context.docsGitCwd,
|
|
1730
|
+
relativeFeaturePathFromDocs
|
|
1731
|
+
);
|
|
1732
|
+
const docsEverCommitted = !!docsLastCommit;
|
|
1741
1733
|
if (docsStatus === void 0) {
|
|
1742
1734
|
warnings.push(tr(lang, "warnings", "docsGitUnavailable"));
|
|
1743
1735
|
}
|
|
1744
1736
|
if (tasksExists && (!prFieldExists || !prStatusFieldExists)) {
|
|
1745
1737
|
warnings.push(tr(lang, "warnings", "legacyTasksPrFields"));
|
|
1746
1738
|
}
|
|
1739
|
+
if (docsEverCommitted && docsHasUncommittedChanges) {
|
|
1740
|
+
warnings.push(tr(lang, "warnings", "docsUncommittedChanges"));
|
|
1741
|
+
}
|
|
1742
|
+
const implementationDone = tasksExists && tasksSummary.total > 0 && tasksSummary.total === tasksSummary.done && isCompletionChecklistDone2({ completionChecklist });
|
|
1743
|
+
const workflowDone = implementationDone && specStatus === "Approved" && planStatus === "Approved" && isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink && prStatus === "Approved";
|
|
1744
|
+
if (implementationDone && !workflowDone) {
|
|
1745
|
+
if (specStatus !== "Approved") {
|
|
1746
|
+
warnings.push(tr(lang, "warnings", "workflowSpecNotApproved"));
|
|
1747
|
+
}
|
|
1748
|
+
if (planStatus !== "Approved") {
|
|
1749
|
+
warnings.push(tr(lang, "warnings", "workflowPlanNotApproved"));
|
|
1750
|
+
}
|
|
1751
|
+
if (prFieldExists && prStatusFieldExists) {
|
|
1752
|
+
if (!prLink) warnings.push(tr(lang, "warnings", "workflowPrLinkMissing"));
|
|
1753
|
+
if (!prStatus) warnings.push(tr(lang, "warnings", "workflowPrStatusMissing"));
|
|
1754
|
+
if (prStatus && prStatus !== "Approved") {
|
|
1755
|
+
warnings.push(tr(lang, "warnings", "workflowPrStatusNotApproved"));
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1747
1759
|
const featureState = {
|
|
1748
1760
|
id,
|
|
1749
1761
|
slug,
|
|
1750
1762
|
folderName,
|
|
1751
1763
|
type,
|
|
1752
1764
|
path: featurePath,
|
|
1765
|
+
completion: {
|
|
1766
|
+
implementationDone,
|
|
1767
|
+
workflowDone
|
|
1768
|
+
},
|
|
1753
1769
|
issueNumber,
|
|
1754
1770
|
specStatus,
|
|
1755
1771
|
planStatus,
|
|
@@ -1765,6 +1781,7 @@ async function parseFeature(featurePath, type, context, options) {
|
|
|
1765
1781
|
docsGitCwd: context.docsGitCwd,
|
|
1766
1782
|
projectGitCwd: context.projectGitCwd,
|
|
1767
1783
|
onExpectedBranch,
|
|
1784
|
+
docsEverCommitted,
|
|
1768
1785
|
docsHasUncommittedChanges
|
|
1769
1786
|
},
|
|
1770
1787
|
docs: {
|
|
@@ -1815,7 +1832,7 @@ async function scanFeatures(config) {
|
|
|
1815
1832
|
ignore: ["**/feature-base/**"]
|
|
1816
1833
|
});
|
|
1817
1834
|
for (const dir of featureDirs) {
|
|
1818
|
-
if ((await
|
|
1835
|
+
if ((await fs8.stat(dir)).isDirectory()) {
|
|
1819
1836
|
features.push(
|
|
1820
1837
|
await parseFeature(
|
|
1821
1838
|
dir,
|
|
@@ -1837,7 +1854,7 @@ async function scanFeatures(config) {
|
|
|
1837
1854
|
const feDirs = await glob("features/fe/*/", { cwd: config.docsDir, absolute: true });
|
|
1838
1855
|
const beDirs = await glob("features/be/*/", { cwd: config.docsDir, absolute: true });
|
|
1839
1856
|
for (const dir of feDirs) {
|
|
1840
|
-
if ((await
|
|
1857
|
+
if ((await fs8.stat(dir)).isDirectory()) {
|
|
1841
1858
|
features.push(
|
|
1842
1859
|
await parseFeature(
|
|
1843
1860
|
dir,
|
|
@@ -1855,39 +1872,453 @@ async function scanFeatures(config) {
|
|
|
1855
1872
|
);
|
|
1856
1873
|
}
|
|
1857
1874
|
}
|
|
1858
|
-
for (const dir of beDirs) {
|
|
1859
|
-
if ((await
|
|
1860
|
-
features.push(
|
|
1861
|
-
await parseFeature(
|
|
1862
|
-
dir,
|
|
1863
|
-
"be",
|
|
1864
|
-
{
|
|
1865
|
-
projectBranch: projectBranches.be,
|
|
1866
|
-
docsBranch,
|
|
1867
|
-
docsGitCwd: config.docsDir,
|
|
1868
|
-
projectGitCwd: beProject?.cwd ?? void 0,
|
|
1869
|
-
docsDir: config.docsDir,
|
|
1870
|
-
projectBranchAvailable: Boolean(beProject?.cwd)
|
|
1871
|
-
},
|
|
1872
|
-
{ lang: config.lang, stepDefinitions }
|
|
1873
|
-
)
|
|
1874
|
-
);
|
|
1875
|
+
for (const dir of beDirs) {
|
|
1876
|
+
if ((await fs8.stat(dir)).isDirectory()) {
|
|
1877
|
+
features.push(
|
|
1878
|
+
await parseFeature(
|
|
1879
|
+
dir,
|
|
1880
|
+
"be",
|
|
1881
|
+
{
|
|
1882
|
+
projectBranch: projectBranches.be,
|
|
1883
|
+
docsBranch,
|
|
1884
|
+
docsGitCwd: config.docsDir,
|
|
1885
|
+
projectGitCwd: beProject?.cwd ?? void 0,
|
|
1886
|
+
docsDir: config.docsDir,
|
|
1887
|
+
projectBranchAvailable: Boolean(beProject?.cwd)
|
|
1888
|
+
},
|
|
1889
|
+
{ lang: config.lang, stepDefinitions }
|
|
1890
|
+
)
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return {
|
|
1896
|
+
features,
|
|
1897
|
+
branches: {
|
|
1898
|
+
docs: docsBranch,
|
|
1899
|
+
project: config.projectType === "single" ? { single: projectBranches.single } : { fe: projectBranches.fe, be: projectBranches.be }
|
|
1900
|
+
},
|
|
1901
|
+
warnings
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
// src/commands/status.ts
|
|
1906
|
+
function statusCommand(program2) {
|
|
1907
|
+
program2.command("status").description("Show feature status").option("-w, --write", "Write status.md file").option("-s, --strict", "Fail on missing/duplicate feature IDs").action(async (options) => {
|
|
1908
|
+
try {
|
|
1909
|
+
await runStatus(options);
|
|
1910
|
+
} catch (error) {
|
|
1911
|
+
console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
|
|
1912
|
+
process.exit(1);
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
async function runStatus(options) {
|
|
1917
|
+
const cwd = process.cwd();
|
|
1918
|
+
const config = await getConfig(cwd);
|
|
1919
|
+
if (!config) {
|
|
1920
|
+
console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")));
|
|
1921
|
+
console.error(
|
|
1922
|
+
chalk6.red(
|
|
1923
|
+
tr(DEFAULT_LANG, "cli", "common.docsNotFound")
|
|
1924
|
+
)
|
|
1925
|
+
);
|
|
1926
|
+
process.exit(1);
|
|
1927
|
+
}
|
|
1928
|
+
const { docsDir, projectType, projectName, lang } = config;
|
|
1929
|
+
const featuresDir = path4.join(docsDir, "features");
|
|
1930
|
+
const scan = await scanFeatures(config);
|
|
1931
|
+
const features = [];
|
|
1932
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
1933
|
+
for (const f of scan.features) {
|
|
1934
|
+
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
1935
|
+
const id = f.id || "UNKNOWN";
|
|
1936
|
+
const name = await getFeatureNameFromSpec(f.path, f.slug, f.folderName);
|
|
1937
|
+
const repo = projectType === "fullstack" ? `${projectName ?? "{{projectName}}"}-${f.type === "single" ? "" : f.type}`.replace(
|
|
1938
|
+
/-$/,
|
|
1939
|
+
""
|
|
1940
|
+
) : projectName ?? "{{projectName}}";
|
|
1941
|
+
const issue = f.issueNumber ? `#${f.issueNumber}` : "-";
|
|
1942
|
+
const relPath = path4.relative(docsDir, f.path);
|
|
1943
|
+
if (!idMap.has(id)) idMap.set(id, []);
|
|
1944
|
+
idMap.get(id).push(relPath);
|
|
1945
|
+
const total = f.tasks.total;
|
|
1946
|
+
const done = f.tasks.done;
|
|
1947
|
+
const doing = f.tasks.doing;
|
|
1948
|
+
const todo = f.tasks.todo;
|
|
1949
|
+
let status = "TODO";
|
|
1950
|
+
if (total > 0 && done === total) status = "DONE";
|
|
1951
|
+
else if (doing > 0) status = "DOING";
|
|
1952
|
+
else if (todo > 0) status = "TODO";
|
|
1953
|
+
else if (total === 0) status = "NO_TASKS";
|
|
1954
|
+
features.push({
|
|
1955
|
+
id,
|
|
1956
|
+
name,
|
|
1957
|
+
repo,
|
|
1958
|
+
issue,
|
|
1959
|
+
status,
|
|
1960
|
+
progress: `${done}/${total}`,
|
|
1961
|
+
path: relPath
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
if (features.length === 0) {
|
|
1965
|
+
console.log(chalk6.yellow(tr(lang, "cli", "status.noFeatures")));
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (options.strict) {
|
|
1969
|
+
const duplicates = [...idMap.entries()].filter(
|
|
1970
|
+
([, paths]) => paths.length > 1
|
|
1971
|
+
);
|
|
1972
|
+
if (duplicates.length > 0) {
|
|
1973
|
+
console.error(chalk6.red(tr(lang, "cli", "status.duplicateIds")));
|
|
1974
|
+
for (const [id, paths] of duplicates) {
|
|
1975
|
+
console.error(chalk6.red(` ${id}:`));
|
|
1976
|
+
for (const p of paths) {
|
|
1977
|
+
console.error(chalk6.red(` - ${p}`));
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
process.exit(1);
|
|
1981
|
+
}
|
|
1982
|
+
const unknowns = [...idMap.entries()].filter(([id]) => id === "UNKNOWN");
|
|
1983
|
+
if (unknowns.length > 0) {
|
|
1984
|
+
console.error(chalk6.red(tr(lang, "cli", "status.missingIds")));
|
|
1985
|
+
for (const [, paths] of unknowns) {
|
|
1986
|
+
for (const p of paths) {
|
|
1987
|
+
console.error(chalk6.red(` - ${p}`));
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
process.exit(1);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
features.sort((a, b) => a.id.localeCompare(b.id));
|
|
1994
|
+
const header = "| ID | Name | Repo | Issue | Status | Progress | Path |";
|
|
1995
|
+
const separator = "| --- | --- | --- | --- | --- | --- | --- |";
|
|
1996
|
+
console.log();
|
|
1997
|
+
console.log(header);
|
|
1998
|
+
console.log(separator);
|
|
1999
|
+
for (const f of features) {
|
|
2000
|
+
const statusColor = f.status === "DONE" ? chalk6.green : f.status === "DOING" ? chalk6.yellow : chalk6.gray;
|
|
2001
|
+
console.log(
|
|
2002
|
+
`| ${f.id} | ${f.name} | ${f.repo} | ${f.issue} | ${statusColor(f.status)} | ${f.progress} | ${f.path} |`
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
console.log();
|
|
2006
|
+
if (options.write) {
|
|
2007
|
+
const outputPath = path4.join(featuresDir, "status.md");
|
|
2008
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2009
|
+
const content = [
|
|
2010
|
+
"# Feature Status",
|
|
2011
|
+
"",
|
|
2012
|
+
`- Generated: ${date}`,
|
|
2013
|
+
"- Source: `tasks.md`, `spec.md`",
|
|
2014
|
+
"",
|
|
2015
|
+
header,
|
|
2016
|
+
separator,
|
|
2017
|
+
...features.map(
|
|
2018
|
+
(f) => `| ${f.id} | ${f.name} | ${f.repo} | ${f.issue} | ${f.status} | ${f.progress} | ${f.path} |`
|
|
2019
|
+
),
|
|
2020
|
+
""
|
|
2021
|
+
].join("\n");
|
|
2022
|
+
await fs8.writeFile(outputPath, content, "utf-8");
|
|
2023
|
+
console.log(
|
|
2024
|
+
chalk6.green(
|
|
2025
|
+
tr(lang, "cli", "status.wrote", { path: outputPath })
|
|
2026
|
+
)
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
function escapeRegExp2(value) {
|
|
2031
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2032
|
+
}
|
|
2033
|
+
async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
|
|
2034
|
+
try {
|
|
2035
|
+
const specPath = path4.join(featureDir, "spec.md");
|
|
2036
|
+
if (!await fs8.pathExists(specPath)) return fallbackSlug;
|
|
2037
|
+
const content = await fs8.readFile(specPath, "utf-8");
|
|
2038
|
+
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
2039
|
+
for (const key of keys) {
|
|
2040
|
+
const regex = new RegExp(
|
|
2041
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp2(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
2042
|
+
"m"
|
|
2043
|
+
);
|
|
2044
|
+
const match = content.match(regex);
|
|
2045
|
+
const value = match?.[1]?.trim();
|
|
2046
|
+
if (value) return value;
|
|
2047
|
+
}
|
|
2048
|
+
} catch {
|
|
2049
|
+
}
|
|
2050
|
+
return fallbackSlug || fallbackFolderName;
|
|
2051
|
+
}
|
|
2052
|
+
function updateCommand(program2) {
|
|
2053
|
+
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--templates", "Update feature-base/ folder only").option("-f, --force", "Force overwrite without confirmation").action(async (options) => {
|
|
2054
|
+
try {
|
|
2055
|
+
await runUpdate(options);
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
if (error instanceof Error && error.message === "canceled") {
|
|
2058
|
+
const config = await getConfig(process.cwd());
|
|
2059
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
2060
|
+
console.log(chalk6.yellow(`
|
|
2061
|
+
${tr(lang, "cli", "common.canceled")}`));
|
|
2062
|
+
process.exit(0);
|
|
2063
|
+
}
|
|
2064
|
+
console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
|
|
2065
|
+
process.exit(1);
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
async function runUpdate(options) {
|
|
2070
|
+
const cwd = process.cwd();
|
|
2071
|
+
const config = await getConfig(cwd);
|
|
2072
|
+
if (!config) {
|
|
2073
|
+
console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")));
|
|
2074
|
+
console.error(
|
|
2075
|
+
chalk6.red(
|
|
2076
|
+
tr(DEFAULT_LANG, "cli", "common.docsNotFound")
|
|
2077
|
+
)
|
|
2078
|
+
);
|
|
2079
|
+
process.exit(1);
|
|
2080
|
+
}
|
|
2081
|
+
const { docsDir, projectType, lang } = config;
|
|
2082
|
+
const templatesDir = getTemplatesDir();
|
|
2083
|
+
const sourceDir = path4.join(templatesDir, lang, projectType);
|
|
2084
|
+
const updateAgents = options.agents || !options.agents && !options.templates;
|
|
2085
|
+
const updateTemplates = options.templates || !options.agents && !options.templates;
|
|
2086
|
+
console.log(chalk6.blue(tr(lang, "cli", "update.start")));
|
|
2087
|
+
console.log(chalk6.gray(` - ${tr(lang, "cli", "update.langLabel")}: ${lang}`));
|
|
2088
|
+
console.log(
|
|
2089
|
+
chalk6.gray(` - ${tr(lang, "cli", "update.typeLabel")}: ${projectType}`)
|
|
2090
|
+
);
|
|
2091
|
+
console.log();
|
|
2092
|
+
let updatedCount = 0;
|
|
2093
|
+
if (updateAgents) {
|
|
2094
|
+
console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
2095
|
+
const commonAgents = path4.join(templatesDir, lang, "common", "agents");
|
|
2096
|
+
const typeAgents = path4.join(templatesDir, lang, projectType, "agents");
|
|
2097
|
+
const targetAgents = path4.join(docsDir, "agents");
|
|
2098
|
+
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
2099
|
+
const replacements = {
|
|
2100
|
+
"{{featurePath}}": featurePath
|
|
2101
|
+
};
|
|
2102
|
+
if (await fs8.pathExists(commonAgents)) {
|
|
2103
|
+
const count = await updateFolder(
|
|
2104
|
+
commonAgents,
|
|
2105
|
+
targetAgents,
|
|
2106
|
+
options.force,
|
|
2107
|
+
replacements,
|
|
2108
|
+
lang
|
|
2109
|
+
);
|
|
2110
|
+
updatedCount += count;
|
|
2111
|
+
}
|
|
2112
|
+
if (await fs8.pathExists(typeAgents)) {
|
|
2113
|
+
const count = await updateFolder(
|
|
2114
|
+
typeAgents,
|
|
2115
|
+
targetAgents,
|
|
2116
|
+
options.force,
|
|
2117
|
+
void 0,
|
|
2118
|
+
lang
|
|
2119
|
+
);
|
|
2120
|
+
updatedCount += count;
|
|
2121
|
+
}
|
|
2122
|
+
console.log(chalk6.green(` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`));
|
|
2123
|
+
}
|
|
2124
|
+
if (updateTemplates) {
|
|
2125
|
+
console.log(chalk6.blue(tr(lang, "cli", "update.updatingFeatureBase")));
|
|
2126
|
+
const sourceFeatureBase = path4.join(sourceDir, "features", "feature-base");
|
|
2127
|
+
const targetFeatureBase = path4.join(docsDir, "features", "feature-base");
|
|
2128
|
+
if (await fs8.pathExists(sourceFeatureBase)) {
|
|
2129
|
+
const count = await updateFolder(
|
|
2130
|
+
sourceFeatureBase,
|
|
2131
|
+
targetFeatureBase,
|
|
2132
|
+
options.force,
|
|
2133
|
+
void 0,
|
|
2134
|
+
lang
|
|
2135
|
+
);
|
|
2136
|
+
updatedCount += count;
|
|
2137
|
+
console.log(
|
|
2138
|
+
chalk6.green(
|
|
2139
|
+
` \u2705 ${tr(lang, "cli", "update.filesUpdated", { count })}`
|
|
2140
|
+
)
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
console.log();
|
|
2145
|
+
console.log(
|
|
2146
|
+
chalk6.green(
|
|
2147
|
+
`\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`
|
|
2148
|
+
)
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG) {
|
|
2152
|
+
const protectedFiles = /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
|
|
2153
|
+
await fs8.ensureDir(targetDir);
|
|
2154
|
+
const files = await fs8.readdir(sourceDir);
|
|
2155
|
+
let updatedCount = 0;
|
|
2156
|
+
for (const file of files) {
|
|
2157
|
+
const sourcePath = path4.join(sourceDir, file);
|
|
2158
|
+
const targetPath = path4.join(targetDir, file);
|
|
2159
|
+
const stat = await fs8.stat(sourcePath);
|
|
2160
|
+
if (stat.isFile()) {
|
|
2161
|
+
if (protectedFiles.has(file)) {
|
|
2162
|
+
continue;
|
|
2163
|
+
}
|
|
2164
|
+
let sourceContent = await fs8.readFile(sourcePath, "utf-8");
|
|
2165
|
+
if (replacements) {
|
|
2166
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
2167
|
+
sourceContent = sourceContent.replaceAll(key, value);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
let shouldUpdate = true;
|
|
2171
|
+
if (await fs8.pathExists(targetPath)) {
|
|
2172
|
+
const targetContent = await fs8.readFile(targetPath, "utf-8");
|
|
2173
|
+
if (sourceContent === targetContent) {
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
if (!force) {
|
|
2177
|
+
console.log(
|
|
2178
|
+
chalk6.yellow(
|
|
2179
|
+
` \u26A0\uFE0F ${file} - ${tr(lang, "cli", "update.changeDetected")}`
|
|
2180
|
+
)
|
|
2181
|
+
);
|
|
2182
|
+
shouldUpdate = false;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
if (shouldUpdate) {
|
|
2186
|
+
await fs8.writeFile(targetPath, sourceContent);
|
|
2187
|
+
console.log(
|
|
2188
|
+
chalk6.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
|
|
2189
|
+
);
|
|
2190
|
+
updatedCount++;
|
|
2191
|
+
}
|
|
2192
|
+
} else if (stat.isDirectory()) {
|
|
2193
|
+
const subCount = await updateFolder(
|
|
2194
|
+
sourcePath,
|
|
2195
|
+
targetPath,
|
|
2196
|
+
force,
|
|
2197
|
+
replacements,
|
|
2198
|
+
lang
|
|
2199
|
+
);
|
|
2200
|
+
updatedCount += subCount;
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
return updatedCount;
|
|
2204
|
+
}
|
|
2205
|
+
function configCommand(program2) {
|
|
2206
|
+
program2.command("config").description("View or modify project configuration").option("--project-root <path>", "Set project root path").option("--repo <repo>", "Repository type for fullstack: fe | be").action(async (options) => {
|
|
2207
|
+
try {
|
|
2208
|
+
await runConfig(options);
|
|
2209
|
+
} catch (error) {
|
|
2210
|
+
if (error instanceof Error && error.message === "canceled") {
|
|
2211
|
+
const config = await getConfig(process.cwd());
|
|
2212
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
2213
|
+
console.log(chalk6.yellow(`
|
|
2214
|
+
${tr(lang, "cli", "common.canceled")}`));
|
|
2215
|
+
process.exit(0);
|
|
1875
2216
|
}
|
|
2217
|
+
console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
|
|
2218
|
+
process.exit(1);
|
|
1876
2219
|
}
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
async function runConfig(options) {
|
|
2223
|
+
const cwd = process.cwd();
|
|
2224
|
+
const config = await getConfig(cwd);
|
|
2225
|
+
if (!config) {
|
|
2226
|
+
console.log(
|
|
2227
|
+
chalk6.red(
|
|
2228
|
+
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
2229
|
+
)
|
|
2230
|
+
);
|
|
2231
|
+
process.exit(1);
|
|
1877
2232
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
}
|
|
2233
|
+
const configPath = path4.join(config.docsDir, ".lee-spec-kit.json");
|
|
2234
|
+
if (!options.projectRoot) {
|
|
2235
|
+
console.log();
|
|
2236
|
+
console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
2237
|
+
console.log();
|
|
2238
|
+
console.log(
|
|
2239
|
+
chalk6.gray(
|
|
2240
|
+
` ${tr(config.lang, "cli", "config.pathLabel")}: ${configPath}`
|
|
2241
|
+
)
|
|
2242
|
+
);
|
|
2243
|
+
console.log();
|
|
2244
|
+
const configFile2 = await fs8.readJson(configPath);
|
|
2245
|
+
console.log(JSON.stringify(configFile2, null, 2));
|
|
2246
|
+
console.log();
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
const configFile = await fs8.readJson(configPath);
|
|
2250
|
+
if (configFile.docsRepo !== "standalone") {
|
|
2251
|
+
console.log(
|
|
2252
|
+
chalk6.yellow(
|
|
2253
|
+
tr(config.lang, "cli", "config.projectRootStandaloneOnly")
|
|
2254
|
+
)
|
|
2255
|
+
);
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
const projectType = configFile.projectType;
|
|
2259
|
+
if (projectType === "fullstack") {
|
|
2260
|
+
if (!options.repo) {
|
|
2261
|
+
const response = await prompts(
|
|
2262
|
+
[
|
|
2263
|
+
{
|
|
2264
|
+
type: "select",
|
|
2265
|
+
name: "repo",
|
|
2266
|
+
message: tr(config.lang, "cli", "config.selectRepoToUpdate"),
|
|
2267
|
+
choices: [
|
|
2268
|
+
{ title: "Frontend (fe)", value: "fe" },
|
|
2269
|
+
{ title: "Backend (be)", value: "be" }
|
|
2270
|
+
]
|
|
2271
|
+
}
|
|
2272
|
+
],
|
|
2273
|
+
{
|
|
2274
|
+
onCancel: () => {
|
|
2275
|
+
throw new Error("canceled");
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
);
|
|
2279
|
+
options.repo = response.repo;
|
|
2280
|
+
}
|
|
2281
|
+
if (!options.repo || !["fe", "be"].includes(options.repo)) {
|
|
2282
|
+
console.log(
|
|
2283
|
+
chalk6.red(
|
|
2284
|
+
tr(config.lang, "cli", "config.fullstackRepoRequired")
|
|
2285
|
+
)
|
|
2286
|
+
);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const currentRoot = configFile.projectRoot || { fe: "", be: "" };
|
|
2290
|
+
if (typeof currentRoot === "string") {
|
|
2291
|
+
configFile.projectRoot = {
|
|
2292
|
+
fe: options.repo === "fe" ? options.projectRoot : "",
|
|
2293
|
+
be: options.repo === "be" ? options.projectRoot : ""
|
|
2294
|
+
};
|
|
2295
|
+
} else {
|
|
2296
|
+
currentRoot[options.repo] = options.projectRoot;
|
|
2297
|
+
configFile.projectRoot = currentRoot;
|
|
2298
|
+
}
|
|
2299
|
+
console.log(
|
|
2300
|
+
chalk6.green(
|
|
2301
|
+
tr(config.lang, "cli", "config.projectRootSet", {
|
|
2302
|
+
repo: options.repo.toUpperCase(),
|
|
2303
|
+
path: options.projectRoot
|
|
2304
|
+
})
|
|
2305
|
+
)
|
|
2306
|
+
);
|
|
2307
|
+
} else {
|
|
2308
|
+
configFile.projectRoot = options.projectRoot;
|
|
2309
|
+
console.log(
|
|
2310
|
+
chalk6.green(
|
|
2311
|
+
tr(config.lang, "cli", "config.projectRootSetSingle", {
|
|
2312
|
+
path: options.projectRoot
|
|
2313
|
+
})
|
|
2314
|
+
)
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
await fs8.writeJson(configPath, configFile, { spaces: 2 });
|
|
2318
|
+
console.log();
|
|
1886
2319
|
}
|
|
1887
|
-
|
|
1888
|
-
// src/commands/context.ts
|
|
1889
2320
|
function contextCommand(program2) {
|
|
1890
|
-
program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option("--repo <repo>", "Repository type for fullstack: fe | be").action(
|
|
2321
|
+
program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option("--repo <repo>", "Repository type for fullstack: fe | be").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").action(
|
|
1891
2322
|
async (featureName, options) => {
|
|
1892
2323
|
try {
|
|
1893
2324
|
await runContext(featureName, options);
|
|
@@ -1900,7 +2331,10 @@ function contextCommand(program2) {
|
|
|
1900
2331
|
})
|
|
1901
2332
|
);
|
|
1902
2333
|
} else {
|
|
1903
|
-
console.error(
|
|
2334
|
+
console.error(
|
|
2335
|
+
chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")),
|
|
2336
|
+
error
|
|
2337
|
+
);
|
|
1904
2338
|
}
|
|
1905
2339
|
process.exit(1);
|
|
1906
2340
|
}
|
|
@@ -1923,22 +2357,63 @@ function detectFromBranch(branchName, features) {
|
|
|
1923
2357
|
(f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
|
|
1924
2358
|
);
|
|
1925
2359
|
}
|
|
2360
|
+
function getListLabel(f, stepsMap, lang) {
|
|
2361
|
+
if (f.completion.implementationDone && !f.completion.workflowDone) {
|
|
2362
|
+
if (f.git.docsHasUncommittedChanges) {
|
|
2363
|
+
return tr(lang, "cli", "context.list.docsCommitNeeded");
|
|
2364
|
+
}
|
|
2365
|
+
if (!f.issueNumber) {
|
|
2366
|
+
return tr(lang, "cli", "context.list.issueNumberNeeded");
|
|
2367
|
+
}
|
|
2368
|
+
if (!f.docs.prFieldExists || !f.docs.prStatusFieldExists) {
|
|
2369
|
+
return tr(lang, "cli", "context.list.addPrMetadata");
|
|
2370
|
+
}
|
|
2371
|
+
if (!f.pr.link) {
|
|
2372
|
+
return tr(lang, "cli", "context.list.recordPrLink");
|
|
2373
|
+
}
|
|
2374
|
+
if (!f.pr.status) {
|
|
2375
|
+
return tr(lang, "cli", "context.list.setPrStatus");
|
|
2376
|
+
}
|
|
2377
|
+
if (f.pr.status !== "Approved") {
|
|
2378
|
+
return tr(lang, "cli", "context.list.prStatusToApproved", {
|
|
2379
|
+
status: f.pr.status
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
if (f.specStatus !== "Approved") {
|
|
2383
|
+
return tr(lang, "cli", "context.list.approveSpec");
|
|
2384
|
+
}
|
|
2385
|
+
if (f.planStatus !== "Approved") {
|
|
2386
|
+
return tr(lang, "cli", "context.list.approvePlan");
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
return stepsMap[f.currentStep] || "Unknown";
|
|
2390
|
+
}
|
|
1926
2391
|
async function runContext(featureName, options) {
|
|
1927
2392
|
const cwd = process.cwd();
|
|
1928
2393
|
const config = await getConfig(cwd);
|
|
1929
|
-
const lang = config?.lang ?? "
|
|
2394
|
+
const lang = config?.lang ?? "en";
|
|
1930
2395
|
if (!config) {
|
|
1931
|
-
throw new Error("
|
|
2396
|
+
throw new Error(tr(DEFAULT_LANG, "cli", "common.configNotFound"));
|
|
1932
2397
|
}
|
|
1933
2398
|
const stepDefinitions = getStepDefinitions(lang);
|
|
1934
2399
|
const stepsMap = getStepsMap(lang);
|
|
1935
2400
|
const { features, branches, warnings } = await scanFeatures(config);
|
|
2401
|
+
const doneFeatures = features.filter((f2) => f2.completion.workflowDone);
|
|
2402
|
+
const openFeatures = features.filter((f2) => !f2.completion.workflowDone);
|
|
2403
|
+
const inProgressFeatures = openFeatures.filter(
|
|
2404
|
+
(f2) => !f2.completion.implementationDone
|
|
2405
|
+
);
|
|
2406
|
+
const readyToCloseFeatures = openFeatures.filter(
|
|
2407
|
+
(f2) => f2.completion.implementationDone
|
|
2408
|
+
);
|
|
1936
2409
|
let targetFeatures = [];
|
|
2410
|
+
let selectionMode = "explicit";
|
|
1937
2411
|
if (featureName) {
|
|
1938
2412
|
targetFeatures = features.filter((f2) => matchesFeatureSelector(f2, featureName));
|
|
1939
2413
|
if (options.repo) {
|
|
1940
2414
|
targetFeatures = targetFeatures.filter((f2) => f2.type === options.repo);
|
|
1941
2415
|
}
|
|
2416
|
+
selectionMode = "explicit";
|
|
1942
2417
|
} else {
|
|
1943
2418
|
if (config.projectType === "single") {
|
|
1944
2419
|
const branchName = branches.project.single || "";
|
|
@@ -1960,15 +2435,33 @@ async function runContext(featureName, options) {
|
|
|
1960
2435
|
) : [];
|
|
1961
2436
|
targetFeatures = [...feMatches, ...beMatches];
|
|
1962
2437
|
}
|
|
1963
|
-
if (targetFeatures.length
|
|
2438
|
+
if (targetFeatures.length > 0) {
|
|
2439
|
+
selectionMode = "branch";
|
|
2440
|
+
} else if (options.all) {
|
|
2441
|
+
targetFeatures = features;
|
|
2442
|
+
selectionMode = "all";
|
|
2443
|
+
} else if (options.done) {
|
|
2444
|
+
targetFeatures = doneFeatures;
|
|
2445
|
+
selectionMode = "done";
|
|
2446
|
+
} else {
|
|
2447
|
+
targetFeatures = openFeatures;
|
|
2448
|
+
selectionMode = "open";
|
|
2449
|
+
}
|
|
1964
2450
|
}
|
|
1965
2451
|
if (options.json) {
|
|
2452
|
+
const isNoOpen = selectionMode === "open" && features.length > 0 && openFeatures.length === 0;
|
|
1966
2453
|
const result = {
|
|
1967
|
-
status: features.length === 0 ? "no_features" : targetFeatures.length === 1 ? "single_matched" : targetFeatures.length > 1 ? "multiple_active" : "no_match",
|
|
2454
|
+
status: features.length === 0 ? "no_features" : isNoOpen ? "no_open" : targetFeatures.length === 1 ? "single_matched" : targetFeatures.length > 1 ? "multiple_active" : "no_match",
|
|
2455
|
+
selectionMode,
|
|
1968
2456
|
branches,
|
|
1969
2457
|
warnings,
|
|
1970
2458
|
matchedFeature: targetFeatures.length === 1 ? targetFeatures[0] : null,
|
|
1971
2459
|
candidates: targetFeatures.length > 1 ? targetFeatures : [],
|
|
2460
|
+
// "Completed" now means workflow-done.
|
|
2461
|
+
completedCandidates: selectionMode === "open" ? doneFeatures : [],
|
|
2462
|
+
openCandidates: selectionMode === "open" ? openFeatures : [],
|
|
2463
|
+
inProgressCandidates: selectionMode === "open" ? inProgressFeatures : [],
|
|
2464
|
+
readyToCloseCandidates: selectionMode === "open" ? readyToCloseFeatures : [],
|
|
1972
2465
|
actions: targetFeatures.length === 1 ? targetFeatures[0].actions : [],
|
|
1973
2466
|
recommendation: ""
|
|
1974
2467
|
};
|
|
@@ -2014,41 +2507,99 @@ async function runContext(featureName, options) {
|
|
|
2014
2507
|
console.log(chalk6.gray("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
2015
2508
|
console.log();
|
|
2016
2509
|
if (features.length === 0) {
|
|
2017
|
-
console.log(
|
|
2510
|
+
console.log(
|
|
2511
|
+
chalk6.yellow(
|
|
2512
|
+
tr(lang, "cli", "context.noActiveFeatures")
|
|
2513
|
+
)
|
|
2514
|
+
);
|
|
2018
2515
|
console.log();
|
|
2019
2516
|
return;
|
|
2020
2517
|
}
|
|
2021
2518
|
if (warnings.length > 0) {
|
|
2022
|
-
console.log(chalk6.yellow("
|
|
2519
|
+
console.log(chalk6.yellow(tr(lang, "cli", "context.envWarnings")));
|
|
2023
2520
|
warnings.forEach((w) => console.log(chalk6.yellow(` - ${w}`)));
|
|
2024
2521
|
console.log();
|
|
2025
2522
|
}
|
|
2026
2523
|
if (targetFeatures.length > 1) {
|
|
2027
|
-
|
|
2028
|
-
chalk6.blue(`\u{1F539} ${targetFeatures.length} Active Features Detected:`)
|
|
2029
|
-
);
|
|
2030
|
-
console.log();
|
|
2031
|
-
targetFeatures.forEach((f2) => {
|
|
2032
|
-
const stepName2 = stepsMap[f2.currentStep] || "Unknown";
|
|
2033
|
-
const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
2524
|
+
if (selectionMode === "open") {
|
|
2034
2525
|
console.log(
|
|
2035
|
-
|
|
2526
|
+
chalk6.gray(
|
|
2527
|
+
` ${tr(lang, "cli", "context.openFallbackSummary", {
|
|
2528
|
+
inProgress: inProgressFeatures.length,
|
|
2529
|
+
readyToClose: readyToCloseFeatures.length,
|
|
2530
|
+
done: doneFeatures.length
|
|
2531
|
+
})}`
|
|
2532
|
+
)
|
|
2036
2533
|
);
|
|
2037
|
-
|
|
2534
|
+
console.log();
|
|
2535
|
+
}
|
|
2536
|
+
if (selectionMode === "open") {
|
|
2537
|
+
console.log(
|
|
2538
|
+
chalk6.blue(
|
|
2539
|
+
`\u{1F539} ${tr(lang, "cli", "context.sectionInProgress")} (${inProgressFeatures.length})`
|
|
2540
|
+
)
|
|
2541
|
+
);
|
|
2542
|
+
inProgressFeatures.forEach((f2) => {
|
|
2543
|
+
const stepName2 = getListLabel(f2, stepsMap, lang);
|
|
2544
|
+
const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
2545
|
+
console.log(
|
|
2546
|
+
` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
|
|
2547
|
+
);
|
|
2548
|
+
});
|
|
2549
|
+
console.log();
|
|
2550
|
+
console.log(
|
|
2551
|
+
chalk6.blue(
|
|
2552
|
+
`\u{1F538} ${tr(lang, "cli", "context.sectionReadyToClose")} (${readyToCloseFeatures.length})`
|
|
2553
|
+
)
|
|
2554
|
+
);
|
|
2555
|
+
readyToCloseFeatures.forEach((f2) => {
|
|
2556
|
+
const stepName2 = getListLabel(f2, stepsMap, lang);
|
|
2557
|
+
const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
2558
|
+
console.log(
|
|
2559
|
+
` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
|
|
2560
|
+
);
|
|
2561
|
+
});
|
|
2562
|
+
} else {
|
|
2563
|
+
const title = selectionMode === "all" ? `\u{1F539} ${targetFeatures.length} Features:` : selectionMode === "done" ? `\u{1F539} ${targetFeatures.length} Done Features:` : `\u{1F539} ${targetFeatures.length} Features Detected:`;
|
|
2564
|
+
console.log(chalk6.blue(title));
|
|
2565
|
+
console.log();
|
|
2566
|
+
targetFeatures.forEach((f2) => {
|
|
2567
|
+
const stepName2 = getListLabel(f2, stepsMap, lang);
|
|
2568
|
+
const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
2569
|
+
console.log(
|
|
2570
|
+
` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
|
|
2571
|
+
);
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2038
2574
|
console.log();
|
|
2039
|
-
console.log(chalk6.gray("
|
|
2575
|
+
console.log(chalk6.gray(tr(lang, "cli", "context.tipDetails")));
|
|
2040
2576
|
console.log(
|
|
2041
2577
|
chalk6.gray(" $ npx lee-spec-kit context <slug|F001|F001-slug> [--repo fe|be]")
|
|
2042
2578
|
);
|
|
2579
|
+
if (selectionMode === "open") {
|
|
2580
|
+
console.log(
|
|
2581
|
+
chalk6.gray(
|
|
2582
|
+
` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
|
|
2583
|
+
)
|
|
2584
|
+
);
|
|
2585
|
+
console.log(
|
|
2586
|
+
chalk6.gray(
|
|
2587
|
+
` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
|
|
2588
|
+
)
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2043
2591
|
console.log();
|
|
2044
2592
|
return;
|
|
2045
2593
|
}
|
|
2046
2594
|
const f = targetFeatures[0];
|
|
2047
2595
|
const stepName = stepsMap[f.currentStep] || "Unknown";
|
|
2048
|
-
const okTag = (requiresUserOk) => requiresUserOk ? chalk6.yellow(lang
|
|
2596
|
+
const okTag = (requiresUserOk) => requiresUserOk ? chalk6.yellow(tr(lang, "cli", "context.okRequired")) : "";
|
|
2049
2597
|
console.log(
|
|
2050
2598
|
`\u{1F539} Feature: ${chalk6.bold(f.folderName)} ${config.projectType === "fullstack" ? chalk6.cyan(`(${f.type})`) : ""}`
|
|
2051
2599
|
);
|
|
2600
|
+
console.log(
|
|
2601
|
+
` \u2022 Completion: ${f.completion.implementationDone ? chalk6.green("Implementation \u2705") : chalk6.gray("Implementation \u25EF")} / ${f.completion.workflowDone ? chalk6.green("Workflow \u2705") : chalk6.yellow("Workflow \u25EF")}`
|
|
2602
|
+
);
|
|
2052
2603
|
if (f.issueNumber) {
|
|
2053
2604
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
2054
2605
|
}
|
|
@@ -2116,13 +2667,248 @@ function printChecklist(f, stepDefinitions) {
|
|
|
2116
2667
|
console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
|
|
2117
2668
|
});
|
|
2118
2669
|
}
|
|
2670
|
+
function formatPath(cwd, p) {
|
|
2671
|
+
if (!p) return "";
|
|
2672
|
+
return path4.isAbsolute(p) ? path4.relative(cwd, p) : p;
|
|
2673
|
+
}
|
|
2674
|
+
function detectPlaceholders(content) {
|
|
2675
|
+
const patterns = [
|
|
2676
|
+
{ key: "{{projectName}}", re: /\{\{projectName\}\}/g },
|
|
2677
|
+
{ key: "{{date}}", re: /\{\{date\}\}/g },
|
|
2678
|
+
{ key: "{{featurePath}}", re: /\{\{featurePath\}\}/g },
|
|
2679
|
+
{ key: "{{description}}", re: /\{\{description\}\}/g },
|
|
2680
|
+
{ key: "{\uAE30\uB2A5\uBA85}", re: /\{기능명\}/g },
|
|
2681
|
+
{ key: "{\uBC88\uD638}", re: /\{번호\}/g },
|
|
2682
|
+
{ key: "{\uC774\uC288\uBC88\uD638}", re: /\{이슈번호\}/g },
|
|
2683
|
+
{ key: "{feature-name}", re: /\{feature-name\}/g },
|
|
2684
|
+
{ key: "{number}", re: /\{number\}/g },
|
|
2685
|
+
{ key: "{issue-number}", re: /\{issue-number\}/g },
|
|
2686
|
+
{ key: "{be|fe}", re: /\{be\|fe\}/g },
|
|
2687
|
+
{ key: "YYYY-MM-DD", re: /\bYYYY-MM-DD\b/g }
|
|
2688
|
+
];
|
|
2689
|
+
const hits = [];
|
|
2690
|
+
for (const { key, re } of patterns) {
|
|
2691
|
+
if (re.test(content)) hits.push(key);
|
|
2692
|
+
}
|
|
2693
|
+
return hits;
|
|
2694
|
+
}
|
|
2695
|
+
async function checkDocsStructure(config, cwd) {
|
|
2696
|
+
const issues = [];
|
|
2697
|
+
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
2698
|
+
for (const dir of requiredDirs) {
|
|
2699
|
+
const p = path4.join(config.docsDir, dir);
|
|
2700
|
+
if (!await fs8.pathExists(p)) {
|
|
2701
|
+
issues.push({
|
|
2702
|
+
level: "error",
|
|
2703
|
+
code: "missing_dir",
|
|
2704
|
+
message: tr(config.lang, "cli", "doctor.issue.missingRequiredDir", { dir }),
|
|
2705
|
+
path: formatPath(cwd, p)
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
const configPath = path4.join(config.docsDir, ".lee-spec-kit.json");
|
|
2710
|
+
if (!await fs8.pathExists(configPath)) {
|
|
2711
|
+
issues.push({
|
|
2712
|
+
level: "warn",
|
|
2713
|
+
code: "missing_config",
|
|
2714
|
+
message: tr(config.lang, "cli", "doctor.issue.missingConfig"),
|
|
2715
|
+
path: formatPath(cwd, configPath)
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
return issues;
|
|
2719
|
+
}
|
|
2720
|
+
async function checkFeatures(config, cwd, features) {
|
|
2721
|
+
const issues = [];
|
|
2722
|
+
if (features.length === 0) {
|
|
2723
|
+
issues.push({
|
|
2724
|
+
level: "warn",
|
|
2725
|
+
code: "no_features",
|
|
2726
|
+
message: tr(config.lang, "cli", "doctor.issue.noFeatures")
|
|
2727
|
+
});
|
|
2728
|
+
return issues;
|
|
2729
|
+
}
|
|
2730
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
2731
|
+
for (const f of features) {
|
|
2732
|
+
const rel = f.docs.featurePathFromDocs || path4.relative(config.docsDir, f.path);
|
|
2733
|
+
const id = f.id || "UNKNOWN";
|
|
2734
|
+
if (!idMap.has(id)) idMap.set(id, []);
|
|
2735
|
+
idMap.get(id).push(rel);
|
|
2736
|
+
const featureDocs = ["spec.md", "plan.md", "tasks.md", "decisions.md"];
|
|
2737
|
+
for (const file of featureDocs) {
|
|
2738
|
+
const p = path4.join(f.path, file);
|
|
2739
|
+
if (!await fs8.pathExists(p)) continue;
|
|
2740
|
+
const content = await fs8.readFile(p, "utf-8");
|
|
2741
|
+
const placeholders = detectPlaceholders(content);
|
|
2742
|
+
if (placeholders.length === 0) continue;
|
|
2743
|
+
issues.push({
|
|
2744
|
+
level: "warn",
|
|
2745
|
+
code: "placeholder_left",
|
|
2746
|
+
message: tr(config.lang, "cli", "doctor.issue.placeholdersLeft", {
|
|
2747
|
+
placeholders: placeholders.join(", ")
|
|
2748
|
+
}),
|
|
2749
|
+
path: formatPath(cwd, p)
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
if (!f.docs.specExists) {
|
|
2753
|
+
issues.push({
|
|
2754
|
+
level: "warn",
|
|
2755
|
+
code: "missing_spec",
|
|
2756
|
+
message: tr(config.lang, "cli", "doctor.issue.missingSpec"),
|
|
2757
|
+
path: formatPath(cwd, f.path)
|
|
2758
|
+
});
|
|
2759
|
+
} else if (!f.specStatus) {
|
|
2760
|
+
issues.push({
|
|
2761
|
+
level: "warn",
|
|
2762
|
+
code: "spec_status_unset",
|
|
2763
|
+
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
2764
|
+
path: formatPath(cwd, path4.join(f.path, "spec.md"))
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2767
|
+
if (f.docs.planExists && !f.planStatus) {
|
|
2768
|
+
issues.push({
|
|
2769
|
+
level: "warn",
|
|
2770
|
+
code: "plan_status_unset",
|
|
2771
|
+
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
2772
|
+
path: formatPath(cwd, path4.join(f.path, "plan.md"))
|
|
2773
|
+
});
|
|
2774
|
+
}
|
|
2775
|
+
if (f.docs.tasksExists && f.tasks.total === 0) {
|
|
2776
|
+
issues.push({
|
|
2777
|
+
level: "warn",
|
|
2778
|
+
code: "tasks_empty",
|
|
2779
|
+
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
2780
|
+
path: formatPath(cwd, path4.join(f.path, "tasks.md"))
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
const duplicates = [...idMap.entries()].filter(
|
|
2785
|
+
([id, paths]) => id !== "UNKNOWN" && paths.length > 1
|
|
2786
|
+
);
|
|
2787
|
+
for (const [id, paths] of duplicates) {
|
|
2788
|
+
issues.push({
|
|
2789
|
+
level: "warn",
|
|
2790
|
+
code: "duplicate_feature_id",
|
|
2791
|
+
message: tr(config.lang, "cli", "doctor.issue.duplicateFeatureId", {
|
|
2792
|
+
id,
|
|
2793
|
+
count: String(paths.length)
|
|
2794
|
+
}),
|
|
2795
|
+
path: formatPath(cwd, paths[0])
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
2798
|
+
const unknowns = idMap.get("UNKNOWN") || [];
|
|
2799
|
+
for (const p of unknowns) {
|
|
2800
|
+
issues.push({
|
|
2801
|
+
level: "warn",
|
|
2802
|
+
code: "missing_feature_id",
|
|
2803
|
+
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
2804
|
+
path: formatPath(cwd, path4.join(config.docsDir, p))
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
return issues;
|
|
2808
|
+
}
|
|
2809
|
+
function doctorCommand(program2) {
|
|
2810
|
+
program2.command("doctor").description("Validate docs structure and feature metadata").option("--json", "Output in JSON format for agents").option("-s, --strict", "Exit with non-zero code when issues are found").action(async (options) => {
|
|
2811
|
+
const cwd = process.cwd();
|
|
2812
|
+
const config = await getConfig(cwd);
|
|
2813
|
+
if (!config) {
|
|
2814
|
+
const message = tr(DEFAULT_LANG, "cli", "common.configNotFound");
|
|
2815
|
+
if (options.json) {
|
|
2816
|
+
console.log(JSON.stringify({ status: "error", error: message }, null, 2));
|
|
2817
|
+
} else {
|
|
2818
|
+
console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), message);
|
|
2819
|
+
}
|
|
2820
|
+
process.exit(1);
|
|
2821
|
+
}
|
|
2822
|
+
const { docsDir, projectType, lang } = config;
|
|
2823
|
+
const { features, branches, warnings } = await scanFeatures(config);
|
|
2824
|
+
const issues = [];
|
|
2825
|
+
issues.push(...await checkDocsStructure({ docsDir, lang }, cwd));
|
|
2826
|
+
issues.push(...await checkFeatures({ docsDir, lang }, cwd, features));
|
|
2827
|
+
const hasIssues = issues.length > 0;
|
|
2828
|
+
const hasErrors = issues.some((i) => i.level === "error");
|
|
2829
|
+
const exitCode = options.strict && hasIssues ? 1 : 0;
|
|
2830
|
+
if (options.json) {
|
|
2831
|
+
console.log(
|
|
2832
|
+
JSON.stringify(
|
|
2833
|
+
{
|
|
2834
|
+
status: hasErrors ? "error" : hasIssues ? "warn" : "ok",
|
|
2835
|
+
meta: { docsDir, projectType, lang },
|
|
2836
|
+
branches,
|
|
2837
|
+
warnings,
|
|
2838
|
+
counts: {
|
|
2839
|
+
features: features.length,
|
|
2840
|
+
issues: issues.length,
|
|
2841
|
+
errors: issues.filter((i) => i.level === "error").length,
|
|
2842
|
+
warnings: issues.filter((i) => i.level === "warn").length
|
|
2843
|
+
},
|
|
2844
|
+
issues
|
|
2845
|
+
},
|
|
2846
|
+
null,
|
|
2847
|
+
2
|
|
2848
|
+
)
|
|
2849
|
+
);
|
|
2850
|
+
process.exit(exitCode);
|
|
2851
|
+
}
|
|
2852
|
+
console.log();
|
|
2853
|
+
console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
|
|
2854
|
+
console.log(chalk6.gray(`- Docs: ${path4.relative(cwd, docsDir)}`));
|
|
2855
|
+
console.log(chalk6.gray(`- Type: ${projectType}`));
|
|
2856
|
+
console.log(chalk6.gray(`- Lang: ${lang}`));
|
|
2857
|
+
console.log();
|
|
2858
|
+
if (warnings.length > 0) {
|
|
2859
|
+
console.log(
|
|
2860
|
+
chalk6.yellow(tr(lang, "cli", "doctor.envWarnings"))
|
|
2861
|
+
);
|
|
2862
|
+
warnings.forEach((w) => console.log(chalk6.yellow(` - ${w}`)));
|
|
2863
|
+
console.log();
|
|
2864
|
+
}
|
|
2865
|
+
if (!hasIssues) {
|
|
2866
|
+
console.log(chalk6.green(tr(lang, "cli", "doctor.noIssues")));
|
|
2867
|
+
console.log();
|
|
2868
|
+
process.exit(0);
|
|
2869
|
+
}
|
|
2870
|
+
const errors = issues.filter((i) => i.level === "error");
|
|
2871
|
+
const warns = issues.filter((i) => i.level === "warn");
|
|
2872
|
+
if (errors.length > 0) {
|
|
2873
|
+
console.log(
|
|
2874
|
+
chalk6.red(`\u274C ${tr(lang, "cli", "doctor.errorsTitle")} (${errors.length})`)
|
|
2875
|
+
);
|
|
2876
|
+
errors.forEach(
|
|
2877
|
+
(i) => console.log(chalk6.red(` - ${i.message}${i.path ? ` (${i.path})` : ""}`))
|
|
2878
|
+
);
|
|
2879
|
+
console.log();
|
|
2880
|
+
}
|
|
2881
|
+
if (warns.length > 0) {
|
|
2882
|
+
console.log(
|
|
2883
|
+
chalk6.yellow(
|
|
2884
|
+
`\u26A0\uFE0F ${tr(lang, "cli", "doctor.warningsTitle")} (${warns.length})`
|
|
2885
|
+
)
|
|
2886
|
+
);
|
|
2887
|
+
warns.forEach(
|
|
2888
|
+
(i) => console.log(
|
|
2889
|
+
chalk6.yellow(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
|
|
2890
|
+
)
|
|
2891
|
+
);
|
|
2892
|
+
console.log();
|
|
2893
|
+
}
|
|
2894
|
+
console.log(
|
|
2895
|
+
chalk6.gray(
|
|
2896
|
+
tr(lang, "cli", "doctor.tipJson", {
|
|
2897
|
+
strictFlag: options.strict ? " --strict" : ""
|
|
2898
|
+
})
|
|
2899
|
+
)
|
|
2900
|
+
);
|
|
2901
|
+
console.log();
|
|
2902
|
+
process.exit(exitCode);
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2119
2905
|
var CACHE_FILE = path4.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
2120
2906
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
2121
2907
|
function getCurrentVersion() {
|
|
2122
2908
|
try {
|
|
2123
2909
|
const packageJsonPath = path4.join(__dirname$1, "..", "package.json");
|
|
2124
|
-
if (
|
|
2125
|
-
const pkg =
|
|
2910
|
+
if (fs8.existsSync(packageJsonPath)) {
|
|
2911
|
+
const pkg = fs8.readJsonSync(packageJsonPath);
|
|
2126
2912
|
return pkg.version;
|
|
2127
2913
|
}
|
|
2128
2914
|
} catch {
|
|
@@ -2131,8 +2917,8 @@ function getCurrentVersion() {
|
|
|
2131
2917
|
}
|
|
2132
2918
|
function readCache() {
|
|
2133
2919
|
try {
|
|
2134
|
-
if (
|
|
2135
|
-
return
|
|
2920
|
+
if (fs8.existsSync(CACHE_FILE)) {
|
|
2921
|
+
return fs8.readJsonSync(CACHE_FILE);
|
|
2136
2922
|
}
|
|
2137
2923
|
} catch {
|
|
2138
2924
|
}
|
|
@@ -2196,12 +2982,23 @@ function checkForUpdates() {
|
|
|
2196
2982
|
}
|
|
2197
2983
|
|
|
2198
2984
|
// src/index.ts
|
|
2199
|
-
|
|
2985
|
+
function shouldCheckForUpdates() {
|
|
2986
|
+
const argv = process.argv.slice(2);
|
|
2987
|
+
const hasJsonFlag = argv.includes("--json");
|
|
2988
|
+
const isHelpOrVersion = argv.includes("--help") || argv.includes("-h") || argv.includes("--version") || argv.includes("-V");
|
|
2989
|
+
const disabledByEnv = (process.env.LSK_NO_UPDATE_CHECK || "").trim() === "1" || (process.env.LEE_SPEC_KIT_NO_UPDATE_CHECK || "").trim() === "1";
|
|
2990
|
+
if (hasJsonFlag) return false;
|
|
2991
|
+
if (!process.stdout.isTTY) return false;
|
|
2992
|
+
if (isHelpOrVersion) return false;
|
|
2993
|
+
if (disabledByEnv) return false;
|
|
2994
|
+
return true;
|
|
2995
|
+
}
|
|
2996
|
+
if (shouldCheckForUpdates()) checkForUpdates();
|
|
2200
2997
|
function getCliVersion() {
|
|
2201
2998
|
try {
|
|
2202
2999
|
const packageJsonPath = path4.join(__dirname$1, "..", "package.json");
|
|
2203
|
-
if (
|
|
2204
|
-
const pkg =
|
|
3000
|
+
if (fs8.existsSync(packageJsonPath)) {
|
|
3001
|
+
const pkg = fs8.readJsonSync(packageJsonPath);
|
|
2205
3002
|
if (pkg?.version) return String(pkg.version);
|
|
2206
3003
|
}
|
|
2207
3004
|
} catch {
|
|
@@ -2217,4 +3014,5 @@ statusCommand(program);
|
|
|
2217
3014
|
updateCommand(program);
|
|
2218
3015
|
configCommand(program);
|
|
2219
3016
|
contextCommand(program);
|
|
2220
|
-
program
|
|
3017
|
+
doctorCommand(program);
|
|
3018
|
+
await program.parseAsync();
|