lee-spec-kit 0.4.3 → 0.4.5

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/dist/index.js CHANGED
@@ -43,6 +43,372 @@ 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.updatingSkills": "\u{1F4C1} agents/skills \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
89
+ "update.agentsUpdated": "agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
90
+ "update.skillsUpdated": "agents/skills \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
91
+ "update.updatingFeatureBase": "\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
92
+ "update.filesUpdated": "{count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
93
+ "update.updatedTotal": "\uCD1D {count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!",
94
+ "update.changeDetected": "\uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)",
95
+ "update.fileUpdated": "{file} \uC5C5\uB370\uC774\uD2B8",
96
+ "doctor.title": "\u{1F50E} \uBB38\uC11C \uC9C4\uB2E8",
97
+ "doctor.envWarnings": "\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:",
98
+ "doctor.noIssues": "\u2705 \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
99
+ "doctor.errorsTitle": "\uC624\uB958",
100
+ "doctor.warningsTitle": "\uACBD\uACE0",
101
+ "doctor.tipJson": "Tip: \uC5D0\uC774\uC804\uD2B8\uC6A9 JSON \uCD9C\uB825: npx lee-spec-kit doctor --json{strictFlag}",
102
+ "doctor.issue.missingRequiredDir": "\uD544\uC218 \uD3F4\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: {dir}",
103
+ "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.",
104
+ "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.)",
105
+ "doctor.issue.placeholdersLeft": "\uD50C\uB808\uC774\uC2A4\uD640\uB354\uAC00 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4: {placeholders}",
106
+ "doctor.issue.missingSpec": "spec.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
107
+ "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)",
108
+ "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)",
109
+ "doctor.issue.tasksEmpty": "tasks.md\uC5D0 \uD0DC\uC2A4\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
110
+ "doctor.issue.duplicateFeatureId": "\uC911\uBCF5 Feature ID \uAC10\uC9C0: {id} ({count}\uAC1C)",
111
+ "doctor.issue.missingFeatureId": "Feature \uD3F4\uB354\uBA85\uC774 F001-... \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. (ID\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC74C)",
112
+ "context.noActiveFeatures": "\u26A0\uFE0F \uC9C4\uD589 \uC911\uC778 Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
113
+ "context.envWarnings": "\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:",
114
+ "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)",
115
+ "context.sectionInProgress": "\uC9C4\uD589 \uC911",
116
+ "context.sectionReadyToClose": "\uC885\uB8CC \uC900\uBE44",
117
+ "context.tipDetails": "Tip: \uD2B9\uC815 Feature\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uBCF4\uB824\uBA74:",
118
+ "context.tipShowAll": "\uC804\uCCB4 \uBCF4\uAE30",
119
+ "context.tipShowDone": "\uC644\uB8CC\uB9CC \uBCF4\uAE30",
120
+ "context.okRequired": "[OK \uD544\uC694] ",
121
+ "context.list.docsCommitNeeded": "\uBB38\uC11C \uCEE4\uBC0B \uD544\uC694",
122
+ "context.list.issueNumberNeeded": "\uC774\uC288 \uBC88\uD638 \uAE30\uB85D \uD544\uC694",
123
+ "context.list.addPrMetadata": "PR \uBA54\uD0C0\uB370\uC774\uD130(PR/PR \uC0C1\uD0DC) \uCD94\uAC00",
124
+ "context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
125
+ "context.list.setPrStatus": "PR \uC0C1\uD0DC \uC124\uC815",
126
+ "context.list.prStatusToApproved": "PR \uC0C1\uD0DC {status} \u2192 Approved",
127
+ "context.list.approveSpec": "spec \uC2B9\uC778 \uD544\uC694",
128
+ "context.list.approvePlan": "plan \uC2B9\uC778 \uD544\uC694",
129
+ "init.selectLangPrompt": "\uBB38\uC11C \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
130
+ "init.currentDirectoryLabel": "\u{1F4CD} \uD604\uC7AC \uC704\uCE58",
131
+ "init.gitDetected": "\u2705 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0\uB428",
132
+ "init.insideProjectRoot": "\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uB0B4\uC5D0\uC11C \uC2E4\uD589\uD558\uACE0 \uACC4\uC2ED\uB2C8\uB2E4.",
133
+ "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.",
134
+ "init.modeStandaloneDesc": "\u2022 standalone: \uBCC4\uB3C4 \uD3F4\uB354\uC5D0\uC11C \uB3C5\uB9BD docs \uB808\uD3EC\uB85C \uAD00\uB9AC\uD558\uB824\uBA74,",
135
+ "init.modeStandaloneMove": " \uD574\uB2F9 \uD3F4\uB354\uB85C \uC774\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD574\uC8FC\uC138\uC694.",
136
+ "init.gitNotDetected": "\u26A0\uFE0F Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
137
+ "init.gitNotDetectedDetail": "\uC0C8\uB85C\uC6B4 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC0DD\uC131\uB429\uB2C8\uB2E4.",
138
+ "init.prompt.projectName": "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694:",
139
+ "init.prompt.projectType": "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
140
+ "init.choice.projectType.single.title": "Single - \uB2E8\uC77C \uB808\uD3EC \uD504\uB85C\uC81D\uD2B8",
141
+ "init.choice.projectType.single.desc": "features/ \uD3F4\uB354 \uD558\uB098\uB85C \uAD00\uB9AC",
142
+ "init.choice.projectType.fullstack.title": "Fullstack - FE/BE \uBD84\uB9AC \uD504\uB85C\uC81D\uD2B8",
143
+ "init.choice.projectType.fullstack.desc": "features/be/, features/fe/ \uBD84\uB9AC \uAD00\uB9AC",
144
+ "init.prompt.docsMode": "Docs \uAD00\uB9AC \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
145
+ "init.choice.docsRepo.embedded.title": "embedded - \uD504\uB85C\uC81D\uD2B8 \uB0B4 \uD3EC\uD568 (./docs)",
146
+ "init.choice.docsRepo.embedded.desc": "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4",
147
+ "init.choice.docsRepo.standalone.title": "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
148
+ "init.choice.docsRepo.standalone.desc": "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4",
149
+ "init.prompt.feRepoPath": "Frontend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
150
+ "init.prompt.beRepoPath": "Backend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
151
+ "init.prompt.projectRepoPath": "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
152
+ "init.validation.enterPath": "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694",
153
+ "init.prompt.pushMode": "Docs push \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
154
+ "init.choice.push.local": "local - \uB85C\uCEEC\uC5D0\uC11C\uB9CC \uAD00\uB9AC (push \uC548 \uD568)",
155
+ "init.choice.push.remote": "remote - \uC6D0\uACA9\uC5D0\uB3C4 push",
156
+ "init.prompt.remoteUrl": "\uC6D0\uACA9 \uB808\uD3EC URL\uC744 \uC785\uB825\uD558\uC138\uC694:",
157
+ "init.validation.enterUrl": "URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694",
158
+ "init.prompt.overwrite": "{dir} \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
159
+ "init.log.creatingDocs": "\u{1F4C1} docs \uAD6C\uC870 \uC0DD\uC131 \uC911...",
160
+ "init.log.projectLabel": "\uD504\uB85C\uC81D\uD2B8",
161
+ "init.log.typeLabel": "\uD0C0\uC785",
162
+ "init.log.langLabel": "\uC5B8\uC5B4",
163
+ "init.log.pathLabel": "\uACBD\uB85C",
164
+ "init.log.docsCreated": "\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!",
165
+ "init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
166
+ "init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
167
+ "init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
168
+ "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
169
+ "init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
170
+ "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)',
171
+ "init.warn.commitManually": " \uC218\uB3D9\uC73C\uB85C \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD55C \uB4A4 \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694.",
172
+ "init.log.gitRemoteSet": "\u2705 Git remote \uC124\uC815 \uC644\uB8CC: {remote}",
173
+ "init.warn.gitRemoteExists": "\u26A0\uFE0F Git remote\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4.",
174
+ "init.log.gitInitialCommitDone": "\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!",
175
+ "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)",
176
+ "init.error.templateNotFound": "\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {path}"
177
+ },
178
+ steps: {
179
+ featureFolder: "Feature \uD3F4\uB354 \uC0DD\uC131",
180
+ specWrite: "spec.md \uC791\uC131",
181
+ specApprove: "spec.md \uC2B9\uC778",
182
+ planWrite: "plan.md \uC791\uC131",
183
+ planApprove: "plan.md \uC2B9\uC778",
184
+ tasksWrite: "tasks.md \uC791\uC131",
185
+ docsCommitPlanning: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
186
+ issueCreate: "GitHub Issue \uC0DD\uC131",
187
+ branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
188
+ tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
189
+ prCreate: "PR \uC0DD\uC131",
190
+ codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
191
+ featureDone: "Feature \uC644\uB8CC"
192
+ },
193
+ messages: {
194
+ specCreate: "spec.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/spec.md \uCC38\uACE0)",
195
+ specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
196
+ specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
197
+ planCreate: "plan.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/plan.md \uCC38\uACE0)",
198
+ planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
199
+ planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
200
+ 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)",
201
+ tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
202
+ docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
203
+ 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)",
204
+ docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
205
+ standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
206
+ createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
207
+ 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.',
208
+ 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})",
209
+ 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)',
210
+ startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
211
+ checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)",
212
+ 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)",
213
+ 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)",
214
+ 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)",
215
+ 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)",
216
+ 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.",
217
+ 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.",
218
+ 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."
219
+ },
220
+ warnings: {
221
+ 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.)",
222
+ 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)",
223
+ 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)",
224
+ 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.",
225
+ 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.)",
226
+ 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.)",
227
+ 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.)",
228
+ 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.)",
229
+ 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.)"
230
+ }
231
+ },
232
+ en: {
233
+ cli: {
234
+ "common.errorLabel": "Error:",
235
+ "common.canceled": "Operation canceled.",
236
+ "common.configNotFound": "Config file not found. Run `init` first.",
237
+ "common.docsNotFound": "docs folder not found. Run `init` first.",
238
+ "status.noFeatures": "No features found.",
239
+ "status.duplicateIds": "Duplicate Feature IDs found:",
240
+ "status.missingIds": "Entries missing Feature ID:",
241
+ "status.wrote": "\u2705 Wrote {path}",
242
+ "feature.selectRepo": "Select a repository:",
243
+ "feature.folderExists": "Folder already exists: {path}",
244
+ "feature.baseNotFound": "feature-base template not found.",
245
+ "feature.created": "\u2705 Feature folder created: {path}",
246
+ "feature.nextStepsTitle": "Next steps:",
247
+ "feature.nextSteps1": " 1. Write {path}/spec.md",
248
+ "feature.nextSteps2": " 2. Ask for review",
249
+ "feature.nextSteps3": " 3. After approval, write plan.md",
250
+ "config.currentTitle": "\u{1F4CB} Current config:",
251
+ "config.pathLabel": "Path",
252
+ "config.projectRootStandaloneOnly": "\u26A0\uFE0F projectRoot can only be set in standalone mode.",
253
+ "config.selectRepoToUpdate": "Select a repository to update:",
254
+ "config.fullstackRepoRequired": "For fullstack projects, you must specify `--repo fe` or `--repo be`.",
255
+ "config.projectRootSet": "\u2705 {repo} projectRoot set: {path}",
256
+ "config.projectRootSetSingle": "\u2705 projectRoot set: {path}",
257
+ "update.start": "\u{1F4E6} Starting template update...",
258
+ "update.langLabel": "Lang",
259
+ "update.typeLabel": "Type",
260
+ "update.updatingAgents": "\u{1F4C1} Updating agents/ folder...",
261
+ "update.updatingSkills": "\u{1F4C1} Updating agents/skills folder...",
262
+ "update.agentsUpdated": "agents/ updated",
263
+ "update.skillsUpdated": "agents/skills updated",
264
+ "update.updatingFeatureBase": "\u{1F4C1} Updating features/feature-base/ folder...",
265
+ "update.filesUpdated": "{count} files updated",
266
+ "update.updatedTotal": "Updated {count} files!",
267
+ "update.changeDetected": "changes detected (use --force to overwrite)",
268
+ "update.fileUpdated": "{file} updated",
269
+ "doctor.title": "\u{1F50E} Docs Doctor",
270
+ "doctor.envWarnings": "\u26A0\uFE0F Environment warnings:",
271
+ "doctor.noIssues": "\u2705 No issues found.",
272
+ "doctor.errorsTitle": "Errors",
273
+ "doctor.warningsTitle": "Warnings",
274
+ "doctor.tipJson": "Tip: Agent JSON output: npx lee-spec-kit doctor --json{strictFlag}",
275
+ "doctor.issue.missingRequiredDir": "Missing required directory: {dir}",
276
+ "doctor.issue.missingConfig": "Missing .lee-spec-kit.json. Some commands may rely on folder-structure heuristics.",
277
+ "doctor.issue.noFeatures": "No feature folders found. (Only feature-base exists, or no features created yet.)",
278
+ "doctor.issue.placeholdersLeft": "Leftover placeholders detected: {placeholders}",
279
+ "doctor.issue.missingSpec": "Missing spec.md.",
280
+ "doctor.issue.specStatusUnset": "spec.md Status is not set. (May still be a template)",
281
+ "doctor.issue.planStatusUnset": "plan.md Status is not set. (May still be a template)",
282
+ "doctor.issue.tasksEmpty": "tasks.md has no tasks.",
283
+ "doctor.issue.duplicateFeatureId": "Duplicate Feature ID detected: {id} ({count})",
284
+ "doctor.issue.missingFeatureId": "Feature folder name is not in F001-... format. (Cannot extract ID)",
285
+ "context.noActiveFeatures": "\u26A0\uFE0F No active features found.",
286
+ "context.envWarnings": "\u26A0\uFE0F Environment warnings:",
287
+ "context.openFallbackSummary": "(Could not detect a feature from the branch, so showing only open features. In Progress: {inProgress} / Ready To Close: {readyToClose} / Done: {done})",
288
+ "context.sectionInProgress": "In Progress",
289
+ "context.sectionReadyToClose": "Ready To Close",
290
+ "context.tipDetails": "Tip: To view details for a feature:",
291
+ "context.tipShowAll": "Show all",
292
+ "context.tipShowDone": "Show done only",
293
+ "context.okRequired": "[OK required] ",
294
+ "context.list.docsCommitNeeded": "Commit docs changes",
295
+ "context.list.issueNumberNeeded": "Fill issue number in docs",
296
+ "context.list.addPrMetadata": "Add PR metadata (PR/PR Status)",
297
+ "context.list.recordPrLink": "Record PR link",
298
+ "context.list.setPrStatus": "Set PR Status",
299
+ "context.list.prStatusToApproved": "PR Status {status} \u2192 Approved",
300
+ "context.list.approveSpec": "Approve spec",
301
+ "context.list.approvePlan": "Approve plan",
302
+ "init.selectLangPrompt": "Select docs language:",
303
+ "init.currentDirectoryLabel": "\u{1F4CD} Current directory",
304
+ "init.gitDetected": "\u2705 Git repository detected",
305
+ "init.insideProjectRoot": "You are running inside your project root.",
306
+ "init.modeEmbeddedDesc": "\u2022 embedded: creates ./docs here and manages it with the project.",
307
+ "init.modeStandaloneDesc": "\u2022 standalone: to manage docs as a separate repo,",
308
+ "init.modeStandaloneMove": " move to that folder and run again.",
309
+ "init.gitNotDetected": "\u26A0\uFE0F Git repository not detected.",
310
+ "init.gitNotDetectedDetail": "A new Git repo will be initialized.",
311
+ "init.prompt.projectName": "Enter project name:",
312
+ "init.prompt.projectType": "Select project type:",
313
+ "init.choice.projectType.single.title": "Single - single repo project",
314
+ "init.choice.projectType.single.desc": "Manage with a single features/ folder",
315
+ "init.choice.projectType.fullstack.title": "Fullstack - split FE/BE repos",
316
+ "init.choice.projectType.fullstack.desc": "Manage with features/be/ and features/fe/",
317
+ "init.prompt.docsMode": "Select docs mode:",
318
+ "init.choice.docsRepo.embedded.title": "embedded - inside the project (./docs)",
319
+ "init.choice.docsRepo.embedded.desc": "Pushed together with the project",
320
+ "init.choice.docsRepo.standalone.title": "standalone - separate docs repo",
321
+ "init.choice.docsRepo.standalone.desc": "Configure push settings separately",
322
+ "init.prompt.feRepoPath": "Enter frontend repository path:",
323
+ "init.prompt.beRepoPath": "Enter backend repository path:",
324
+ "init.prompt.projectRepoPath": "Enter project repository path:",
325
+ "init.validation.enterPath": "Please enter a path",
326
+ "init.prompt.pushMode": "Select docs push mode:",
327
+ "init.choice.push.local": "local - manage locally (no push)",
328
+ "init.choice.push.remote": "remote - push to remote",
329
+ "init.prompt.remoteUrl": "Enter remote repository URL:",
330
+ "init.validation.enterUrl": "Please enter a URL",
331
+ "init.prompt.overwrite": "{dir} already exists. Overwrite?",
332
+ "init.log.creatingDocs": "\u{1F4C1} Creating docs structure...",
333
+ "init.log.projectLabel": "Project",
334
+ "init.log.typeLabel": "Type",
335
+ "init.log.langLabel": "Lang",
336
+ "init.log.pathLabel": "Path",
337
+ "init.log.docsCreated": "\u2705 Docs structure created!",
338
+ "init.log.nextStepsTitle": "Next steps:",
339
+ "init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
340
+ "init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
341
+ "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
342
+ "init.log.gitInit": "\u{1F4E6} Initializing Git...",
343
+ "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.)',
344
+ "init.warn.commitManually": " Review the changes and commit manually.",
345
+ "init.log.gitRemoteSet": "\u2705 Git remote set: {remote}",
346
+ "init.warn.gitRemoteExists": "\u26A0\uFE0F Git remote already exists.",
347
+ "init.log.gitInitialCommitDone": "\u2705 Initial Git commit created!",
348
+ "init.warn.skipGitInit": "\u26A0\uFE0F Skipping Git initialization (please commit manually)",
349
+ "init.error.templateNotFound": "Template not found: {path}"
350
+ },
351
+ steps: {
352
+ featureFolder: "Create feature folder",
353
+ specWrite: "Write spec.md",
354
+ specApprove: "Approve spec.md",
355
+ planWrite: "Write plan.md",
356
+ planApprove: "Approve plan.md",
357
+ tasksWrite: "Write tasks.md",
358
+ docsCommitPlanning: "Commit docs (sync)",
359
+ issueCreate: "Create GitHub Issue",
360
+ branchCreate: "Create branch",
361
+ tasksExecute: "Execute tasks",
362
+ prCreate: "Create PR",
363
+ codeReview: "Code review",
364
+ featureDone: "Feature done"
365
+ },
366
+ messages: {
367
+ specCreate: "Create spec.md by copying the template. (See features/feature-base/spec.md)",
368
+ specImprove: "Improve spec.md and change Status to Review.",
369
+ specApproval: "Share spec.md with the user and get approval (OK).",
370
+ planCreate: "Create plan.md by copying the template. (See features/feature-base/plan.md)",
371
+ planImprove: "Improve plan.md and change Status to Review.",
372
+ planApproval: "Share plan.md with the user and get approval (OK).",
373
+ tasksCreate: "Create tasks.md by copying the template. (See features/feature-base/tasks.md)",
374
+ tasksNeedAtLeastOne: "Write at least 1 task in tasks.md.",
375
+ docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
376
+ issueCreateAndWrite: "Create a GitHub Issue, fill the issue number in spec.md/tasks.md, then prepare a docs commit. (See skills/create-issue.md)",
377
+ docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
378
+ standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
379
+ createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
380
+ tasksAllDoneButNoChecklist: 'All tasks are DONE, but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
381
+ tasksAllDoneButChecklist: "All tasks are DONE, but the completion checklist is not fully checked. ({checked}/{total})",
382
+ finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
383
+ startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
384
+ checkTaskStatuses: "Check task statuses. ({done}/{total}) (See skills/execute-task.md)",
385
+ prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (OK required)",
386
+ prCreate: "Create a PR and record the PR link in tasks.md. (See skills/create-pr.md)",
387
+ prFillStatus: "Set PR Status in tasks.md to Draft/Review/Approved. (After merge, update it to Approved.)",
388
+ prResolveReview: "Resolve review comments and update PR Status. (PR Status: Review \u2192 Approved)",
389
+ prRequestReview: "Request review and update PR Status to Review.",
390
+ featureDone: "PR is Approved and all tasks/completion criteria are satisfied. This feature is done.",
391
+ fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
392
+ },
393
+ warnings: {
394
+ projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
395
+ docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
396
+ docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.)",
397
+ legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
398
+ workflowSpecNotApproved: "Implementation is done but spec.md Status is not Approved. (Update spec.md Status to Approved.)",
399
+ workflowPlanNotApproved: "Implementation is done but plan.md Status is not Approved. (Update plan.md Status to Approved.)",
400
+ workflowPrLinkMissing: "Implementation is done but PR link is missing. (Fill the PR field in tasks.md.)",
401
+ workflowPrStatusMissing: "Implementation is done but PR Status is missing. (Set PR Status to Draft/Review/Approved in tasks.md.)",
402
+ workflowPrStatusNotApproved: "Implementation is done but PR Status is not Approved. (After merge, update PR Status to Approved in tasks.md.)"
403
+ }
404
+ }
405
+ };
406
+ function tr(lang, category, key, vars = {}) {
407
+ const safeLang = normalizeLang(lang);
408
+ const template = I18N[safeLang]?.[category]?.[key] ?? I18N[DEFAULT_LANG]?.[category]?.[key] ?? I18N.ko?.[category]?.[key] ?? `${category}.${key}`;
409
+ return formatTemplate(template, vars);
410
+ }
411
+
46
412
  // src/utils/validation.ts
47
413
  var VALID_PROJECT_TYPES = ["single", "fullstack"];
48
414
  var VALID_LANGUAGES = ["ko", "en"];
@@ -149,15 +515,19 @@ function checkGitRepo(cwd) {
149
515
  }
150
516
  }
151
517
  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: ko)").option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
518
+ 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
519
  try {
154
520
  await runInit(options);
155
521
  } catch (error) {
156
522
  if (error instanceof Error && error.message === "canceled") {
157
- console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
523
+ const lang = options.lang ?? DEFAULT_LANG;
524
+ console.log(
525
+ chalk6.yellow(`
526
+ ${tr(lang, "cli", "common.canceled")}`)
527
+ );
158
528
  process.exit(0);
159
529
  }
160
- console.error(chalk6.red("\uC624\uB958:"), error);
530
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
161
531
  process.exit(1);
162
532
  }
163
533
  });
@@ -167,7 +537,7 @@ async function runInit(options) {
167
537
  const defaultName = path4.basename(cwd);
168
538
  let projectName = options.name || defaultName;
169
539
  let projectType = options.type;
170
- let lang = options.lang || "ko";
540
+ let lang = options.lang || "en";
171
541
  let docsRepo = "embedded";
172
542
  let pushDocs;
173
543
  let docsRemote;
@@ -175,24 +545,62 @@ async function runInit(options) {
175
545
  const targetDir = path4.resolve(cwd, options.dir || "./docs");
176
546
  const isInsideGitRepo = checkGitRepo(cwd);
177
547
  if (!options.yes) {
548
+ if (!options.lang) {
549
+ const langResponse = await prompts(
550
+ [
551
+ {
552
+ type: "select",
553
+ name: "lang",
554
+ message: tr(DEFAULT_LANG, "cli", "init.selectLangPrompt"),
555
+ choices: [
556
+ { title: "English (en)", value: "en" },
557
+ { title: "\uD55C\uAD6D\uC5B4 (ko)", value: "ko" }
558
+ ],
559
+ initial: 0
560
+ }
561
+ ],
562
+ {
563
+ onCancel: () => {
564
+ throw new Error("canceled");
565
+ }
566
+ }
567
+ );
568
+ lang = langResponse.lang || lang;
569
+ }
178
570
  console.log();
179
- console.log(chalk6.blue(`\u{1F4CD} \uD604\uC7AC \uC704\uCE58: ${cwd}`));
571
+ console.log(
572
+ chalk6.blue(`${tr(lang, "cli", "init.currentDirectoryLabel")}: ${cwd}`)
573
+ );
180
574
  if (isInsideGitRepo) {
181
- console.log(chalk6.green("\u2705 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0\uB428"));
575
+ console.log(chalk6.green(tr(lang, "cli", "init.gitDetected")));
182
576
  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
577
  console.log(
185
578
  chalk6.gray(
186
- "\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."
579
+ tr(lang, "cli", "init.insideProjectRoot")
187
580
  )
188
581
  );
189
582
  console.log(
190
- chalk6.gray("\u2022 standalone: \uBCC4\uB3C4 \uD3F4\uB354\uC5D0\uC11C \uB3C5\uB9BD docs \uB808\uD3EC\uB85C \uAD00\uB9AC\uD558\uB824\uBA74,")
583
+ chalk6.gray(
584
+ tr(lang, "cli", "init.modeEmbeddedDesc")
585
+ )
586
+ );
587
+ console.log(
588
+ chalk6.gray(
589
+ tr(lang, "cli", "init.modeStandaloneDesc")
590
+ )
591
+ );
592
+ console.log(
593
+ chalk6.gray(
594
+ tr(lang, "cli", "init.modeStandaloneMove")
595
+ )
191
596
  );
192
- console.log(chalk6.gray(" \uD574\uB2F9 \uD3F4\uB354\uB85C \uC774\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD574\uC8FC\uC138\uC694."));
193
597
  } else {
194
- console.log(chalk6.yellow("\u26A0\uFE0F Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4."));
195
- console.log(chalk6.gray("\uC0C8\uB85C\uC6B4 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC0DD\uC131\uB429\uB2C8\uB2E4."));
598
+ console.log(
599
+ chalk6.yellow(
600
+ tr(lang, "cli", "init.gitNotDetected")
601
+ )
602
+ );
603
+ console.log(chalk6.gray(tr(lang, "cli", "init.gitNotDetectedDetail")));
196
604
  }
197
605
  console.log();
198
606
  const response = await prompts(
@@ -200,51 +608,41 @@ async function runInit(options) {
200
608
  {
201
609
  type: options.name ? null : "text",
202
610
  name: "projectName",
203
- message: "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694:",
611
+ message: tr(lang, "cli", "init.prompt.projectName"),
204
612
  initial: defaultName
205
613
  },
206
614
  {
207
615
  type: options.type ? null : "select",
208
616
  name: "projectType",
209
- message: "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
617
+ message: tr(lang, "cli", "init.prompt.projectType"),
210
618
  choices: [
211
619
  {
212
- title: "Single - \uB2E8\uC77C \uB808\uD3EC \uD504\uB85C\uC81D\uD2B8",
620
+ title: tr(lang, "cli", "init.choice.projectType.single.title"),
213
621
  value: "single",
214
- description: "features/ \uD3F4\uB354 \uD558\uB098\uB85C \uAD00\uB9AC"
622
+ description: tr(lang, "cli", "init.choice.projectType.single.desc")
215
623
  },
216
624
  {
217
- title: "Fullstack - FE/BE \uBD84\uB9AC \uD504\uB85C\uC81D\uD2B8",
625
+ title: tr(lang, "cli", "init.choice.projectType.fullstack.title"),
218
626
  value: "fullstack",
219
- description: "features/be/, features/fe/ \uBD84\uB9AC \uAD00\uB9AC"
627
+ description: tr(lang, "cli", "init.choice.projectType.fullstack.desc")
220
628
  }
221
629
  ],
222
630
  initial: 0
223
631
  },
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
632
  {
235
633
  type: "select",
236
634
  name: "docsRepo",
237
- message: "Docs \uAD00\uB9AC \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
635
+ message: tr(lang, "cli", "init.prompt.docsMode"),
238
636
  choices: [
239
637
  {
240
- title: "embedded - \uD504\uB85C\uC81D\uD2B8 \uB0B4 \uD3EC\uD568 (./docs)",
638
+ title: tr(lang, "cli", "init.choice.docsRepo.embedded.title"),
241
639
  value: "embedded",
242
- description: "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4"
640
+ description: tr(lang, "cli", "init.choice.docsRepo.embedded.desc")
243
641
  },
244
642
  {
245
- title: "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
643
+ title: tr(lang, "cli", "init.choice.docsRepo.standalone.title"),
246
644
  value: "standalone",
247
- description: "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4"
645
+ description: tr(lang, "cli", "init.choice.docsRepo.standalone.desc")
248
646
  }
249
647
  ],
250
648
  initial: 0
@@ -258,7 +656,6 @@ async function runInit(options) {
258
656
  );
259
657
  projectName = response.projectName || projectName;
260
658
  projectType = response.projectType || projectType;
261
- lang = response.lang || lang;
262
659
  docsRepo = response.docsRepo || "embedded";
263
660
  if (docsRepo === "standalone") {
264
661
  const resolvedType = projectType || response.projectType || "single";
@@ -268,14 +665,14 @@ async function runInit(options) {
268
665
  {
269
666
  type: "text",
270
667
  name: "feRoot",
271
- message: "Frontend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
272
- validate: (value) => value.trim() ? true : "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694"
668
+ message: tr(lang, "cli", "init.prompt.feRepoPath"),
669
+ validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
273
670
  },
274
671
  {
275
672
  type: "text",
276
673
  name: "beRoot",
277
- message: "Backend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
278
- validate: (value) => value.trim() ? true : "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694"
674
+ message: tr(lang, "cli", "init.prompt.beRepoPath"),
675
+ validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
279
676
  }
280
677
  ],
281
678
  {
@@ -294,8 +691,8 @@ async function runInit(options) {
294
691
  {
295
692
  type: "text",
296
693
  name: "projectRoot",
297
- message: "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
298
- validate: (value) => value.trim() ? true : "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694"
694
+ message: tr(lang, "cli", "init.prompt.projectRepoPath"),
695
+ validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
299
696
  }
300
697
  ],
301
698
  {
@@ -311,14 +708,14 @@ async function runInit(options) {
311
708
  {
312
709
  type: "select",
313
710
  name: "pushDocs",
314
- message: "Docs push \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
711
+ message: tr(lang, "cli", "init.prompt.pushMode"),
315
712
  choices: [
316
713
  {
317
- title: "local - \uB85C\uCEEC\uC5D0\uC11C\uB9CC \uAD00\uB9AC (push \uC548 \uD568)",
714
+ title: tr(lang, "cli", "init.choice.push.local"),
318
715
  value: false
319
716
  },
320
717
  {
321
- title: "remote - \uC6D0\uACA9\uC5D0\uB3C4 push",
718
+ title: tr(lang, "cli", "init.choice.push.remote"),
322
719
  value: true
323
720
  }
324
721
  ],
@@ -338,8 +735,8 @@ async function runInit(options) {
338
735
  {
339
736
  type: "text",
340
737
  name: "docsRemote",
341
- message: "\uC6D0\uACA9 \uB808\uD3EC URL\uC744 \uC785\uB825\uD558\uC138\uC694:",
342
- validate: (value) => value.trim() ? true : "URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694"
738
+ message: tr(lang, "cli", "init.prompt.remoteUrl"),
739
+ validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterUrl")
343
740
  }
344
741
  ],
345
742
  {
@@ -364,21 +761,27 @@ async function runInit(options) {
364
761
  const { overwrite } = await prompts({
365
762
  type: "confirm",
366
763
  name: "overwrite",
367
- message: `${targetDir} \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`,
764
+ message: tr(lang, "cli", "init.prompt.overwrite", { dir: targetDir }),
368
765
  initial: false
369
766
  });
370
767
  if (!overwrite) {
371
- console.log(chalk6.yellow("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
768
+ console.log(chalk6.yellow(tr(lang, "cli", "common.canceled")));
372
769
  return;
373
770
  }
374
771
  }
375
772
  }
376
773
  console.log();
377
- console.log(chalk6.blue("\u{1F4C1} docs \uAD6C\uC870 \uC0DD\uC131 \uC911..."));
378
- console.log(chalk6.gray(` \uD504\uB85C\uC81D\uD2B8: ${projectName}`));
379
- console.log(chalk6.gray(` \uD0C0\uC785: ${projectType}`));
380
- console.log(chalk6.gray(` \uC5B8\uC5B4: ${lang}`));
381
- console.log(chalk6.gray(` \uACBD\uB85C: ${targetDir}`));
774
+ console.log(chalk6.blue(tr(lang, "cli", "init.log.creatingDocs")));
775
+ console.log(
776
+ chalk6.gray(` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`)
777
+ );
778
+ console.log(
779
+ chalk6.gray(` ${tr(lang, "cli", "init.log.typeLabel")}: ${projectType}`)
780
+ );
781
+ console.log(chalk6.gray(` ${tr(lang, "cli", "init.log.langLabel")}: ${lang}`));
782
+ console.log(
783
+ chalk6.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
784
+ );
382
785
  console.log();
383
786
  const templatesDir = getTemplatesDir();
384
787
  const commonPath = path4.join(templatesDir, lang, "common");
@@ -387,7 +790,7 @@ async function runInit(options) {
387
790
  await copyTemplates(commonPath, targetDir);
388
791
  }
389
792
  if (!await fs8.pathExists(typePath)) {
390
- throw new Error(`\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${typePath}`);
793
+ throw new Error(tr(lang, "cli", "init.error.templateNotFound", { path: typePath }));
391
794
  }
392
795
  await copyTemplates(typePath, targetDir);
393
796
  const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
@@ -415,17 +818,17 @@ async function runInit(options) {
415
818
  }
416
819
  const configPath = path4.join(targetDir, ".lee-spec-kit.json");
417
820
  await fs8.writeJson(configPath, config, { spaces: 2 });
418
- console.log(chalk6.green("\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!"));
821
+ console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
419
822
  console.log();
420
- await initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote);
421
- console.log(chalk6.blue("\uB2E4\uC74C \uB2E8\uACC4:"));
422
- console.log(chalk6.gray(` 1. ${targetDir}/prd/README.md \uC791\uC131`));
823
+ await initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote);
824
+ console.log(chalk6.blue(tr(lang, "cli", "init.log.nextStepsTitle")));
423
825
  console.log(
424
- chalk6.gray(" 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00")
826
+ chalk6.gray(tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir }))
425
827
  );
828
+ console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps2")));
426
829
  console.log();
427
830
  }
428
- async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
831
+ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
429
832
  try {
430
833
  const runGit = (args, workdir) => {
431
834
  execFileSync("git", args, { cwd: workdir, stdio: "ignore" });
@@ -445,9 +848,9 @@ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
445
848
  };
446
849
  try {
447
850
  runGit(["rev-parse", "--is-inside-work-tree"], cwd);
448
- console.log(chalk6.blue("\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911..."));
851
+ console.log(chalk6.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit")));
449
852
  } catch {
450
- console.log(chalk6.blue("\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911..."));
853
+ console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
451
854
  runGit(["init"], cwd);
452
855
  }
453
856
  const relativePath = path4.relative(cwd, targetDir);
@@ -455,10 +858,10 @@ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
455
858
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
456
859
  console.log(
457
860
  chalk6.yellow(
458
- '\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)'
861
+ tr(lang, "cli", "init.warn.stagedChangesSkip")
459
862
  )
460
863
  );
461
- console.log(chalk6.gray(" \uC218\uB3D9\uC73C\uB85C \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD55C \uB4A4 \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694."));
864
+ console.log(chalk6.gray(tr(lang, "cli", "init.warn.commitManually")));
462
865
  console.log();
463
866
  return;
464
867
  }
@@ -470,16 +873,18 @@ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
470
873
  if (docsRepo === "standalone" && pushDocs && docsRemote) {
471
874
  try {
472
875
  runGit(["remote", "add", "origin", docsRemote], cwd);
473
- console.log(chalk6.green(`\u2705 Git remote \uC124\uC815 \uC644\uB8CC: ${docsRemote}`));
876
+ console.log(
877
+ chalk6.green(tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote }))
878
+ );
474
879
  } catch {
475
- console.log(chalk6.yellow("\u26A0\uFE0F Git remote\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4."));
880
+ console.log(chalk6.yellow(tr(lang, "cli", "init.warn.gitRemoteExists")));
476
881
  }
477
882
  }
478
- console.log(chalk6.green("\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!"));
883
+ console.log(chalk6.green(tr(lang, "cli", "init.log.gitInitialCommitDone")));
479
884
  console.log();
480
885
  } catch {
481
886
  console.log(
482
- chalk6.yellow("\u26A0\uFE0F Git \uCD08\uAE30\uD654\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4 (\uC218\uB3D9\uC73C\uB85C \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694)")
887
+ chalk6.yellow(tr(lang, "cli", "init.warn.skipGitInit"))
483
888
  );
484
889
  console.log();
485
890
  }
@@ -536,12 +941,10 @@ async function getConfig(cwd) {
536
941
  const fePath = path4.join(featuresPath, "fe");
537
942
  const projectType = await fs8.pathExists(bePath) || await fs8.pathExists(fePath) ? "fullstack" : "single";
538
943
  const agentsMdPath = path4.join(agentsPath, "agents.md");
539
- let lang = "ko";
944
+ let lang = "en";
540
945
  if (await fs8.pathExists(agentsMdPath)) {
541
946
  const content = await fs8.readFile(agentsMdPath, "utf-8");
542
- if (!/[가-힣]/.test(content)) {
543
- lang = "en";
544
- }
947
+ if (/[가-힣]/.test(content)) lang = "ko";
545
948
  }
546
949
  return { docsDir: resolvedDocsDir, projectType, lang };
547
950
  }
@@ -557,10 +960,13 @@ function featureCommand(program2) {
557
960
  await runFeature(name, options);
558
961
  } catch (error) {
559
962
  if (error instanceof Error && error.message === "canceled") {
560
- console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
963
+ const config = await getConfig(process.cwd());
964
+ const lang = config?.lang ?? DEFAULT_LANG;
965
+ console.log(chalk6.yellow(`
966
+ ${tr(lang, "cli", "common.canceled")}`));
561
967
  process.exit(0);
562
968
  }
563
- console.error(chalk6.red("\uC624\uB958:"), error);
969
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
564
970
  process.exit(1);
565
971
  }
566
972
  });
@@ -570,7 +976,9 @@ async function runFeature(name, options) {
570
976
  const config = await getConfig(cwd);
571
977
  if (!config) {
572
978
  console.error(
573
- chalk6.red("docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.")
979
+ chalk6.red(
980
+ tr(DEFAULT_LANG, "cli", "common.docsNotFound")
981
+ )
574
982
  );
575
983
  process.exit(1);
576
984
  }
@@ -582,7 +990,7 @@ async function runFeature(name, options) {
582
990
  {
583
991
  type: "select",
584
992
  name: "repo",
585
- message: "\uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
993
+ message: tr(lang, "cli", "feature.selectRepo"),
586
994
  choices: [
587
995
  { title: "Backend (be)", value: "be" },
588
996
  { title: "Frontend (fe)", value: "fe" }
@@ -615,12 +1023,20 @@ async function runFeature(name, options) {
615
1023
  const featureFolderName = `${featureId}-${name}`;
616
1024
  const featureDir = path4.join(featuresDir, featureFolderName);
617
1025
  if (await fs8.pathExists(featureDir)) {
618
- console.error(chalk6.red(`\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: ${featureDir}`));
1026
+ console.error(
1027
+ chalk6.red(
1028
+ tr(lang, "cli", "feature.folderExists", { path: featureDir })
1029
+ )
1030
+ );
619
1031
  process.exit(1);
620
1032
  }
621
1033
  const featureBasePath = path4.join(docsDir, "features", "feature-base");
622
1034
  if (!await fs8.pathExists(featureBasePath)) {
623
- console.error(chalk6.red("feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
1035
+ console.error(
1036
+ chalk6.red(
1037
+ tr(lang, "cli", "feature.baseNotFound")
1038
+ )
1039
+ );
624
1040
  process.exit(1);
625
1041
  }
626
1042
  await fs8.copy(featureBasePath, featureDir);
@@ -650,12 +1066,24 @@ async function runFeature(name, options) {
650
1066
  }
651
1067
  await replaceInFiles(featureDir, replacements);
652
1068
  console.log();
653
- console.log(chalk6.green(`\u2705 Feature \uD3F4\uB354 \uC0DD\uC131 \uC644\uB8CC: ${featureDir}`));
1069
+ console.log(
1070
+ chalk6.green(
1071
+ tr(lang, "cli", "feature.created", { path: featureDir })
1072
+ )
1073
+ );
654
1074
  console.log();
655
- console.log(chalk6.blue("\uB2E4\uC74C \uB2E8\uACC4:"));
656
- console.log(chalk6.gray(` 1. ${featureDir}/spec.md \uC791\uC131`));
657
- console.log(chalk6.gray(" 2. \uC0AC\uC6A9\uC790 \uB9AC\uBDF0 \uC694\uCCAD"));
658
- console.log(chalk6.gray(" 3. \uC2B9\uC778 \uD6C4 plan.md \uC791\uC131"));
1075
+ console.log(chalk6.blue(tr(lang, "cli", "feature.nextStepsTitle")));
1076
+ console.log(
1077
+ chalk6.gray(
1078
+ tr(lang, "cli", "feature.nextSteps1", { path: featureDir })
1079
+ )
1080
+ );
1081
+ console.log(chalk6.gray(tr(lang, "cli", "feature.nextSteps2")));
1082
+ console.log(
1083
+ chalk6.gray(
1084
+ tr(lang, "cli", "feature.nextSteps3")
1085
+ )
1086
+ );
659
1087
  console.log();
660
1088
  }
661
1089
  async function getNextFeatureId(docsDir, projectType) {
@@ -685,128 +1113,6 @@ async function getNextFeatureId(docsDir, projectType) {
685
1113
  return `F${String(next).padStart(width, "0")}`;
686
1114
  }
687
1115
 
688
- // src/utils/context/i18n.ts
689
- function formatTemplate(template, vars) {
690
- return template.replace(/\{(\w+)\}/g, (_, key) => {
691
- const value = vars[key];
692
- return value === void 0 ? `{${key}}` : String(value);
693
- });
694
- }
695
- var I18N = {
696
- ko: {
697
- steps: {
698
- featureFolder: "Feature \uD3F4\uB354 \uC0DD\uC131",
699
- specWrite: "spec.md \uC791\uC131",
700
- specApprove: "spec.md \uC2B9\uC778",
701
- planWrite: "plan.md \uC791\uC131",
702
- planApprove: "plan.md \uC2B9\uC778",
703
- tasksWrite: "tasks.md \uC791\uC131",
704
- docsCommitPlanning: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
705
- issueCreate: "GitHub Issue \uC0DD\uC131",
706
- branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
707
- tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
708
- prCreate: "PR \uC0DD\uC131",
709
- codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
710
- featureDone: "Feature \uC644\uB8CC"
711
- },
712
- messages: {
713
- specCreate: "spec.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/spec.md \uCC38\uACE0)",
714
- specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
715
- specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
716
- planCreate: "plan.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/plan.md \uCC38\uACE0)",
717
- planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
718
- planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
719
- 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)",
720
- tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
721
- docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
722
- 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)",
723
- docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
724
- standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
725
- createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
726
- 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.',
727
- 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})",
728
- 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)',
729
- startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
730
- checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)",
731
- 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)",
732
- 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)",
733
- 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)",
734
- 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)",
735
- 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.",
736
- 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.",
737
- 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."
738
- },
739
- warnings: {
740
- 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.)",
741
- 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)",
742
- 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.",
743
- 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.)",
744
- 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.)",
745
- workflowPrLinkMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uB9C1\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. (tasks.md\uC758 PR \uD544\uB4DC\uB97C \uCC44\uC6B0\uC138\uC694.)",
746
- workflowPrStatusMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. (tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Draft/Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694.)",
747
- workflowPrStatusNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (merge \uD6C4 tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)"
748
- }
749
- },
750
- en: {
751
- steps: {
752
- featureFolder: "Create feature folder",
753
- specWrite: "Write spec.md",
754
- specApprove: "Approve spec.md",
755
- planWrite: "Write plan.md",
756
- planApprove: "Approve plan.md",
757
- tasksWrite: "Write tasks.md",
758
- docsCommitPlanning: "Commit docs (sync)",
759
- issueCreate: "Create GitHub Issue",
760
- branchCreate: "Create branch",
761
- tasksExecute: "Execute tasks",
762
- prCreate: "Create PR",
763
- codeReview: "Code review",
764
- featureDone: "Feature done"
765
- },
766
- messages: {
767
- specCreate: "Copy the spec.md template and write it. (See features/feature-base/spec.md)",
768
- specImprove: "Improve spec.md and set Status to Review.",
769
- specApproval: "Share spec.md with the user and get approval (OK).",
770
- planCreate: "Copy the plan.md template and write it. (See features/feature-base/plan.md)",
771
- planImprove: "Improve plan.md and set Status to Review.",
772
- planApproval: "Share plan.md with the user and get approval (OK).",
773
- tasksCreate: "Copy the tasks.md template and write tasks. (See features/feature-base/tasks.md)",
774
- tasksNeedAtLeastOne: "Add at least one task to tasks.md.",
775
- docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
776
- 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)",
777
- docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
778
- standaloneNeedsProjectRoot: "In standalone mode, projectRoot is required. (npx lee-spec-kit config --project-root ...)",
779
- createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
780
- tasksAllDoneButNoChecklist: 'All tasks are DONE but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
781
- tasksAllDoneButChecklist: "All tasks are DONE but the completion checklist is not fully checked. ({checked}/{total})",
782
- finishDoingTask: 'Finish the active DOING/REVIEW task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
783
- startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
784
- checkTaskStatuses: "Check task statuses. ({done}/{total}) (See skills/execute-task.md)",
785
- prLegacyAsk: "Legacy tasks.md format detected (missing PR/PR Status fields). Update to the latest format? (OK required)",
786
- prCreate: "Create a PR and record the PR link in tasks.md. (See skills/create-pr.md)",
787
- prFillStatus: "Set PR Status in tasks.md to Draft/Review/Approved. (After merge, update to Approved)",
788
- prResolveReview: "Resolve review comments and update PR status. (PR Status: Review \u2192 Approved)",
789
- prRequestReview: "Request reviews and update PR status to Review.",
790
- featureDone: "PR is Approved and all tasks/completion criteria are satisfied. This feature is done.",
791
- fallbackRerunContext: "Unable to determine current state. Verify docs and run context again."
792
- },
793
- warnings: {
794
- projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
795
- docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
796
- legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
797
- workflowSpecNotApproved: "Implementation is done but spec.md Status is not Approved. (Update spec.md Status to Approved.)",
798
- workflowPlanNotApproved: "Implementation is done but plan.md Status is not Approved. (Update plan.md Status to Approved.)",
799
- workflowPrLinkMissing: "Implementation is done but PR link is missing. (Fill the PR field in tasks.md.)",
800
- workflowPrStatusMissing: "Implementation is done but PR Status is missing. (Set PR Status to Draft/Review/Approved in tasks.md.)",
801
- workflowPrStatusNotApproved: "Implementation is done but PR Status is not Approved. (After merge, update PR Status to Approved in tasks.md.)"
802
- }
803
- }
804
- };
805
- function tr(lang, category, key, vars = {}) {
806
- const template = I18N[lang][category][key] ?? I18N.ko[category][key] ?? `${category}.${key}`;
807
- return formatTemplate(template, vars);
808
- }
809
-
810
1116
  // src/utils/context/steps.ts
811
1117
  function isCompletionChecklistDone(feature) {
812
1118
  return !!feature.completionChecklist && feature.completionChecklist.total > 0 && feature.completionChecklist.checked === feature.completionChecklist.total;
@@ -893,21 +1199,12 @@ function getStepDefinitions(lang) {
893
1199
  step: 6,
894
1200
  name: tr(lang, "steps", "tasksWrite"),
895
1201
  checklist: {
896
- done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.docs.prFieldExists && f.docs.prStatusFieldExists,
1202
+ done: (f) => f.docs.tasksExists && f.tasks.total > 0,
897
1203
  detail: (f) => f.tasks.total > 0 ? `(${f.tasks.total})` : ""
898
1204
  },
899
1205
  current: {
900
- when: (f) => f.planStatus === "Approved" && (!f.docs.tasksExists || f.tasks.total === 0 || f.docs.tasksExists && f.tasks.total > 0 && (!f.docs.prFieldExists || !f.docs.prStatusFieldExists)),
1206
+ when: (f) => f.planStatus === "Approved" && (!f.docs.tasksExists || f.tasks.total === 0),
901
1207
  actions: (f) => {
902
- if (f.docs.tasksExists && f.tasks.total > 0 && (!f.docs.prFieldExists || !f.docs.prStatusFieldExists)) {
903
- return [
904
- {
905
- type: "instruction",
906
- requiresUserOk: true,
907
- message: tr(lang, "messages", "prLegacyAsk")
908
- }
909
- ];
910
- }
911
1208
  if (!f.docs.tasksExists) {
912
1209
  return [
913
1210
  {
@@ -929,10 +1226,10 @@ function getStepDefinitions(lang) {
929
1226
  step: 7,
930
1227
  name: tr(lang, "steps", "docsCommitPlanning"),
931
1228
  checklist: {
932
- done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.git.docsHasUncommittedChanges
1229
+ done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && f.git.docsEverCommitted
933
1230
  },
934
1231
  current: {
935
- when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.activeTask && f.git.docsHasUncommittedChanges,
1232
+ when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.activeTask && !f.git.docsEverCommitted && f.git.docsHasUncommittedChanges,
936
1233
  actions: (f) => {
937
1234
  if (f.issueNumber) {
938
1235
  return [
@@ -988,9 +1285,9 @@ function getStepDefinitions(lang) {
988
1285
  {
989
1286
  step: 9,
990
1287
  name: tr(lang, "steps", "branchCreate"),
991
- checklist: { done: (f) => f.git.onExpectedBranch },
1288
+ checklist: { done: (f) => f.git.onExpectedBranch || isImplementationDone(f) || isFeatureDone(f) },
992
1289
  current: {
993
- when: (f) => !!f.issueNumber && !isImplementationDone(f) && !isFeatureDone(f) && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
1290
+ when: (f) => !!f.issueNumber && f.tasks.total > 0 && f.tasks.done < f.tasks.total && !isFeatureDone(f) && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
994
1291
  actions: (f) => {
995
1292
  if (!f.git.projectBranchAvailable || !f.git.projectGitCwd) {
996
1293
  return [
@@ -1023,10 +1320,10 @@ function getStepDefinitions(lang) {
1023
1320
  detail: (f) => f.tasks.total > 0 ? `(${f.tasks.done}/${f.tasks.total})` : ""
1024
1321
  },
1025
1322
  current: {
1026
- when: (f) => f.git.onExpectedBranch && f.docs.tasksExists && f.tasks.total > 0 && (f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)),
1323
+ when: (f) => f.docs.tasksExists && f.tasks.total > 0 && (f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)) && (f.git.onExpectedBranch || f.tasks.done === f.tasks.total),
1027
1324
  actions: (f) => {
1028
1325
  if (f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f)) {
1029
- return [
1326
+ const actions = [
1030
1327
  {
1031
1328
  type: "instruction",
1032
1329
  requiresUserOk: true,
@@ -1036,6 +1333,14 @@ function getStepDefinitions(lang) {
1036
1333
  })
1037
1334
  }
1038
1335
  ];
1336
+ if (!isPrMetadataConfigured(f)) {
1337
+ actions.push({
1338
+ type: "instruction",
1339
+ requiresUserOk: true,
1340
+ message: tr(lang, "messages", "prLegacyAsk")
1341
+ });
1342
+ }
1343
+ return actions;
1039
1344
  }
1040
1345
  if (f.activeTask) {
1041
1346
  return [
@@ -1208,6 +1513,18 @@ function getGitStatusPorcelain(cwd, relativePaths) {
1208
1513
  return void 0;
1209
1514
  }
1210
1515
  }
1516
+ function getLastCommitForPath(cwd, relativePath) {
1517
+ try {
1518
+ const out = execSync(`git rev-list -n 1 HEAD -- "${relativePath}"`, {
1519
+ cwd,
1520
+ encoding: "utf-8",
1521
+ stdio: ["ignore", "pipe", "pipe"]
1522
+ }).trim();
1523
+ return out || void 0;
1524
+ } catch {
1525
+ return void 0;
1526
+ }
1527
+ }
1211
1528
  function getGitTopLevel(cwd) {
1212
1529
  try {
1213
1530
  return execSync("git rev-parse --show-toplevel", {
@@ -1420,12 +1737,20 @@ async function parseFeature(featurePath, type, context, options) {
1420
1737
  const relativeFeaturePathFromDocs = path4.relative(context.docsDir, featurePath);
1421
1738
  const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [relativeFeaturePathFromDocs]);
1422
1739
  const docsHasUncommittedChanges = docsStatus === void 0 ? true : docsStatus.trim().length > 0;
1740
+ const docsLastCommit = getLastCommitForPath(
1741
+ context.docsGitCwd,
1742
+ relativeFeaturePathFromDocs
1743
+ );
1744
+ const docsEverCommitted = !!docsLastCommit;
1423
1745
  if (docsStatus === void 0) {
1424
1746
  warnings.push(tr(lang, "warnings", "docsGitUnavailable"));
1425
1747
  }
1426
1748
  if (tasksExists && (!prFieldExists || !prStatusFieldExists)) {
1427
1749
  warnings.push(tr(lang, "warnings", "legacyTasksPrFields"));
1428
1750
  }
1751
+ if (docsEverCommitted && docsHasUncommittedChanges) {
1752
+ warnings.push(tr(lang, "warnings", "docsUncommittedChanges"));
1753
+ }
1429
1754
  const implementationDone = tasksExists && tasksSummary.total > 0 && tasksSummary.total === tasksSummary.done && isCompletionChecklistDone2({ completionChecklist });
1430
1755
  const workflowDone = implementationDone && specStatus === "Approved" && planStatus === "Approved" && isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink && prStatus === "Approved";
1431
1756
  if (implementationDone && !workflowDone) {
@@ -1468,6 +1793,7 @@ async function parseFeature(featurePath, type, context, options) {
1468
1793
  docsGitCwd: context.docsGitCwd,
1469
1794
  projectGitCwd: context.projectGitCwd,
1470
1795
  onExpectedBranch,
1796
+ docsEverCommitted,
1471
1797
  docsHasUncommittedChanges
1472
1798
  },
1473
1799
  docs: {
@@ -1594,7 +1920,7 @@ function statusCommand(program2) {
1594
1920
  try {
1595
1921
  await runStatus(options);
1596
1922
  } catch (error) {
1597
- console.error(chalk6.red("\uC624\uB958:"), error);
1923
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
1598
1924
  process.exit(1);
1599
1925
  }
1600
1926
  });
@@ -1603,12 +1929,15 @@ async function runStatus(options) {
1603
1929
  const cwd = process.cwd();
1604
1930
  const config = await getConfig(cwd);
1605
1931
  if (!config) {
1932
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")));
1606
1933
  console.error(
1607
- chalk6.red("docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.")
1934
+ chalk6.red(
1935
+ tr(DEFAULT_LANG, "cli", "common.docsNotFound")
1936
+ )
1608
1937
  );
1609
1938
  process.exit(1);
1610
1939
  }
1611
- const { docsDir, projectType, projectName } = config;
1940
+ const { docsDir, projectType, projectName, lang } = config;
1612
1941
  const featuresDir = path4.join(docsDir, "features");
1613
1942
  const scan = await scanFeatures(config);
1614
1943
  const features = [];
@@ -1645,7 +1974,7 @@ async function runStatus(options) {
1645
1974
  });
1646
1975
  }
1647
1976
  if (features.length === 0) {
1648
- console.log(chalk6.yellow("Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
1977
+ console.log(chalk6.yellow(tr(lang, "cli", "status.noFeatures")));
1649
1978
  return;
1650
1979
  }
1651
1980
  if (options.strict) {
@@ -1653,7 +1982,7 @@ async function runStatus(options) {
1653
1982
  ([, paths]) => paths.length > 1
1654
1983
  );
1655
1984
  if (duplicates.length > 0) {
1656
- console.error(chalk6.red("\uC911\uBCF5 Feature ID \uBC1C\uACAC:"));
1985
+ console.error(chalk6.red(tr(lang, "cli", "status.duplicateIds")));
1657
1986
  for (const [id, paths] of duplicates) {
1658
1987
  console.error(chalk6.red(` ${id}:`));
1659
1988
  for (const p of paths) {
@@ -1664,7 +1993,7 @@ async function runStatus(options) {
1664
1993
  }
1665
1994
  const unknowns = [...idMap.entries()].filter(([id]) => id === "UNKNOWN");
1666
1995
  if (unknowns.length > 0) {
1667
- console.error(chalk6.red("Feature ID\uAC00 \uC5C6\uB294 \uD56D\uBAA9:"));
1996
+ console.error(chalk6.red(tr(lang, "cli", "status.missingIds")));
1668
1997
  for (const [, paths] of unknowns) {
1669
1998
  for (const p of paths) {
1670
1999
  console.error(chalk6.red(` - ${p}`));
@@ -1703,7 +2032,11 @@ async function runStatus(options) {
1703
2032
  ""
1704
2033
  ].join("\n");
1705
2034
  await fs8.writeFile(outputPath, content, "utf-8");
1706
- console.log(chalk6.green(`\u2705 ${outputPath} \uC0DD\uC131 \uC644\uB8CC`));
2035
+ console.log(
2036
+ chalk6.green(
2037
+ tr(lang, "cli", "status.wrote", { path: outputPath })
2038
+ )
2039
+ );
1707
2040
  }
1708
2041
  }
1709
2042
  function escapeRegExp2(value) {
@@ -1729,15 +2062,18 @@ async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderNa
1729
2062
  return fallbackSlug || fallbackFolderName;
1730
2063
  }
1731
2064
  function updateCommand(program2) {
1732
- 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) => {
2065
+ program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--skills", "Update agents/skills folder only").option("--templates", "Update feature-base/ folder only").option("-f, --force", "Force overwrite without confirmation").action(async (options) => {
1733
2066
  try {
1734
2067
  await runUpdate(options);
1735
2068
  } catch (error) {
1736
2069
  if (error instanceof Error && error.message === "canceled") {
1737
- console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2070
+ const config = await getConfig(process.cwd());
2071
+ const lang = config?.lang ?? DEFAULT_LANG;
2072
+ console.log(chalk6.yellow(`
2073
+ ${tr(lang, "cli", "common.canceled")}`));
1738
2074
  process.exit(0);
1739
2075
  }
1740
- console.error(chalk6.red("\uC624\uB958:"), error);
2076
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
1741
2077
  process.exit(1);
1742
2078
  }
1743
2079
  });
@@ -1746,26 +2082,40 @@ async function runUpdate(options) {
1746
2082
  const cwd = process.cwd();
1747
2083
  const config = await getConfig(cwd);
1748
2084
  if (!config) {
2085
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")));
1749
2086
  console.error(
1750
- chalk6.red("docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.")
2087
+ chalk6.red(
2088
+ tr(DEFAULT_LANG, "cli", "common.docsNotFound")
2089
+ )
1751
2090
  );
1752
2091
  process.exit(1);
1753
2092
  }
1754
2093
  const { docsDir, projectType, lang } = config;
1755
2094
  const templatesDir = getTemplatesDir();
1756
2095
  const sourceDir = path4.join(templatesDir, lang, projectType);
1757
- const updateAgents = options.agents || !options.agents && !options.templates;
1758
- const updateTemplates = options.templates || !options.agents && !options.templates;
1759
- console.log(chalk6.blue("\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4..."));
1760
- console.log(chalk6.gray(` - \uC5B8\uC5B4: ${lang}`));
1761
- console.log(chalk6.gray(` - \uD0C0\uC785: ${projectType}`));
2096
+ const hasExplicitSelection = !!(options.agents || options.skills || options.templates);
2097
+ const updateAgents = options.agents || options.skills || !hasExplicitSelection;
2098
+ const updateTemplates = options.templates || !hasExplicitSelection;
2099
+ const agentsMode = options.skills && !options.agents ? "skills" : "all";
2100
+ console.log(chalk6.blue(tr(lang, "cli", "update.start")));
2101
+ console.log(chalk6.gray(` - ${tr(lang, "cli", "update.langLabel")}: ${lang}`));
2102
+ console.log(
2103
+ chalk6.gray(` - ${tr(lang, "cli", "update.typeLabel")}: ${projectType}`)
2104
+ );
1762
2105
  console.log();
1763
2106
  let updatedCount = 0;
1764
2107
  if (updateAgents) {
1765
- console.log(chalk6.blue("\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
1766
- const commonAgents = path4.join(templatesDir, lang, "common", "agents");
1767
- const typeAgents = path4.join(templatesDir, lang, projectType, "agents");
1768
- const targetAgents = path4.join(docsDir, "agents");
2108
+ console.log(
2109
+ chalk6.blue(
2110
+ agentsMode === "skills" ? tr(lang, "cli", "update.updatingSkills") : tr(lang, "cli", "update.updatingAgents")
2111
+ )
2112
+ );
2113
+ const commonAgentsBase = path4.join(templatesDir, lang, "common", "agents");
2114
+ const typeAgentsBase = path4.join(templatesDir, lang, projectType, "agents");
2115
+ const targetAgentsBase = path4.join(docsDir, "agents");
2116
+ const commonAgents = agentsMode === "skills" ? path4.join(commonAgentsBase, "skills") : commonAgentsBase;
2117
+ const typeAgents = agentsMode === "skills" ? path4.join(typeAgentsBase, "skills") : typeAgentsBase;
2118
+ const targetAgents = agentsMode === "skills" ? path4.join(targetAgentsBase, "skills") : targetAgentsBase;
1769
2119
  const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
1770
2120
  const replacements = {
1771
2121
  "{{featurePath}}": featurePath
@@ -1775,34 +2125,55 @@ async function runUpdate(options) {
1775
2125
  commonAgents,
1776
2126
  targetAgents,
1777
2127
  options.force,
1778
- replacements
2128
+ replacements,
2129
+ lang
1779
2130
  );
1780
2131
  updatedCount += count;
1781
2132
  }
1782
2133
  if (await fs8.pathExists(typeAgents)) {
1783
- const count = await updateFolder(typeAgents, targetAgents, options.force);
2134
+ const count = await updateFolder(
2135
+ typeAgents,
2136
+ targetAgents,
2137
+ options.force,
2138
+ void 0,
2139
+ lang
2140
+ );
1784
2141
  updatedCount += count;
1785
2142
  }
1786
- console.log(chalk6.green(` \u2705 agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
2143
+ console.log(
2144
+ chalk6.green(
2145
+ ` \u2705 ${agentsMode === "skills" ? tr(lang, "cli", "update.skillsUpdated") : tr(lang, "cli", "update.agentsUpdated")}`
2146
+ )
2147
+ );
1787
2148
  }
1788
2149
  if (updateTemplates) {
1789
- console.log(chalk6.blue("\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
2150
+ console.log(chalk6.blue(tr(lang, "cli", "update.updatingFeatureBase")));
1790
2151
  const sourceFeatureBase = path4.join(sourceDir, "features", "feature-base");
1791
2152
  const targetFeatureBase = path4.join(docsDir, "features", "feature-base");
1792
2153
  if (await fs8.pathExists(sourceFeatureBase)) {
1793
2154
  const count = await updateFolder(
1794
2155
  sourceFeatureBase,
1795
2156
  targetFeatureBase,
1796
- options.force
2157
+ options.force,
2158
+ void 0,
2159
+ lang
1797
2160
  );
1798
2161
  updatedCount += count;
1799
- console.log(chalk6.green(` \u2705 ${count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
2162
+ console.log(
2163
+ chalk6.green(
2164
+ ` \u2705 ${tr(lang, "cli", "update.filesUpdated", { count })}`
2165
+ )
2166
+ );
1800
2167
  }
1801
2168
  }
1802
2169
  console.log();
1803
- console.log(chalk6.green(`\u2705 \uCD1D ${updatedCount}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
2170
+ console.log(
2171
+ chalk6.green(
2172
+ `\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`
2173
+ )
2174
+ );
1804
2175
  }
1805
- async function updateFolder(sourceDir, targetDir, force, replacements) {
2176
+ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG) {
1806
2177
  const protectedFiles = /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
1807
2178
  await fs8.ensureDir(targetDir);
1808
2179
  const files = await fs8.readdir(sourceDir);
@@ -1829,14 +2200,18 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
1829
2200
  }
1830
2201
  if (!force) {
1831
2202
  console.log(
1832
- chalk6.yellow(` \u26A0\uFE0F ${file} - \uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)`)
2203
+ chalk6.yellow(
2204
+ ` \u26A0\uFE0F ${file} - ${tr(lang, "cli", "update.changeDetected")}`
2205
+ )
1833
2206
  );
1834
2207
  shouldUpdate = false;
1835
2208
  }
1836
2209
  }
1837
2210
  if (shouldUpdate) {
1838
2211
  await fs8.writeFile(targetPath, sourceContent);
1839
- console.log(chalk6.gray(` \u{1F4C4} ${file} \uC5C5\uB370\uC774\uD2B8`));
2212
+ console.log(
2213
+ chalk6.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
2214
+ );
1840
2215
  updatedCount++;
1841
2216
  }
1842
2217
  } else if (stat.isDirectory()) {
@@ -1844,7 +2219,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
1844
2219
  sourcePath,
1845
2220
  targetPath,
1846
2221
  force,
1847
- replacements
2222
+ replacements,
2223
+ lang
1848
2224
  );
1849
2225
  updatedCount += subCount;
1850
2226
  }
@@ -1857,10 +2233,13 @@ function configCommand(program2) {
1857
2233
  await runConfig(options);
1858
2234
  } catch (error) {
1859
2235
  if (error instanceof Error && error.message === "canceled") {
1860
- console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2236
+ const config = await getConfig(process.cwd());
2237
+ const lang = config?.lang ?? DEFAULT_LANG;
2238
+ console.log(chalk6.yellow(`
2239
+ ${tr(lang, "cli", "common.canceled")}`));
1861
2240
  process.exit(0);
1862
2241
  }
1863
- console.error(chalk6.red("\uC624\uB958:"), error);
2242
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
1864
2243
  process.exit(1);
1865
2244
  }
1866
2245
  });
@@ -1870,16 +2249,22 @@ async function runConfig(options) {
1870
2249
  const config = await getConfig(cwd);
1871
2250
  if (!config) {
1872
2251
  console.log(
1873
- chalk6.red("\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.")
2252
+ chalk6.red(
2253
+ tr(DEFAULT_LANG, "cli", "common.configNotFound")
2254
+ )
1874
2255
  );
1875
2256
  process.exit(1);
1876
2257
  }
1877
2258
  const configPath = path4.join(config.docsDir, ".lee-spec-kit.json");
1878
2259
  if (!options.projectRoot) {
1879
2260
  console.log();
1880
- console.log(chalk6.blue("\u{1F4CB} \uD604\uC7AC \uC124\uC815:"));
2261
+ console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
1881
2262
  console.log();
1882
- console.log(chalk6.gray(` \uACBD\uB85C: ${configPath}`));
2263
+ console.log(
2264
+ chalk6.gray(
2265
+ ` ${tr(config.lang, "cli", "config.pathLabel")}: ${configPath}`
2266
+ )
2267
+ );
1883
2268
  console.log();
1884
2269
  const configFile2 = await fs8.readJson(configPath);
1885
2270
  console.log(JSON.stringify(configFile2, null, 2));
@@ -1889,7 +2274,9 @@ async function runConfig(options) {
1889
2274
  const configFile = await fs8.readJson(configPath);
1890
2275
  if (configFile.docsRepo !== "standalone") {
1891
2276
  console.log(
1892
- chalk6.yellow("\u26A0\uFE0F projectRoot\uB294 standalone \uBAA8\uB4DC\uC5D0\uC11C\uB9CC \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.")
2277
+ chalk6.yellow(
2278
+ tr(config.lang, "cli", "config.projectRootStandaloneOnly")
2279
+ )
1893
2280
  );
1894
2281
  return;
1895
2282
  }
@@ -1901,7 +2288,7 @@ async function runConfig(options) {
1901
2288
  {
1902
2289
  type: "select",
1903
2290
  name: "repo",
1904
- message: "\uC218\uC815\uD560 \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
2291
+ message: tr(config.lang, "cli", "config.selectRepoToUpdate"),
1905
2292
  choices: [
1906
2293
  { title: "Frontend (fe)", value: "fe" },
1907
2294
  { title: "Backend (be)", value: "be" }
@@ -1919,7 +2306,7 @@ async function runConfig(options) {
1919
2306
  if (!options.repo || !["fe", "be"].includes(options.repo)) {
1920
2307
  console.log(
1921
2308
  chalk6.red(
1922
- "Fullstack \uD504\uB85C\uC81D\uD2B8\uB294 --repo fe \uB610\uB294 --repo be\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
2309
+ tr(config.lang, "cli", "config.fullstackRepoRequired")
1923
2310
  )
1924
2311
  );
1925
2312
  return;
@@ -1936,13 +2323,20 @@ async function runConfig(options) {
1936
2323
  }
1937
2324
  console.log(
1938
2325
  chalk6.green(
1939
- `\u2705 ${options.repo.toUpperCase()} projectRoot \uC124\uC815 \uC644\uB8CC: ${options.projectRoot}`
2326
+ tr(config.lang, "cli", "config.projectRootSet", {
2327
+ repo: options.repo.toUpperCase(),
2328
+ path: options.projectRoot
2329
+ })
1940
2330
  )
1941
2331
  );
1942
2332
  } else {
1943
2333
  configFile.projectRoot = options.projectRoot;
1944
2334
  console.log(
1945
- chalk6.green(`\u2705 projectRoot \uC124\uC815 \uC644\uB8CC: ${options.projectRoot}`)
2335
+ chalk6.green(
2336
+ tr(config.lang, "cli", "config.projectRootSetSingle", {
2337
+ path: options.projectRoot
2338
+ })
2339
+ )
1946
2340
  );
1947
2341
  }
1948
2342
  await fs8.writeJson(configPath, configFile, { spaces: 2 });
@@ -1962,7 +2356,10 @@ function contextCommand(program2) {
1962
2356
  })
1963
2357
  );
1964
2358
  } else {
1965
- console.error(chalk6.red("\uC624\uB958:"), error);
2359
+ console.error(
2360
+ chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")),
2361
+ error
2362
+ );
1966
2363
  }
1967
2364
  process.exit(1);
1968
2365
  }
@@ -1985,12 +2382,43 @@ function detectFromBranch(branchName, features) {
1985
2382
  (f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
1986
2383
  );
1987
2384
  }
2385
+ function getListLabel(f, stepsMap, lang) {
2386
+ if (f.completion.implementationDone && !f.completion.workflowDone) {
2387
+ if (f.git.docsHasUncommittedChanges) {
2388
+ return tr(lang, "cli", "context.list.docsCommitNeeded");
2389
+ }
2390
+ if (!f.issueNumber) {
2391
+ return tr(lang, "cli", "context.list.issueNumberNeeded");
2392
+ }
2393
+ if (!f.docs.prFieldExists || !f.docs.prStatusFieldExists) {
2394
+ return tr(lang, "cli", "context.list.addPrMetadata");
2395
+ }
2396
+ if (!f.pr.link) {
2397
+ return tr(lang, "cli", "context.list.recordPrLink");
2398
+ }
2399
+ if (!f.pr.status) {
2400
+ return tr(lang, "cli", "context.list.setPrStatus");
2401
+ }
2402
+ if (f.pr.status !== "Approved") {
2403
+ return tr(lang, "cli", "context.list.prStatusToApproved", {
2404
+ status: f.pr.status
2405
+ });
2406
+ }
2407
+ if (f.specStatus !== "Approved") {
2408
+ return tr(lang, "cli", "context.list.approveSpec");
2409
+ }
2410
+ if (f.planStatus !== "Approved") {
2411
+ return tr(lang, "cli", "context.list.approvePlan");
2412
+ }
2413
+ }
2414
+ return stepsMap[f.currentStep] || "Unknown";
2415
+ }
1988
2416
  async function runContext(featureName, options) {
1989
2417
  const cwd = process.cwd();
1990
2418
  const config = await getConfig(cwd);
1991
- const lang = config?.lang ?? "ko";
2419
+ const lang = config?.lang ?? "en";
1992
2420
  if (!config) {
1993
- throw new Error("\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
2421
+ throw new Error(tr(DEFAULT_LANG, "cli", "common.configNotFound"));
1994
2422
  }
1995
2423
  const stepDefinitions = getStepDefinitions(lang);
1996
2424
  const stepsMap = getStepsMap(lang);
@@ -2104,12 +2532,16 @@ async function runContext(featureName, options) {
2104
2532
  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"));
2105
2533
  console.log();
2106
2534
  if (features.length === 0) {
2107
- console.log(chalk6.yellow("\u26A0\uFE0F \uC9C4\uD589 \uC911\uC778 Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2535
+ console.log(
2536
+ chalk6.yellow(
2537
+ tr(lang, "cli", "context.noActiveFeatures")
2538
+ )
2539
+ );
2108
2540
  console.log();
2109
2541
  return;
2110
2542
  }
2111
2543
  if (warnings.length > 0) {
2112
- console.log(chalk6.yellow("\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:"));
2544
+ console.log(chalk6.yellow(tr(lang, "cli", "context.envWarnings")));
2113
2545
  warnings.forEach((w) => console.log(chalk6.yellow(` - ${w}`)));
2114
2546
  console.log();
2115
2547
  }
@@ -2117,24 +2549,36 @@ async function runContext(featureName, options) {
2117
2549
  if (selectionMode === "open") {
2118
2550
  console.log(
2119
2551
  chalk6.gray(
2120
- ` (\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: ${inProgressFeatures.length}\uAC1C / \uC885\uB8CC \uB300\uAE30: ${readyToCloseFeatures.length}\uAC1C / \uC644\uB8CC: ${doneFeatures.length}\uAC1C)`
2552
+ ` ${tr(lang, "cli", "context.openFallbackSummary", {
2553
+ inProgress: inProgressFeatures.length,
2554
+ readyToClose: readyToCloseFeatures.length,
2555
+ done: doneFeatures.length
2556
+ })}`
2121
2557
  )
2122
2558
  );
2123
2559
  console.log();
2124
2560
  }
2125
2561
  if (selectionMode === "open") {
2126
- console.log(chalk6.blue(`\u{1F539} In Progress (${inProgressFeatures.length})`));
2562
+ console.log(
2563
+ chalk6.blue(
2564
+ `\u{1F539} ${tr(lang, "cli", "context.sectionInProgress")} (${inProgressFeatures.length})`
2565
+ )
2566
+ );
2127
2567
  inProgressFeatures.forEach((f2) => {
2128
- const stepName2 = stepsMap[f2.currentStep] || "Unknown";
2568
+ const stepName2 = getListLabel(f2, stepsMap, lang);
2129
2569
  const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
2130
2570
  console.log(
2131
2571
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
2132
2572
  );
2133
2573
  });
2134
2574
  console.log();
2135
- console.log(chalk6.blue(`\u{1F538} Ready To Close (${readyToCloseFeatures.length})`));
2575
+ console.log(
2576
+ chalk6.blue(
2577
+ `\u{1F538} ${tr(lang, "cli", "context.sectionReadyToClose")} (${readyToCloseFeatures.length})`
2578
+ )
2579
+ );
2136
2580
  readyToCloseFeatures.forEach((f2) => {
2137
- const stepName2 = stepsMap[f2.currentStep] || "Unknown";
2581
+ const stepName2 = getListLabel(f2, stepsMap, lang);
2138
2582
  const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
2139
2583
  console.log(
2140
2584
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -2145,7 +2589,7 @@ async function runContext(featureName, options) {
2145
2589
  console.log(chalk6.blue(title));
2146
2590
  console.log();
2147
2591
  targetFeatures.forEach((f2) => {
2148
- const stepName2 = stepsMap[f2.currentStep] || "Unknown";
2592
+ const stepName2 = getListLabel(f2, stepsMap, lang);
2149
2593
  const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
2150
2594
  console.log(
2151
2595
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -2153,20 +2597,28 @@ async function runContext(featureName, options) {
2153
2597
  });
2154
2598
  }
2155
2599
  console.log();
2156
- console.log(chalk6.gray("Tip: \uD2B9\uC815 Feature\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uBCF4\uB824\uBA74:"));
2600
+ console.log(chalk6.gray(tr(lang, "cli", "context.tipDetails")));
2157
2601
  console.log(
2158
2602
  chalk6.gray(" $ npx lee-spec-kit context <slug|F001|F001-slug> [--repo fe|be]")
2159
2603
  );
2160
2604
  if (selectionMode === "open") {
2161
- console.log(chalk6.gray(" $ npx lee-spec-kit context --all # \uC804\uCCB4 \uBCF4\uAE30"));
2162
- console.log(chalk6.gray(" $ npx lee-spec-kit context --done # \uC644\uB8CC\uB9CC \uBCF4\uAE30"));
2605
+ console.log(
2606
+ chalk6.gray(
2607
+ ` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
2608
+ )
2609
+ );
2610
+ console.log(
2611
+ chalk6.gray(
2612
+ ` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
2613
+ )
2614
+ );
2163
2615
  }
2164
2616
  console.log();
2165
2617
  return;
2166
2618
  }
2167
2619
  const f = targetFeatures[0];
2168
2620
  const stepName = stepsMap[f.currentStep] || "Unknown";
2169
- const okTag = (requiresUserOk) => requiresUserOk ? chalk6.yellow(lang === "ko" ? "[OK \uD544\uC694] " : "[OK required] ") : "";
2621
+ const okTag = (requiresUserOk) => requiresUserOk ? chalk6.yellow(tr(lang, "cli", "context.okRequired")) : "";
2170
2622
  console.log(
2171
2623
  `\u{1F539} Feature: ${chalk6.bold(f.folderName)} ${config.projectType === "fullstack" ? chalk6.cyan(`(${f.type})`) : ""}`
2172
2624
  );
@@ -2240,9 +2692,6 @@ function printChecklist(f, stepDefinitions) {
2240
2692
  console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
2241
2693
  });
2242
2694
  }
2243
- function msg(lang, ko, en) {
2244
- return lang === "en" ? en : ko;
2245
- }
2246
2695
  function formatPath(cwd, p) {
2247
2696
  if (!p) return "";
2248
2697
  return path4.isAbsolute(p) ? path4.relative(cwd, p) : p;
@@ -2277,11 +2726,7 @@ async function checkDocsStructure(config, cwd) {
2277
2726
  issues.push({
2278
2727
  level: "error",
2279
2728
  code: "missing_dir",
2280
- message: msg(
2281
- config.lang,
2282
- `\uD544\uC218 \uD3F4\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`,
2283
- `Missing required directory: ${dir}`
2284
- ),
2729
+ message: tr(config.lang, "cli", "doctor.issue.missingRequiredDir", { dir }),
2285
2730
  path: formatPath(cwd, p)
2286
2731
  });
2287
2732
  }
@@ -2291,11 +2736,7 @@ async function checkDocsStructure(config, cwd) {
2291
2736
  issues.push({
2292
2737
  level: "warn",
2293
2738
  code: "missing_config",
2294
- message: msg(
2295
- config.lang,
2296
- "\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.",
2297
- "Missing .lee-spec-kit.json. Some commands may rely on folder-structure heuristics."
2298
- ),
2739
+ message: tr(config.lang, "cli", "doctor.issue.missingConfig"),
2299
2740
  path: formatPath(cwd, configPath)
2300
2741
  });
2301
2742
  }
@@ -2307,11 +2748,7 @@ async function checkFeatures(config, cwd, features) {
2307
2748
  issues.push({
2308
2749
  level: "warn",
2309
2750
  code: "no_features",
2310
- message: msg(
2311
- config.lang,
2312
- "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.)",
2313
- "No feature folders found. (Only feature-base exists, or no features created yet.)"
2314
- )
2751
+ message: tr(config.lang, "cli", "doctor.issue.noFeatures")
2315
2752
  });
2316
2753
  return issues;
2317
2754
  }
@@ -2331,11 +2768,9 @@ async function checkFeatures(config, cwd, features) {
2331
2768
  issues.push({
2332
2769
  level: "warn",
2333
2770
  code: "placeholder_left",
2334
- message: msg(
2335
- config.lang,
2336
- `\uD50C\uB808\uC774\uC2A4\uD640\uB354\uAC00 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4: ${placeholders.join(", ")}`,
2337
- `Leftover placeholders detected: ${placeholders.join(", ")}`
2338
- ),
2771
+ message: tr(config.lang, "cli", "doctor.issue.placeholdersLeft", {
2772
+ placeholders: placeholders.join(", ")
2773
+ }),
2339
2774
  path: formatPath(cwd, p)
2340
2775
  });
2341
2776
  }
@@ -2343,22 +2778,14 @@ async function checkFeatures(config, cwd, features) {
2343
2778
  issues.push({
2344
2779
  level: "warn",
2345
2780
  code: "missing_spec",
2346
- message: msg(
2347
- config.lang,
2348
- "spec.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
2349
- "Missing spec.md."
2350
- ),
2781
+ message: tr(config.lang, "cli", "doctor.issue.missingSpec"),
2351
2782
  path: formatPath(cwd, f.path)
2352
2783
  });
2353
2784
  } else if (!f.specStatus) {
2354
2785
  issues.push({
2355
2786
  level: "warn",
2356
2787
  code: "spec_status_unset",
2357
- message: msg(
2358
- config.lang,
2359
- "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)",
2360
- "spec.md Status is not set. (May still be a template)"
2361
- ),
2788
+ message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
2362
2789
  path: formatPath(cwd, path4.join(f.path, "spec.md"))
2363
2790
  });
2364
2791
  }
@@ -2366,11 +2793,7 @@ async function checkFeatures(config, cwd, features) {
2366
2793
  issues.push({
2367
2794
  level: "warn",
2368
2795
  code: "plan_status_unset",
2369
- message: msg(
2370
- config.lang,
2371
- "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)",
2372
- "plan.md Status is not set. (May still be a template)"
2373
- ),
2796
+ message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
2374
2797
  path: formatPath(cwd, path4.join(f.path, "plan.md"))
2375
2798
  });
2376
2799
  }
@@ -2378,11 +2801,7 @@ async function checkFeatures(config, cwd, features) {
2378
2801
  issues.push({
2379
2802
  level: "warn",
2380
2803
  code: "tasks_empty",
2381
- message: msg(
2382
- config.lang,
2383
- "tasks.md\uC5D0 \uD0DC\uC2A4\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
2384
- "tasks.md has no tasks."
2385
- ),
2804
+ message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
2386
2805
  path: formatPath(cwd, path4.join(f.path, "tasks.md"))
2387
2806
  });
2388
2807
  }
@@ -2394,11 +2813,10 @@ async function checkFeatures(config, cwd, features) {
2394
2813
  issues.push({
2395
2814
  level: "warn",
2396
2815
  code: "duplicate_feature_id",
2397
- message: msg(
2398
- config.lang,
2399
- `\uC911\uBCF5 Feature ID \uAC10\uC9C0: ${id} (${paths.length}\uAC1C)`,
2400
- `Duplicate Feature ID detected: ${id} (${paths.length})`
2401
- ),
2816
+ message: tr(config.lang, "cli", "doctor.issue.duplicateFeatureId", {
2817
+ id,
2818
+ count: String(paths.length)
2819
+ }),
2402
2820
  path: formatPath(cwd, paths[0])
2403
2821
  });
2404
2822
  }
@@ -2407,11 +2825,7 @@ async function checkFeatures(config, cwd, features) {
2407
2825
  issues.push({
2408
2826
  level: "warn",
2409
2827
  code: "missing_feature_id",
2410
- message: msg(
2411
- config.lang,
2412
- "Feature \uD3F4\uB354\uBA85\uC774 F001-... \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. (ID\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC74C)",
2413
- "Feature folder name is not in F001-... format. (Cannot extract ID)"
2414
- ),
2828
+ message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
2415
2829
  path: formatPath(cwd, path4.join(config.docsDir, p))
2416
2830
  });
2417
2831
  }
@@ -2422,11 +2836,11 @@ function doctorCommand(program2) {
2422
2836
  const cwd = process.cwd();
2423
2837
  const config = await getConfig(cwd);
2424
2838
  if (!config) {
2425
- const message = "\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.";
2839
+ const message = tr(DEFAULT_LANG, "cli", "common.configNotFound");
2426
2840
  if (options.json) {
2427
2841
  console.log(JSON.stringify({ status: "error", error: message }, null, 2));
2428
2842
  } else {
2429
- console.error(chalk6.red("\uC624\uB958:"), message);
2843
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), message);
2430
2844
  }
2431
2845
  process.exit(1);
2432
2846
  }
@@ -2461,32 +2875,40 @@ function doctorCommand(program2) {
2461
2875
  process.exit(exitCode);
2462
2876
  }
2463
2877
  console.log();
2464
- console.log(chalk6.bold("\u{1F50E} Docs Doctor"));
2878
+ console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
2465
2879
  console.log(chalk6.gray(`- Docs: ${path4.relative(cwd, docsDir)}`));
2466
2880
  console.log(chalk6.gray(`- Type: ${projectType}`));
2467
2881
  console.log(chalk6.gray(`- Lang: ${lang}`));
2468
2882
  console.log();
2469
2883
  if (warnings.length > 0) {
2470
- console.log(chalk6.yellow("\u26A0\uFE0F Environment warnings:"));
2884
+ console.log(
2885
+ chalk6.yellow(tr(lang, "cli", "doctor.envWarnings"))
2886
+ );
2471
2887
  warnings.forEach((w) => console.log(chalk6.yellow(` - ${w}`)));
2472
2888
  console.log();
2473
2889
  }
2474
2890
  if (!hasIssues) {
2475
- console.log(chalk6.green("\u2705 \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."));
2891
+ console.log(chalk6.green(tr(lang, "cli", "doctor.noIssues")));
2476
2892
  console.log();
2477
2893
  process.exit(0);
2478
2894
  }
2479
2895
  const errors = issues.filter((i) => i.level === "error");
2480
2896
  const warns = issues.filter((i) => i.level === "warn");
2481
2897
  if (errors.length > 0) {
2482
- console.log(chalk6.red(`\u274C Errors (${errors.length})`));
2898
+ console.log(
2899
+ chalk6.red(`\u274C ${tr(lang, "cli", "doctor.errorsTitle")} (${errors.length})`)
2900
+ );
2483
2901
  errors.forEach(
2484
2902
  (i) => console.log(chalk6.red(` - ${i.message}${i.path ? ` (${i.path})` : ""}`))
2485
2903
  );
2486
2904
  console.log();
2487
2905
  }
2488
2906
  if (warns.length > 0) {
2489
- console.log(chalk6.yellow(`\u26A0\uFE0F Warnings (${warns.length})`));
2907
+ console.log(
2908
+ chalk6.yellow(
2909
+ `\u26A0\uFE0F ${tr(lang, "cli", "doctor.warningsTitle")} (${warns.length})`
2910
+ )
2911
+ );
2490
2912
  warns.forEach(
2491
2913
  (i) => console.log(
2492
2914
  chalk6.yellow(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
@@ -2496,7 +2918,9 @@ function doctorCommand(program2) {
2496
2918
  }
2497
2919
  console.log(
2498
2920
  chalk6.gray(
2499
- `Tip: \uC5D0\uC774\uC804\uD2B8\uC6A9 JSON \uCD9C\uB825: npx lee-spec-kit doctor --json${options.strict ? " --strict" : ""}`
2921
+ tr(lang, "cli", "doctor.tipJson", {
2922
+ strictFlag: options.strict ? " --strict" : ""
2923
+ })
2500
2924
  )
2501
2925
  );
2502
2926
  console.log();