lee-spec-kit 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +727 -328
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -43,6 +43,368 @@ function getTemplatesDir() {
43
43
  return path4.join(rootDir, "templates");
44
44
  }
45
45
 
46
+ // src/utils/i18n.ts
47
+ var DEFAULT_LANG = "en";
48
+ function normalizeLang(lang) {
49
+ if (lang === "ko" || lang === "en") return lang;
50
+ return DEFAULT_LANG;
51
+ }
52
+ function formatTemplate(template, vars) {
53
+ return template.replace(/\{(\w+)\}/g, (_, key) => {
54
+ const value = vars[key];
55
+ return value === void 0 ? `{${key}}` : String(value);
56
+ });
57
+ }
58
+ var I18N = {
59
+ ko: {
60
+ cli: {
61
+ "common.errorLabel": "\uC624\uB958:",
62
+ "common.canceled": "\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
63
+ "common.configNotFound": "\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.",
64
+ "common.docsNotFound": "docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
65
+ "status.noFeatures": "Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
66
+ "status.duplicateIds": "\uC911\uBCF5 Feature ID \uBC1C\uACAC:",
67
+ "status.missingIds": "Feature ID\uAC00 \uC5C6\uB294 \uD56D\uBAA9:",
68
+ "status.wrote": "\u2705 {path} \uC0DD\uC131 \uC644\uB8CC",
69
+ "feature.selectRepo": "\uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
70
+ "feature.folderExists": "\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: {path}",
71
+ "feature.baseNotFound": "feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
72
+ "feature.created": "\u2705 Feature \uD3F4\uB354 \uC0DD\uC131 \uC644\uB8CC: {path}",
73
+ "feature.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
74
+ "feature.nextSteps1": " 1. {path}/spec.md \uC791\uC131",
75
+ "feature.nextSteps2": " 2. \uC0AC\uC6A9\uC790 \uB9AC\uBDF0 \uC694\uCCAD",
76
+ "feature.nextSteps3": " 3. \uC2B9\uC778 \uD6C4 plan.md \uC791\uC131",
77
+ "config.currentTitle": "\u{1F4CB} \uD604\uC7AC \uC124\uC815:",
78
+ "config.pathLabel": "\uACBD\uB85C",
79
+ "config.projectRootStandaloneOnly": "\u26A0\uFE0F projectRoot\uB294 standalone \uBAA8\uB4DC\uC5D0\uC11C\uB9CC \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.",
80
+ "config.selectRepoToUpdate": "\uC218\uC815\uD560 \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
81
+ "config.fullstackRepoRequired": "Fullstack \uD504\uB85C\uC81D\uD2B8\uB294 --repo fe \uB610\uB294 --repo be\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.",
82
+ "config.projectRootSet": "\u2705 {repo} projectRoot \uC124\uC815 \uC644\uB8CC: {path}",
83
+ "config.projectRootSetSingle": "\u2705 projectRoot \uC124\uC815 \uC644\uB8CC: {path}",
84
+ "update.start": "\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...",
85
+ "update.langLabel": "\uC5B8\uC5B4",
86
+ "update.typeLabel": "\uD0C0\uC785",
87
+ "update.updatingAgents": "\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
88
+ "update.agentsUpdated": "agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
89
+ "update.updatingFeatureBase": "\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911...",
90
+ "update.filesUpdated": "{count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC",
91
+ "update.updatedTotal": "\uCD1D {count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!",
92
+ "update.changeDetected": "\uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)",
93
+ "update.fileUpdated": "{file} \uC5C5\uB370\uC774\uD2B8",
94
+ "doctor.title": "\u{1F50E} \uBB38\uC11C \uC9C4\uB2E8",
95
+ "doctor.envWarnings": "\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:",
96
+ "doctor.noIssues": "\u2705 \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
97
+ "doctor.errorsTitle": "\uC624\uB958",
98
+ "doctor.warningsTitle": "\uACBD\uACE0",
99
+ "doctor.tipJson": "Tip: \uC5D0\uC774\uC804\uD2B8\uC6A9 JSON \uCD9C\uB825: npx lee-spec-kit doctor --json{strictFlag}",
100
+ "doctor.issue.missingRequiredDir": "\uD544\uC218 \uD3F4\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: {dir}",
101
+ "doctor.issue.missingConfig": "\uC124\uC815 \uD30C\uC77C(.lee-spec-kit.json)\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC77C\uBD80 \uAE30\uB2A5\uC774 \uD3F4\uB354 \uAD6C\uC870 \uCD94\uC815\uC73C\uB85C \uB3D9\uC791\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
102
+ "doctor.issue.noFeatures": "Feature \uD3F4\uB354\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. (feature-base\uB9CC \uC874\uC7AC\uD558\uAC70\uB098 \uC544\uC9C1 feature\uB97C \uB9CC\uB4E4\uC9C0 \uC54A\uC558\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.)",
103
+ "doctor.issue.placeholdersLeft": "\uD50C\uB808\uC774\uC2A4\uD640\uB354\uAC00 \uB0A8\uC544\uC788\uC2B5\uB2C8\uB2E4: {placeholders}",
104
+ "doctor.issue.missingSpec": "spec.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
105
+ "doctor.issue.specStatusUnset": "spec.md\uC758 Status(\uC0C1\uD0DC)\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uD15C\uD50C\uB9BF \uADF8\uB300\uB85C\uC77C \uC218 \uC788\uC74C)",
106
+ "doctor.issue.planStatusUnset": "plan.md\uC758 Status(\uC0C1\uD0DC)\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uD15C\uD50C\uB9BF \uADF8\uB300\uB85C\uC77C \uC218 \uC788\uC74C)",
107
+ "doctor.issue.tasksEmpty": "tasks.md\uC5D0 \uD0DC\uC2A4\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
108
+ "doctor.issue.duplicateFeatureId": "\uC911\uBCF5 Feature ID \uAC10\uC9C0: {id} ({count}\uAC1C)",
109
+ "doctor.issue.missingFeatureId": "Feature \uD3F4\uB354\uBA85\uC774 F001-... \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4. (ID\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC74C)",
110
+ "context.noActiveFeatures": "\u26A0\uFE0F \uC9C4\uD589 \uC911\uC778 Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
111
+ "context.envWarnings": "\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:",
112
+ "context.openFallbackSummary": "(\uBE0C\uB79C\uCE58\uB85C Feature\uB97C \uD2B9\uC815\uD558\uC9C0 \uBABB\uD574 \uBBF8\uC644\uB8CC Feature\uB9CC \uD45C\uC2DC\uD569\uB2C8\uB2E4. \uC9C4\uD589 \uC911: {inProgress}\uAC1C / \uC885\uB8CC \uB300\uAE30: {readyToClose}\uAC1C / \uC644\uB8CC: {done}\uAC1C)",
113
+ "context.sectionInProgress": "\uC9C4\uD589 \uC911",
114
+ "context.sectionReadyToClose": "\uC885\uB8CC \uC900\uBE44",
115
+ "context.tipDetails": "Tip: \uD2B9\uC815 Feature\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uBCF4\uB824\uBA74:",
116
+ "context.tipShowAll": "\uC804\uCCB4 \uBCF4\uAE30",
117
+ "context.tipShowDone": "\uC644\uB8CC\uB9CC \uBCF4\uAE30",
118
+ "context.okRequired": "[OK \uD544\uC694] ",
119
+ "context.list.docsCommitNeeded": "\uBB38\uC11C \uCEE4\uBC0B \uD544\uC694",
120
+ "context.list.issueNumberNeeded": "\uC774\uC288 \uBC88\uD638 \uAE30\uB85D \uD544\uC694",
121
+ "context.list.addPrMetadata": "PR \uBA54\uD0C0\uB370\uC774\uD130(PR/PR \uC0C1\uD0DC) \uCD94\uAC00",
122
+ "context.list.recordPrLink": "PR \uB9C1\uD06C \uAE30\uB85D",
123
+ "context.list.setPrStatus": "PR \uC0C1\uD0DC \uC124\uC815",
124
+ "context.list.prStatusToApproved": "PR \uC0C1\uD0DC {status} \u2192 Approved",
125
+ "context.list.approveSpec": "spec \uC2B9\uC778 \uD544\uC694",
126
+ "context.list.approvePlan": "plan \uC2B9\uC778 \uD544\uC694",
127
+ "init.selectLangPrompt": "\uBB38\uC11C \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
128
+ "init.currentDirectoryLabel": "\u{1F4CD} \uD604\uC7AC \uC704\uCE58",
129
+ "init.gitDetected": "\u2705 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0\uB428",
130
+ "init.insideProjectRoot": "\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uB0B4\uC5D0\uC11C \uC2E4\uD589\uD558\uACE0 \uACC4\uC2ED\uB2C8\uB2E4.",
131
+ "init.modeEmbeddedDesc": "\u2022 embedded: \uC5EC\uAE30\uC5D0 ./docs \uD3F4\uB354\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 \uAD00\uB9AC\uB429\uB2C8\uB2E4.",
132
+ "init.modeStandaloneDesc": "\u2022 standalone: \uBCC4\uB3C4 \uD3F4\uB354\uC5D0\uC11C \uB3C5\uB9BD docs \uB808\uD3EC\uB85C \uAD00\uB9AC\uD558\uB824\uBA74,",
133
+ "init.modeStandaloneMove": " \uD574\uB2F9 \uD3F4\uB354\uB85C \uC774\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD574\uC8FC\uC138\uC694.",
134
+ "init.gitNotDetected": "\u26A0\uFE0F Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
135
+ "init.gitNotDetectedDetail": "\uC0C8\uB85C\uC6B4 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC0DD\uC131\uB429\uB2C8\uB2E4.",
136
+ "init.prompt.projectName": "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694:",
137
+ "init.prompt.projectType": "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
138
+ "init.choice.projectType.single.title": "Single - \uB2E8\uC77C \uB808\uD3EC \uD504\uB85C\uC81D\uD2B8",
139
+ "init.choice.projectType.single.desc": "features/ \uD3F4\uB354 \uD558\uB098\uB85C \uAD00\uB9AC",
140
+ "init.choice.projectType.fullstack.title": "Fullstack - FE/BE \uBD84\uB9AC \uD504\uB85C\uC81D\uD2B8",
141
+ "init.choice.projectType.fullstack.desc": "features/be/, features/fe/ \uBD84\uB9AC \uAD00\uB9AC",
142
+ "init.prompt.docsMode": "Docs \uAD00\uB9AC \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
143
+ "init.choice.docsRepo.embedded.title": "embedded - \uD504\uB85C\uC81D\uD2B8 \uB0B4 \uD3EC\uD568 (./docs)",
144
+ "init.choice.docsRepo.embedded.desc": "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4",
145
+ "init.choice.docsRepo.standalone.title": "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
146
+ "init.choice.docsRepo.standalone.desc": "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4",
147
+ "init.prompt.feRepoPath": "Frontend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
148
+ "init.prompt.beRepoPath": "Backend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
149
+ "init.prompt.projectRepoPath": "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
150
+ "init.validation.enterPath": "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694",
151
+ "init.prompt.pushMode": "Docs push \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
152
+ "init.choice.push.local": "local - \uB85C\uCEEC\uC5D0\uC11C\uB9CC \uAD00\uB9AC (push \uC548 \uD568)",
153
+ "init.choice.push.remote": "remote - \uC6D0\uACA9\uC5D0\uB3C4 push",
154
+ "init.prompt.remoteUrl": "\uC6D0\uACA9 \uB808\uD3EC URL\uC744 \uC785\uB825\uD558\uC138\uC694:",
155
+ "init.validation.enterUrl": "URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694",
156
+ "init.prompt.overwrite": "{dir} \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
157
+ "init.log.creatingDocs": "\u{1F4C1} docs \uAD6C\uC870 \uC0DD\uC131 \uC911...",
158
+ "init.log.projectLabel": "\uD504\uB85C\uC81D\uD2B8",
159
+ "init.log.typeLabel": "\uD0C0\uC785",
160
+ "init.log.langLabel": "\uC5B8\uC5B4",
161
+ "init.log.pathLabel": "\uACBD\uB85C",
162
+ "init.log.docsCreated": "\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!",
163
+ "init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
164
+ "init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
165
+ "init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
166
+ "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
167
+ "init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
168
+ "init.warn.stagedChangesSkip": '\u26A0\uFE0F \uD604\uC7AC Git index\uC5D0 \uC774\uBBF8 stage\uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. (--dir "." \uC778 \uACBD\uC6B0 \uCEE4\uBC0B \uBC94\uC704\uB97C \uC548\uC804\uD558\uAC8C \uC81C\uD55C\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uCEE4\uBC0B\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4)',
169
+ "init.warn.commitManually": " \uC218\uB3D9\uC73C\uB85C \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD55C \uB4A4 \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694.",
170
+ "init.log.gitRemoteSet": "\u2705 Git remote \uC124\uC815 \uC644\uB8CC: {remote}",
171
+ "init.warn.gitRemoteExists": "\u26A0\uFE0F Git remote\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4.",
172
+ "init.log.gitInitialCommitDone": "\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!",
173
+ "init.warn.skipGitInit": "\u26A0\uFE0F Git \uCD08\uAE30\uD654\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4 (\uC218\uB3D9\uC73C\uB85C \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694)",
174
+ "init.error.templateNotFound": "\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {path}"
175
+ },
176
+ steps: {
177
+ featureFolder: "Feature \uD3F4\uB354 \uC0DD\uC131",
178
+ specWrite: "spec.md \uC791\uC131",
179
+ specApprove: "spec.md \uC2B9\uC778",
180
+ planWrite: "plan.md \uC791\uC131",
181
+ planApprove: "plan.md \uC2B9\uC778",
182
+ tasksWrite: "tasks.md \uC791\uC131",
183
+ docsCommitPlanning: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
184
+ issueCreate: "GitHub Issue \uC0DD\uC131",
185
+ branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
186
+ tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
187
+ prCreate: "PR \uC0DD\uC131",
188
+ codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
189
+ featureDone: "Feature \uC644\uB8CC"
190
+ },
191
+ messages: {
192
+ specCreate: "spec.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/spec.md \uCC38\uACE0)",
193
+ specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
194
+ specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
195
+ planCreate: "plan.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/plan.md \uCC38\uACE0)",
196
+ planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
197
+ planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
198
+ tasksCreate: "tasks.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694. (features/feature-base/tasks.md \uCC38\uACE0)",
199
+ tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
200
+ docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
201
+ issueCreateAndWrite: "GitHub Issue\uB97C \uC0DD\uC131\uD55C \uB4A4, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694. (skills/create-issue.md \uCC38\uACE0)",
202
+ docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
203
+ standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
204
+ createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
205
+ tasksAllDoneButNoChecklist: '\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC139\uC158\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC744 \uCD94\uAC00/\uD655\uC778\uD558\uC138\uC694.',
206
+ tasksAllDoneButChecklist: "\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uAC00 \uC644\uC804\uD788 \uCCB4\uD06C\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. ({checked}/{total})",
207
+ finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
208
+ startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
209
+ checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)",
210
+ prLegacyAsk: "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD15C\uD50C\uB9BF\uC744 \uCD5C\uC2E0 \uD3EC\uB9F7\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694? (OK \uD544\uC694)",
211
+ prCreate: "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694. (skills/create-pr.md \uCC38\uACE0)",
212
+ prFillStatus: "tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Draft/Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694. (merge \uD6C4 Approved\uB85C \uC5C5\uB370\uC774\uD2B8)",
213
+ prResolveReview: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD574\uACB0\uD558\uACE0 PR \uC0C1\uD0DC\uB97C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. (PR \uC0C1\uD0DC: Review \u2192 Approved)",
214
+ prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.",
215
+ featureDone: "PR\uC774 Approved\uC774\uACE0 \uBAA8\uB4E0 \uD0DC\uC2A4\uD06C/\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uB8CC \uC0C1\uD0DC\uC785\uB2C8\uB2E4.",
216
+ fallbackRerunContext: "\uC0C1\uD0DC\uB97C \uD310\uBCC4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694."
217
+ },
218
+ warnings: {
219
+ projectBranchUnavailable: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.)",
220
+ docsGitUnavailable: "docs \uB808\uD3EC\uC758 git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uB808\uD3EC \uC704\uCE58 / git init \uD655\uC778)",
221
+ docsUncommittedChanges: "\uBB38\uC11C \uBCC0\uACBD\uC0AC\uD56D\uC774 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. (\uCD94\uAC00 \uBB38\uC11C \uCEE4\uBC0B \uD544\uC694)",
222
+ legacyTasksPrFields: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR` \uBC0F `PR \uC0C1\uD0DC` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
223
+ workflowSpecNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC spec.md \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (spec.md\uC758 \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
224
+ workflowPlanNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC plan.md \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (plan.md\uC758 \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)",
225
+ workflowPrLinkMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uB9C1\uD06C\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (tasks.md\uC758 PR \uD544\uB4DC\uB97C \uCC44\uC6B0\uC138\uC694.)",
226
+ workflowPrStatusMissing: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (tasks.md\uC758 PR \uC0C1\uD0DC\uB97C Draft/Review/Approved \uC911 \uD558\uB098\uB85C \uC124\uC815\uD558\uC138\uC694.)",
227
+ workflowPrStatusNotApproved: "\uC644\uB8CC \uC0C1\uD0DC\uC774\uC9C0\uB9CC PR \uC0C1\uD0DC\uAC00 Approved\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (merge \uD6C4 PR \uC0C1\uD0DC\uB97C Approved\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.)"
228
+ }
229
+ },
230
+ en: {
231
+ cli: {
232
+ "common.errorLabel": "Error:",
233
+ "common.canceled": "Operation canceled.",
234
+ "common.configNotFound": "Config file not found. Run `init` first.",
235
+ "common.docsNotFound": "docs folder not found. Run `init` first.",
236
+ "status.noFeatures": "No features found.",
237
+ "status.duplicateIds": "Duplicate Feature IDs found:",
238
+ "status.missingIds": "Entries missing Feature ID:",
239
+ "status.wrote": "\u2705 Wrote {path}",
240
+ "feature.selectRepo": "Select a repository:",
241
+ "feature.folderExists": "Folder already exists: {path}",
242
+ "feature.baseNotFound": "feature-base template not found.",
243
+ "feature.created": "\u2705 Feature folder created: {path}",
244
+ "feature.nextStepsTitle": "Next steps:",
245
+ "feature.nextSteps1": " 1. Write {path}/spec.md",
246
+ "feature.nextSteps2": " 2. Ask for review",
247
+ "feature.nextSteps3": " 3. After approval, write plan.md",
248
+ "config.currentTitle": "\u{1F4CB} Current config:",
249
+ "config.pathLabel": "Path",
250
+ "config.projectRootStandaloneOnly": "\u26A0\uFE0F projectRoot can only be set in standalone mode.",
251
+ "config.selectRepoToUpdate": "Select a repository to update:",
252
+ "config.fullstackRepoRequired": "For fullstack projects, you must specify `--repo fe` or `--repo be`.",
253
+ "config.projectRootSet": "\u2705 {repo} projectRoot set: {path}",
254
+ "config.projectRootSetSingle": "\u2705 projectRoot set: {path}",
255
+ "update.start": "\u{1F4E6} Starting template update...",
256
+ "update.langLabel": "Lang",
257
+ "update.typeLabel": "Type",
258
+ "update.updatingAgents": "\u{1F4C1} Updating agents/ folder...",
259
+ "update.agentsUpdated": "agents/ updated",
260
+ "update.updatingFeatureBase": "\u{1F4C1} Updating features/feature-base/ folder...",
261
+ "update.filesUpdated": "{count} files updated",
262
+ "update.updatedTotal": "Updated {count} files!",
263
+ "update.changeDetected": "changes detected (use --force to overwrite)",
264
+ "update.fileUpdated": "{file} updated",
265
+ "doctor.title": "\u{1F50E} Docs Doctor",
266
+ "doctor.envWarnings": "\u26A0\uFE0F Environment warnings:",
267
+ "doctor.noIssues": "\u2705 No issues found.",
268
+ "doctor.errorsTitle": "Errors",
269
+ "doctor.warningsTitle": "Warnings",
270
+ "doctor.tipJson": "Tip: Agent JSON output: npx lee-spec-kit doctor --json{strictFlag}",
271
+ "doctor.issue.missingRequiredDir": "Missing required directory: {dir}",
272
+ "doctor.issue.missingConfig": "Missing .lee-spec-kit.json. Some commands may rely on folder-structure heuristics.",
273
+ "doctor.issue.noFeatures": "No feature folders found. (Only feature-base exists, or no features created yet.)",
274
+ "doctor.issue.placeholdersLeft": "Leftover placeholders detected: {placeholders}",
275
+ "doctor.issue.missingSpec": "Missing spec.md.",
276
+ "doctor.issue.specStatusUnset": "spec.md Status is not set. (May still be a template)",
277
+ "doctor.issue.planStatusUnset": "plan.md Status is not set. (May still be a template)",
278
+ "doctor.issue.tasksEmpty": "tasks.md has no tasks.",
279
+ "doctor.issue.duplicateFeatureId": "Duplicate Feature ID detected: {id} ({count})",
280
+ "doctor.issue.missingFeatureId": "Feature folder name is not in F001-... format. (Cannot extract ID)",
281
+ "context.noActiveFeatures": "\u26A0\uFE0F No active features found.",
282
+ "context.envWarnings": "\u26A0\uFE0F Environment warnings:",
283
+ "context.openFallbackSummary": "(Could not detect a feature from the branch, so showing only open features. In Progress: {inProgress} / Ready To Close: {readyToClose} / Done: {done})",
284
+ "context.sectionInProgress": "In Progress",
285
+ "context.sectionReadyToClose": "Ready To Close",
286
+ "context.tipDetails": "Tip: To view details for a feature:",
287
+ "context.tipShowAll": "Show all",
288
+ "context.tipShowDone": "Show done only",
289
+ "context.okRequired": "[OK required] ",
290
+ "context.list.docsCommitNeeded": "Commit docs changes",
291
+ "context.list.issueNumberNeeded": "Fill issue number in docs",
292
+ "context.list.addPrMetadata": "Add PR metadata (PR/PR Status)",
293
+ "context.list.recordPrLink": "Record PR link",
294
+ "context.list.setPrStatus": "Set PR Status",
295
+ "context.list.prStatusToApproved": "PR Status {status} \u2192 Approved",
296
+ "context.list.approveSpec": "Approve spec",
297
+ "context.list.approvePlan": "Approve plan",
298
+ "init.selectLangPrompt": "Select docs language:",
299
+ "init.currentDirectoryLabel": "\u{1F4CD} Current directory",
300
+ "init.gitDetected": "\u2705 Git repository detected",
301
+ "init.insideProjectRoot": "You are running inside your project root.",
302
+ "init.modeEmbeddedDesc": "\u2022 embedded: creates ./docs here and manages it with the project.",
303
+ "init.modeStandaloneDesc": "\u2022 standalone: to manage docs as a separate repo,",
304
+ "init.modeStandaloneMove": " move to that folder and run again.",
305
+ "init.gitNotDetected": "\u26A0\uFE0F Git repository not detected.",
306
+ "init.gitNotDetectedDetail": "A new Git repo will be initialized.",
307
+ "init.prompt.projectName": "Enter project name:",
308
+ "init.prompt.projectType": "Select project type:",
309
+ "init.choice.projectType.single.title": "Single - single repo project",
310
+ "init.choice.projectType.single.desc": "Manage with a single features/ folder",
311
+ "init.choice.projectType.fullstack.title": "Fullstack - split FE/BE repos",
312
+ "init.choice.projectType.fullstack.desc": "Manage with features/be/ and features/fe/",
313
+ "init.prompt.docsMode": "Select docs mode:",
314
+ "init.choice.docsRepo.embedded.title": "embedded - inside the project (./docs)",
315
+ "init.choice.docsRepo.embedded.desc": "Pushed together with the project",
316
+ "init.choice.docsRepo.standalone.title": "standalone - separate docs repo",
317
+ "init.choice.docsRepo.standalone.desc": "Configure push settings separately",
318
+ "init.prompt.feRepoPath": "Enter frontend repository path:",
319
+ "init.prompt.beRepoPath": "Enter backend repository path:",
320
+ "init.prompt.projectRepoPath": "Enter project repository path:",
321
+ "init.validation.enterPath": "Please enter a path",
322
+ "init.prompt.pushMode": "Select docs push mode:",
323
+ "init.choice.push.local": "local - manage locally (no push)",
324
+ "init.choice.push.remote": "remote - push to remote",
325
+ "init.prompt.remoteUrl": "Enter remote repository URL:",
326
+ "init.validation.enterUrl": "Please enter a URL",
327
+ "init.prompt.overwrite": "{dir} already exists. Overwrite?",
328
+ "init.log.creatingDocs": "\u{1F4C1} Creating docs structure...",
329
+ "init.log.projectLabel": "Project",
330
+ "init.log.typeLabel": "Type",
331
+ "init.log.langLabel": "Lang",
332
+ "init.log.pathLabel": "Path",
333
+ "init.log.docsCreated": "\u2705 Docs structure created!",
334
+ "init.log.nextStepsTitle": "Next steps:",
335
+ "init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
336
+ "init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
337
+ "init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
338
+ "init.log.gitInit": "\u{1F4E6} Initializing Git...",
339
+ "init.warn.stagedChangesSkip": '\u26A0\uFE0F There are already staged changes in the Git index. (With --dir ".", commit scope cannot be safely restricted, so auto-commit is skipped.)',
340
+ "init.warn.commitManually": " Review the changes and commit manually.",
341
+ "init.log.gitRemoteSet": "\u2705 Git remote set: {remote}",
342
+ "init.warn.gitRemoteExists": "\u26A0\uFE0F Git remote already exists.",
343
+ "init.log.gitInitialCommitDone": "\u2705 Initial Git commit created!",
344
+ "init.warn.skipGitInit": "\u26A0\uFE0F Skipping Git initialization (please commit manually)",
345
+ "init.error.templateNotFound": "Template not found: {path}"
346
+ },
347
+ steps: {
348
+ featureFolder: "Create feature folder",
349
+ specWrite: "Write spec.md",
350
+ specApprove: "Approve spec.md",
351
+ planWrite: "Write plan.md",
352
+ planApprove: "Approve plan.md",
353
+ tasksWrite: "Write tasks.md",
354
+ docsCommitPlanning: "Commit docs (sync)",
355
+ issueCreate: "Create GitHub Issue",
356
+ branchCreate: "Create branch",
357
+ tasksExecute: "Execute tasks",
358
+ prCreate: "Create PR",
359
+ codeReview: "Code review",
360
+ featureDone: "Feature done"
361
+ },
362
+ messages: {
363
+ specCreate: "Create spec.md by copying the template. (See features/feature-base/spec.md)",
364
+ specImprove: "Improve spec.md and change Status to Review.",
365
+ specApproval: "Share spec.md with the user and get approval (OK).",
366
+ planCreate: "Create plan.md by copying the template. (See features/feature-base/plan.md)",
367
+ planImprove: "Improve plan.md and change Status to Review.",
368
+ planApproval: "Share plan.md with the user and get approval (OK).",
369
+ tasksCreate: "Create tasks.md by copying the template. (See features/feature-base/tasks.md)",
370
+ tasksNeedAtLeastOne: "Write at least 1 task in tasks.md.",
371
+ docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
372
+ issueCreateAndWrite: "Create a GitHub Issue, fill the issue number in spec.md/tasks.md, then prepare a docs commit. (See skills/create-issue.md)",
373
+ docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
374
+ standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
375
+ createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
376
+ tasksAllDoneButNoChecklist: 'All tasks are DONE, but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
377
+ tasksAllDoneButChecklist: "All tasks are DONE, but the completion checklist is not fully checked. ({checked}/{total})",
378
+ finishDoingTask: 'Finish the current DOING/REVIEW task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
379
+ startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
380
+ checkTaskStatuses: "Check task statuses. ({done}/{total}) (See skills/execute-task.md)",
381
+ prLegacyAsk: "tasks.md is missing PR/PR Status fields. Update to the latest template format? (OK required)",
382
+ prCreate: "Create a PR and record the PR link in tasks.md. (See skills/create-pr.md)",
383
+ prFillStatus: "Set PR Status in tasks.md to Draft/Review/Approved. (After merge, update it to Approved.)",
384
+ prResolveReview: "Resolve review comments and update PR Status. (PR Status: Review \u2192 Approved)",
385
+ prRequestReview: "Request review and update PR Status to Review.",
386
+ featureDone: "PR is Approved and all tasks/completion criteria are satisfied. This feature is done.",
387
+ fallbackRerunContext: "Cannot determine status. Check the docs and run context again."
388
+ },
389
+ warnings: {
390
+ projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
391
+ docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
392
+ docsUncommittedChanges: "Docs changes are not committed. (Additional docs commit needed.)",
393
+ legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps.",
394
+ workflowSpecNotApproved: "Implementation is done but spec.md Status is not Approved. (Update spec.md Status to Approved.)",
395
+ workflowPlanNotApproved: "Implementation is done but plan.md Status is not Approved. (Update plan.md Status to Approved.)",
396
+ workflowPrLinkMissing: "Implementation is done but PR link is missing. (Fill the PR field in tasks.md.)",
397
+ workflowPrStatusMissing: "Implementation is done but PR Status is missing. (Set PR Status to Draft/Review/Approved in tasks.md.)",
398
+ workflowPrStatusNotApproved: "Implementation is done but PR Status is not Approved. (After merge, update PR Status to Approved in tasks.md.)"
399
+ }
400
+ }
401
+ };
402
+ function tr(lang, category, key, vars = {}) {
403
+ const safeLang = normalizeLang(lang);
404
+ const template = I18N[safeLang]?.[category]?.[key] ?? I18N[DEFAULT_LANG]?.[category]?.[key] ?? I18N.ko?.[category]?.[key] ?? `${category}.${key}`;
405
+ return formatTemplate(template, vars);
406
+ }
407
+
46
408
  // src/utils/validation.ts
47
409
  var VALID_PROJECT_TYPES = ["single", "fullstack"];
48
410
  var VALID_LANGUAGES = ["ko", "en"];
@@ -149,15 +511,19 @@ function checkGitRepo(cwd) {
149
511
  }
150
512
  }
151
513
  function initCommand(program2) {
152
- program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option("-t, --type <type>", "Project type: single | fullstack").option("-l, --lang <lang>", "Language: ko | en (default: ko)").option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
514
+ program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option("-t, --type <type>", "Project type: single | fullstack").option("-l, --lang <lang>", "Language: ko | en (default: en)").option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
153
515
  try {
154
516
  await runInit(options);
155
517
  } catch (error) {
156
518
  if (error instanceof Error && error.message === "canceled") {
157
- console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
519
+ const lang = options.lang ?? DEFAULT_LANG;
520
+ console.log(
521
+ chalk6.yellow(`
522
+ ${tr(lang, "cli", "common.canceled")}`)
523
+ );
158
524
  process.exit(0);
159
525
  }
160
- console.error(chalk6.red("\uC624\uB958:"), error);
526
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
161
527
  process.exit(1);
162
528
  }
163
529
  });
@@ -167,7 +533,7 @@ async function runInit(options) {
167
533
  const defaultName = path4.basename(cwd);
168
534
  let projectName = options.name || defaultName;
169
535
  let projectType = options.type;
170
- let lang = options.lang || "ko";
536
+ let lang = options.lang || "en";
171
537
  let docsRepo = "embedded";
172
538
  let pushDocs;
173
539
  let docsRemote;
@@ -175,24 +541,62 @@ async function runInit(options) {
175
541
  const targetDir = path4.resolve(cwd, options.dir || "./docs");
176
542
  const isInsideGitRepo = checkGitRepo(cwd);
177
543
  if (!options.yes) {
544
+ if (!options.lang) {
545
+ const langResponse = await prompts(
546
+ [
547
+ {
548
+ type: "select",
549
+ name: "lang",
550
+ message: tr(DEFAULT_LANG, "cli", "init.selectLangPrompt"),
551
+ choices: [
552
+ { title: "English (en)", value: "en" },
553
+ { title: "\uD55C\uAD6D\uC5B4 (ko)", value: "ko" }
554
+ ],
555
+ initial: 0
556
+ }
557
+ ],
558
+ {
559
+ onCancel: () => {
560
+ throw new Error("canceled");
561
+ }
562
+ }
563
+ );
564
+ lang = langResponse.lang || lang;
565
+ }
178
566
  console.log();
179
- console.log(chalk6.blue(`\u{1F4CD} \uD604\uC7AC \uC704\uCE58: ${cwd}`));
567
+ console.log(
568
+ chalk6.blue(`${tr(lang, "cli", "init.currentDirectoryLabel")}: ${cwd}`)
569
+ );
180
570
  if (isInsideGitRepo) {
181
- console.log(chalk6.green("\u2705 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0\uB428"));
571
+ console.log(chalk6.green(tr(lang, "cli", "init.gitDetected")));
182
572
  console.log();
183
- console.log(chalk6.gray("\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uB0B4\uC5D0\uC11C \uC2E4\uD589\uD558\uACE0 \uACC4\uC2ED\uB2C8\uB2E4."));
184
573
  console.log(
185
574
  chalk6.gray(
186
- "\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."
575
+ tr(lang, "cli", "init.insideProjectRoot")
576
+ )
577
+ );
578
+ console.log(
579
+ chalk6.gray(
580
+ tr(lang, "cli", "init.modeEmbeddedDesc")
187
581
  )
188
582
  );
189
583
  console.log(
190
- chalk6.gray("\u2022 standalone: \uBCC4\uB3C4 \uD3F4\uB354\uC5D0\uC11C \uB3C5\uB9BD docs \uB808\uD3EC\uB85C \uAD00\uB9AC\uD558\uB824\uBA74,")
584
+ chalk6.gray(
585
+ tr(lang, "cli", "init.modeStandaloneDesc")
586
+ )
587
+ );
588
+ console.log(
589
+ chalk6.gray(
590
+ tr(lang, "cli", "init.modeStandaloneMove")
591
+ )
191
592
  );
192
- console.log(chalk6.gray(" \uD574\uB2F9 \uD3F4\uB354\uB85C \uC774\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD574\uC8FC\uC138\uC694."));
193
593
  } else {
194
- console.log(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."));
594
+ console.log(
595
+ chalk6.yellow(
596
+ tr(lang, "cli", "init.gitNotDetected")
597
+ )
598
+ );
599
+ console.log(chalk6.gray(tr(lang, "cli", "init.gitNotDetectedDetail")));
196
600
  }
197
601
  console.log();
198
602
  const response = await prompts(
@@ -200,51 +604,41 @@ async function runInit(options) {
200
604
  {
201
605
  type: options.name ? null : "text",
202
606
  name: "projectName",
203
- message: "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694:",
607
+ message: tr(lang, "cli", "init.prompt.projectName"),
204
608
  initial: defaultName
205
609
  },
206
610
  {
207
611
  type: options.type ? null : "select",
208
612
  name: "projectType",
209
- message: "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
613
+ message: tr(lang, "cli", "init.prompt.projectType"),
210
614
  choices: [
211
615
  {
212
- title: "Single - \uB2E8\uC77C \uB808\uD3EC \uD504\uB85C\uC81D\uD2B8",
616
+ title: tr(lang, "cli", "init.choice.projectType.single.title"),
213
617
  value: "single",
214
- description: "features/ \uD3F4\uB354 \uD558\uB098\uB85C \uAD00\uB9AC"
618
+ description: tr(lang, "cli", "init.choice.projectType.single.desc")
215
619
  },
216
620
  {
217
- title: "Fullstack - FE/BE \uBD84\uB9AC \uD504\uB85C\uC81D\uD2B8",
621
+ title: tr(lang, "cli", "init.choice.projectType.fullstack.title"),
218
622
  value: "fullstack",
219
- description: "features/be/, features/fe/ \uBD84\uB9AC \uAD00\uB9AC"
623
+ description: tr(lang, "cli", "init.choice.projectType.fullstack.desc")
220
624
  }
221
625
  ],
222
626
  initial: 0
223
627
  },
224
- {
225
- type: options.lang ? null : "select",
226
- name: "lang",
227
- message: "\uBB38\uC11C \uC5B8\uC5B4\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
228
- choices: [
229
- { title: "\uD55C\uAD6D\uC5B4 (ko)", value: "ko" },
230
- { title: "English (en)", value: "en" }
231
- ],
232
- initial: 0
233
- },
234
628
  {
235
629
  type: "select",
236
630
  name: "docsRepo",
237
- message: "Docs \uAD00\uB9AC \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
631
+ message: tr(lang, "cli", "init.prompt.docsMode"),
238
632
  choices: [
239
633
  {
240
- title: "embedded - \uD504\uB85C\uC81D\uD2B8 \uB0B4 \uD3EC\uD568 (./docs)",
634
+ title: tr(lang, "cli", "init.choice.docsRepo.embedded.title"),
241
635
  value: "embedded",
242
- description: "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4"
636
+ description: tr(lang, "cli", "init.choice.docsRepo.embedded.desc")
243
637
  },
244
638
  {
245
- title: "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
639
+ title: tr(lang, "cli", "init.choice.docsRepo.standalone.title"),
246
640
  value: "standalone",
247
- description: "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4"
641
+ description: tr(lang, "cli", "init.choice.docsRepo.standalone.desc")
248
642
  }
249
643
  ],
250
644
  initial: 0
@@ -258,7 +652,6 @@ async function runInit(options) {
258
652
  );
259
653
  projectName = response.projectName || projectName;
260
654
  projectType = response.projectType || projectType;
261
- lang = response.lang || lang;
262
655
  docsRepo = response.docsRepo || "embedded";
263
656
  if (docsRepo === "standalone") {
264
657
  const resolvedType = projectType || response.projectType || "single";
@@ -268,14 +661,14 @@ async function runInit(options) {
268
661
  {
269
662
  type: "text",
270
663
  name: "feRoot",
271
- message: "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"
664
+ message: tr(lang, "cli", "init.prompt.feRepoPath"),
665
+ validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
273
666
  },
274
667
  {
275
668
  type: "text",
276
669
  name: "beRoot",
277
- message: "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"
670
+ message: tr(lang, "cli", "init.prompt.beRepoPath"),
671
+ validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
279
672
  }
280
673
  ],
281
674
  {
@@ -294,8 +687,8 @@ async function runInit(options) {
294
687
  {
295
688
  type: "text",
296
689
  name: "projectRoot",
297
- message: "\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"
690
+ message: tr(lang, "cli", "init.prompt.projectRepoPath"),
691
+ validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterPath")
299
692
  }
300
693
  ],
301
694
  {
@@ -311,14 +704,14 @@ async function runInit(options) {
311
704
  {
312
705
  type: "select",
313
706
  name: "pushDocs",
314
- message: "Docs push \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
707
+ message: tr(lang, "cli", "init.prompt.pushMode"),
315
708
  choices: [
316
709
  {
317
- title: "local - \uB85C\uCEEC\uC5D0\uC11C\uB9CC \uAD00\uB9AC (push \uC548 \uD568)",
710
+ title: tr(lang, "cli", "init.choice.push.local"),
318
711
  value: false
319
712
  },
320
713
  {
321
- title: "remote - \uC6D0\uACA9\uC5D0\uB3C4 push",
714
+ title: tr(lang, "cli", "init.choice.push.remote"),
322
715
  value: true
323
716
  }
324
717
  ],
@@ -338,8 +731,8 @@ async function runInit(options) {
338
731
  {
339
732
  type: "text",
340
733
  name: "docsRemote",
341
- message: "\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"
734
+ message: tr(lang, "cli", "init.prompt.remoteUrl"),
735
+ validate: (value) => value.trim() ? true : tr(lang, "cli", "init.validation.enterUrl")
343
736
  }
344
737
  ],
345
738
  {
@@ -364,21 +757,27 @@ async function runInit(options) {
364
757
  const { overwrite } = await prompts({
365
758
  type: "confirm",
366
759
  name: "overwrite",
367
- message: `${targetDir} \uD3F4\uB354\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`,
760
+ message: tr(lang, "cli", "init.prompt.overwrite", { dir: targetDir }),
368
761
  initial: false
369
762
  });
370
763
  if (!overwrite) {
371
- console.log(chalk6.yellow("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
764
+ console.log(chalk6.yellow(tr(lang, "cli", "common.canceled")));
372
765
  return;
373
766
  }
374
767
  }
375
768
  }
376
769
  console.log();
377
- console.log(chalk6.blue("\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}`));
770
+ console.log(chalk6.blue(tr(lang, "cli", "init.log.creatingDocs")));
771
+ console.log(
772
+ chalk6.gray(` ${tr(lang, "cli", "init.log.projectLabel")}: ${projectName}`)
773
+ );
774
+ console.log(
775
+ chalk6.gray(` ${tr(lang, "cli", "init.log.typeLabel")}: ${projectType}`)
776
+ );
777
+ console.log(chalk6.gray(` ${tr(lang, "cli", "init.log.langLabel")}: ${lang}`));
778
+ console.log(
779
+ chalk6.gray(` ${tr(lang, "cli", "init.log.pathLabel")}: ${targetDir}`)
780
+ );
382
781
  console.log();
383
782
  const templatesDir = getTemplatesDir();
384
783
  const commonPath = path4.join(templatesDir, lang, "common");
@@ -387,7 +786,7 @@ async function runInit(options) {
387
786
  await copyTemplates(commonPath, targetDir);
388
787
  }
389
788
  if (!await fs8.pathExists(typePath)) {
390
- throw new Error(`\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${typePath}`);
789
+ throw new Error(tr(lang, "cli", "init.error.templateNotFound", { path: typePath }));
391
790
  }
392
791
  await copyTemplates(typePath, targetDir);
393
792
  const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
@@ -415,17 +814,17 @@ async function runInit(options) {
415
814
  }
416
815
  const configPath = path4.join(targetDir, ".lee-spec-kit.json");
417
816
  await fs8.writeJson(configPath, config, { spaces: 2 });
418
- console.log(chalk6.green("\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!"));
817
+ console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
419
818
  console.log();
420
- await initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote);
421
- console.log(chalk6.blue("\uB2E4\uC74C \uB2E8\uACC4:"));
422
- console.log(chalk6.gray(` 1. ${targetDir}/prd/README.md \uC791\uC131`));
819
+ await initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote);
820
+ console.log(chalk6.blue(tr(lang, "cli", "init.log.nextStepsTitle")));
423
821
  console.log(
424
- chalk6.gray(" 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00")
822
+ chalk6.gray(tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir }))
425
823
  );
824
+ console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps2")));
426
825
  console.log();
427
826
  }
428
- async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
827
+ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
429
828
  try {
430
829
  const runGit = (args, workdir) => {
431
830
  execFileSync("git", args, { cwd: workdir, stdio: "ignore" });
@@ -445,9 +844,9 @@ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
445
844
  };
446
845
  try {
447
846
  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..."));
847
+ console.log(chalk6.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit")));
449
848
  } catch {
450
- console.log(chalk6.blue("\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911..."));
849
+ console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
451
850
  runGit(["init"], cwd);
452
851
  }
453
852
  const relativePath = path4.relative(cwd, targetDir);
@@ -455,10 +854,10 @@ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
455
854
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
456
855
  console.log(
457
856
  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)'
857
+ tr(lang, "cli", "init.warn.stagedChangesSkip")
459
858
  )
460
859
  );
461
- console.log(chalk6.gray(" \uC218\uB3D9\uC73C\uB85C \uBCC0\uACBD \uB0B4\uC6A9\uC744 \uD655\uC778\uD55C \uB4A4 \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694."));
860
+ console.log(chalk6.gray(tr(lang, "cli", "init.warn.commitManually")));
462
861
  console.log();
463
862
  return;
464
863
  }
@@ -470,16 +869,18 @@ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
470
869
  if (docsRepo === "standalone" && pushDocs && docsRemote) {
471
870
  try {
472
871
  runGit(["remote", "add", "origin", docsRemote], cwd);
473
- console.log(chalk6.green(`\u2705 Git remote \uC124\uC815 \uC644\uB8CC: ${docsRemote}`));
872
+ console.log(
873
+ chalk6.green(tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote }))
874
+ );
474
875
  } catch {
475
- console.log(chalk6.yellow("\u26A0\uFE0F Git remote\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4."));
876
+ console.log(chalk6.yellow(tr(lang, "cli", "init.warn.gitRemoteExists")));
476
877
  }
477
878
  }
478
- console.log(chalk6.green("\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!"));
879
+ console.log(chalk6.green(tr(lang, "cli", "init.log.gitInitialCommitDone")));
479
880
  console.log();
480
881
  } catch {
481
882
  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)")
883
+ chalk6.yellow(tr(lang, "cli", "init.warn.skipGitInit"))
483
884
  );
484
885
  console.log();
485
886
  }
@@ -536,12 +937,10 @@ async function getConfig(cwd) {
536
937
  const fePath = path4.join(featuresPath, "fe");
537
938
  const projectType = await fs8.pathExists(bePath) || await fs8.pathExists(fePath) ? "fullstack" : "single";
538
939
  const agentsMdPath = path4.join(agentsPath, "agents.md");
539
- let lang = "ko";
940
+ let lang = "en";
540
941
  if (await fs8.pathExists(agentsMdPath)) {
541
942
  const content = await fs8.readFile(agentsMdPath, "utf-8");
542
- if (!/[가-힣]/.test(content)) {
543
- lang = "en";
544
- }
943
+ if (/[가-힣]/.test(content)) lang = "ko";
545
944
  }
546
945
  return { docsDir: resolvedDocsDir, projectType, lang };
547
946
  }
@@ -557,10 +956,13 @@ function featureCommand(program2) {
557
956
  await runFeature(name, options);
558
957
  } catch (error) {
559
958
  if (error instanceof Error && error.message === "canceled") {
560
- console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
959
+ const config = await getConfig(process.cwd());
960
+ const lang = config?.lang ?? DEFAULT_LANG;
961
+ console.log(chalk6.yellow(`
962
+ ${tr(lang, "cli", "common.canceled")}`));
561
963
  process.exit(0);
562
964
  }
563
- console.error(chalk6.red("\uC624\uB958:"), error);
965
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
564
966
  process.exit(1);
565
967
  }
566
968
  });
@@ -570,7 +972,9 @@ async function runFeature(name, options) {
570
972
  const config = await getConfig(cwd);
571
973
  if (!config) {
572
974
  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.")
975
+ chalk6.red(
976
+ tr(DEFAULT_LANG, "cli", "common.docsNotFound")
977
+ )
574
978
  );
575
979
  process.exit(1);
576
980
  }
@@ -582,7 +986,7 @@ async function runFeature(name, options) {
582
986
  {
583
987
  type: "select",
584
988
  name: "repo",
585
- message: "\uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
989
+ message: tr(lang, "cli", "feature.selectRepo"),
586
990
  choices: [
587
991
  { title: "Backend (be)", value: "be" },
588
992
  { title: "Frontend (fe)", value: "fe" }
@@ -615,12 +1019,20 @@ async function runFeature(name, options) {
615
1019
  const featureFolderName = `${featureId}-${name}`;
616
1020
  const featureDir = path4.join(featuresDir, featureFolderName);
617
1021
  if (await fs8.pathExists(featureDir)) {
618
- console.error(chalk6.red(`\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: ${featureDir}`));
1022
+ console.error(
1023
+ chalk6.red(
1024
+ tr(lang, "cli", "feature.folderExists", { path: featureDir })
1025
+ )
1026
+ );
619
1027
  process.exit(1);
620
1028
  }
621
1029
  const featureBasePath = path4.join(docsDir, "features", "feature-base");
622
1030
  if (!await fs8.pathExists(featureBasePath)) {
623
- console.error(chalk6.red("feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
1031
+ console.error(
1032
+ chalk6.red(
1033
+ tr(lang, "cli", "feature.baseNotFound")
1034
+ )
1035
+ );
624
1036
  process.exit(1);
625
1037
  }
626
1038
  await fs8.copy(featureBasePath, featureDir);
@@ -650,12 +1062,24 @@ async function runFeature(name, options) {
650
1062
  }
651
1063
  await replaceInFiles(featureDir, replacements);
652
1064
  console.log();
653
- console.log(chalk6.green(`\u2705 Feature \uD3F4\uB354 \uC0DD\uC131 \uC644\uB8CC: ${featureDir}`));
1065
+ console.log(
1066
+ chalk6.green(
1067
+ tr(lang, "cli", "feature.created", { path: featureDir })
1068
+ )
1069
+ );
654
1070
  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"));
1071
+ console.log(chalk6.blue(tr(lang, "cli", "feature.nextStepsTitle")));
1072
+ console.log(
1073
+ chalk6.gray(
1074
+ tr(lang, "cli", "feature.nextSteps1", { path: featureDir })
1075
+ )
1076
+ );
1077
+ console.log(chalk6.gray(tr(lang, "cli", "feature.nextSteps2")));
1078
+ console.log(
1079
+ chalk6.gray(
1080
+ tr(lang, "cli", "feature.nextSteps3")
1081
+ )
1082
+ );
659
1083
  console.log();
660
1084
  }
661
1085
  async function getNextFeatureId(docsDir, projectType) {
@@ -685,128 +1109,6 @@ async function getNextFeatureId(docsDir, projectType) {
685
1109
  return `F${String(next).padStart(width, "0")}`;
686
1110
  }
687
1111
 
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
1112
  // src/utils/context/steps.ts
811
1113
  function isCompletionChecklistDone(feature) {
812
1114
  return !!feature.completionChecklist && feature.completionChecklist.total > 0 && feature.completionChecklist.checked === feature.completionChecklist.total;
@@ -893,21 +1195,12 @@ function getStepDefinitions(lang) {
893
1195
  step: 6,
894
1196
  name: tr(lang, "steps", "tasksWrite"),
895
1197
  checklist: {
896
- done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.docs.prFieldExists && f.docs.prStatusFieldExists,
1198
+ done: (f) => f.docs.tasksExists && f.tasks.total > 0,
897
1199
  detail: (f) => f.tasks.total > 0 ? `(${f.tasks.total})` : ""
898
1200
  },
899
1201
  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)),
1202
+ when: (f) => f.planStatus === "Approved" && (!f.docs.tasksExists || f.tasks.total === 0),
901
1203
  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
1204
  if (!f.docs.tasksExists) {
912
1205
  return [
913
1206
  {
@@ -929,10 +1222,10 @@ function getStepDefinitions(lang) {
929
1222
  step: 7,
930
1223
  name: tr(lang, "steps", "docsCommitPlanning"),
931
1224
  checklist: {
932
- done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.git.docsHasUncommittedChanges
1225
+ done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && f.git.docsEverCommitted
933
1226
  },
934
1227
  current: {
935
- when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.activeTask && f.git.docsHasUncommittedChanges,
1228
+ when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.activeTask && !f.git.docsEverCommitted && f.git.docsHasUncommittedChanges,
936
1229
  actions: (f) => {
937
1230
  if (f.issueNumber) {
938
1231
  return [
@@ -1208,6 +1501,18 @@ function getGitStatusPorcelain(cwd, relativePaths) {
1208
1501
  return void 0;
1209
1502
  }
1210
1503
  }
1504
+ function getLastCommitForPath(cwd, relativePath) {
1505
+ try {
1506
+ const out = execSync(`git rev-list -n 1 HEAD -- "${relativePath}"`, {
1507
+ cwd,
1508
+ encoding: "utf-8",
1509
+ stdio: ["ignore", "pipe", "pipe"]
1510
+ }).trim();
1511
+ return out || void 0;
1512
+ } catch {
1513
+ return void 0;
1514
+ }
1515
+ }
1211
1516
  function getGitTopLevel(cwd) {
1212
1517
  try {
1213
1518
  return execSync("git rev-parse --show-toplevel", {
@@ -1420,12 +1725,20 @@ async function parseFeature(featurePath, type, context, options) {
1420
1725
  const relativeFeaturePathFromDocs = path4.relative(context.docsDir, featurePath);
1421
1726
  const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [relativeFeaturePathFromDocs]);
1422
1727
  const docsHasUncommittedChanges = docsStatus === void 0 ? true : docsStatus.trim().length > 0;
1728
+ const docsLastCommit = getLastCommitForPath(
1729
+ context.docsGitCwd,
1730
+ relativeFeaturePathFromDocs
1731
+ );
1732
+ const docsEverCommitted = !!docsLastCommit;
1423
1733
  if (docsStatus === void 0) {
1424
1734
  warnings.push(tr(lang, "warnings", "docsGitUnavailable"));
1425
1735
  }
1426
1736
  if (tasksExists && (!prFieldExists || !prStatusFieldExists)) {
1427
1737
  warnings.push(tr(lang, "warnings", "legacyTasksPrFields"));
1428
1738
  }
1739
+ if (docsEverCommitted && docsHasUncommittedChanges) {
1740
+ warnings.push(tr(lang, "warnings", "docsUncommittedChanges"));
1741
+ }
1429
1742
  const implementationDone = tasksExists && tasksSummary.total > 0 && tasksSummary.total === tasksSummary.done && isCompletionChecklistDone2({ completionChecklist });
1430
1743
  const workflowDone = implementationDone && specStatus === "Approved" && planStatus === "Approved" && isPrMetadataConfigured2({ docs: { prFieldExists, prStatusFieldExists } }) && !!prLink && prStatus === "Approved";
1431
1744
  if (implementationDone && !workflowDone) {
@@ -1468,6 +1781,7 @@ async function parseFeature(featurePath, type, context, options) {
1468
1781
  docsGitCwd: context.docsGitCwd,
1469
1782
  projectGitCwd: context.projectGitCwd,
1470
1783
  onExpectedBranch,
1784
+ docsEverCommitted,
1471
1785
  docsHasUncommittedChanges
1472
1786
  },
1473
1787
  docs: {
@@ -1594,7 +1908,7 @@ function statusCommand(program2) {
1594
1908
  try {
1595
1909
  await runStatus(options);
1596
1910
  } catch (error) {
1597
- console.error(chalk6.red("\uC624\uB958:"), error);
1911
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
1598
1912
  process.exit(1);
1599
1913
  }
1600
1914
  });
@@ -1603,12 +1917,15 @@ async function runStatus(options) {
1603
1917
  const cwd = process.cwd();
1604
1918
  const config = await getConfig(cwd);
1605
1919
  if (!config) {
1920
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")));
1606
1921
  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.")
1922
+ chalk6.red(
1923
+ tr(DEFAULT_LANG, "cli", "common.docsNotFound")
1924
+ )
1608
1925
  );
1609
1926
  process.exit(1);
1610
1927
  }
1611
- const { docsDir, projectType, projectName } = config;
1928
+ const { docsDir, projectType, projectName, lang } = config;
1612
1929
  const featuresDir = path4.join(docsDir, "features");
1613
1930
  const scan = await scanFeatures(config);
1614
1931
  const features = [];
@@ -1645,7 +1962,7 @@ async function runStatus(options) {
1645
1962
  });
1646
1963
  }
1647
1964
  if (features.length === 0) {
1648
- console.log(chalk6.yellow("Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
1965
+ console.log(chalk6.yellow(tr(lang, "cli", "status.noFeatures")));
1649
1966
  return;
1650
1967
  }
1651
1968
  if (options.strict) {
@@ -1653,7 +1970,7 @@ async function runStatus(options) {
1653
1970
  ([, paths]) => paths.length > 1
1654
1971
  );
1655
1972
  if (duplicates.length > 0) {
1656
- console.error(chalk6.red("\uC911\uBCF5 Feature ID \uBC1C\uACAC:"));
1973
+ console.error(chalk6.red(tr(lang, "cli", "status.duplicateIds")));
1657
1974
  for (const [id, paths] of duplicates) {
1658
1975
  console.error(chalk6.red(` ${id}:`));
1659
1976
  for (const p of paths) {
@@ -1664,7 +1981,7 @@ async function runStatus(options) {
1664
1981
  }
1665
1982
  const unknowns = [...idMap.entries()].filter(([id]) => id === "UNKNOWN");
1666
1983
  if (unknowns.length > 0) {
1667
- console.error(chalk6.red("Feature ID\uAC00 \uC5C6\uB294 \uD56D\uBAA9:"));
1984
+ console.error(chalk6.red(tr(lang, "cli", "status.missingIds")));
1668
1985
  for (const [, paths] of unknowns) {
1669
1986
  for (const p of paths) {
1670
1987
  console.error(chalk6.red(` - ${p}`));
@@ -1703,7 +2020,11 @@ async function runStatus(options) {
1703
2020
  ""
1704
2021
  ].join("\n");
1705
2022
  await fs8.writeFile(outputPath, content, "utf-8");
1706
- console.log(chalk6.green(`\u2705 ${outputPath} \uC0DD\uC131 \uC644\uB8CC`));
2023
+ console.log(
2024
+ chalk6.green(
2025
+ tr(lang, "cli", "status.wrote", { path: outputPath })
2026
+ )
2027
+ );
1707
2028
  }
1708
2029
  }
1709
2030
  function escapeRegExp2(value) {
@@ -1734,10 +2055,13 @@ function updateCommand(program2) {
1734
2055
  await runUpdate(options);
1735
2056
  } catch (error) {
1736
2057
  if (error instanceof Error && error.message === "canceled") {
1737
- console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2058
+ const config = await getConfig(process.cwd());
2059
+ const lang = config?.lang ?? DEFAULT_LANG;
2060
+ console.log(chalk6.yellow(`
2061
+ ${tr(lang, "cli", "common.canceled")}`));
1738
2062
  process.exit(0);
1739
2063
  }
1740
- console.error(chalk6.red("\uC624\uB958:"), error);
2064
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
1741
2065
  process.exit(1);
1742
2066
  }
1743
2067
  });
@@ -1746,8 +2070,11 @@ async function runUpdate(options) {
1746
2070
  const cwd = process.cwd();
1747
2071
  const config = await getConfig(cwd);
1748
2072
  if (!config) {
2073
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")));
1749
2074
  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.")
2075
+ chalk6.red(
2076
+ tr(DEFAULT_LANG, "cli", "common.docsNotFound")
2077
+ )
1751
2078
  );
1752
2079
  process.exit(1);
1753
2080
  }
@@ -1756,13 +2083,15 @@ async function runUpdate(options) {
1756
2083
  const sourceDir = path4.join(templatesDir, lang, projectType);
1757
2084
  const updateAgents = options.agents || !options.agents && !options.templates;
1758
2085
  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}`));
2086
+ console.log(chalk6.blue(tr(lang, "cli", "update.start")));
2087
+ console.log(chalk6.gray(` - ${tr(lang, "cli", "update.langLabel")}: ${lang}`));
2088
+ console.log(
2089
+ chalk6.gray(` - ${tr(lang, "cli", "update.typeLabel")}: ${projectType}`)
2090
+ );
1762
2091
  console.log();
1763
2092
  let updatedCount = 0;
1764
2093
  if (updateAgents) {
1765
- console.log(chalk6.blue("\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
2094
+ console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
1766
2095
  const commonAgents = path4.join(templatesDir, lang, "common", "agents");
1767
2096
  const typeAgents = path4.join(templatesDir, lang, projectType, "agents");
1768
2097
  const targetAgents = path4.join(docsDir, "agents");
@@ -1775,34 +2104,51 @@ async function runUpdate(options) {
1775
2104
  commonAgents,
1776
2105
  targetAgents,
1777
2106
  options.force,
1778
- replacements
2107
+ replacements,
2108
+ lang
1779
2109
  );
1780
2110
  updatedCount += count;
1781
2111
  }
1782
2112
  if (await fs8.pathExists(typeAgents)) {
1783
- const count = await updateFolder(typeAgents, targetAgents, options.force);
2113
+ const count = await updateFolder(
2114
+ typeAgents,
2115
+ targetAgents,
2116
+ options.force,
2117
+ void 0,
2118
+ lang
2119
+ );
1784
2120
  updatedCount += count;
1785
2121
  }
1786
- console.log(chalk6.green(` \u2705 agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
2122
+ console.log(chalk6.green(` \u2705 ${tr(lang, "cli", "update.agentsUpdated")}`));
1787
2123
  }
1788
2124
  if (updateTemplates) {
1789
- console.log(chalk6.blue("\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
2125
+ console.log(chalk6.blue(tr(lang, "cli", "update.updatingFeatureBase")));
1790
2126
  const sourceFeatureBase = path4.join(sourceDir, "features", "feature-base");
1791
2127
  const targetFeatureBase = path4.join(docsDir, "features", "feature-base");
1792
2128
  if (await fs8.pathExists(sourceFeatureBase)) {
1793
2129
  const count = await updateFolder(
1794
2130
  sourceFeatureBase,
1795
2131
  targetFeatureBase,
1796
- options.force
2132
+ options.force,
2133
+ void 0,
2134
+ lang
1797
2135
  );
1798
2136
  updatedCount += count;
1799
- console.log(chalk6.green(` \u2705 ${count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
2137
+ console.log(
2138
+ chalk6.green(
2139
+ ` \u2705 ${tr(lang, "cli", "update.filesUpdated", { count })}`
2140
+ )
2141
+ );
1800
2142
  }
1801
2143
  }
1802
2144
  console.log();
1803
- console.log(chalk6.green(`\u2705 \uCD1D ${updatedCount}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
2145
+ console.log(
2146
+ chalk6.green(
2147
+ `\u2705 ${tr(lang, "cli", "update.updatedTotal", { count: updatedCount })}`
2148
+ )
2149
+ );
1804
2150
  }
1805
- async function updateFolder(sourceDir, targetDir, force, replacements) {
2151
+ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG) {
1806
2152
  const protectedFiles = /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
1807
2153
  await fs8.ensureDir(targetDir);
1808
2154
  const files = await fs8.readdir(sourceDir);
@@ -1829,14 +2175,18 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
1829
2175
  }
1830
2176
  if (!force) {
1831
2177
  console.log(
1832
- chalk6.yellow(` \u26A0\uFE0F ${file} - \uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)`)
2178
+ chalk6.yellow(
2179
+ ` \u26A0\uFE0F ${file} - ${tr(lang, "cli", "update.changeDetected")}`
2180
+ )
1833
2181
  );
1834
2182
  shouldUpdate = false;
1835
2183
  }
1836
2184
  }
1837
2185
  if (shouldUpdate) {
1838
2186
  await fs8.writeFile(targetPath, sourceContent);
1839
- console.log(chalk6.gray(` \u{1F4C4} ${file} \uC5C5\uB370\uC774\uD2B8`));
2187
+ console.log(
2188
+ chalk6.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
2189
+ );
1840
2190
  updatedCount++;
1841
2191
  }
1842
2192
  } else if (stat.isDirectory()) {
@@ -1844,7 +2194,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
1844
2194
  sourcePath,
1845
2195
  targetPath,
1846
2196
  force,
1847
- replacements
2197
+ replacements,
2198
+ lang
1848
2199
  );
1849
2200
  updatedCount += subCount;
1850
2201
  }
@@ -1857,10 +2208,13 @@ function configCommand(program2) {
1857
2208
  await runConfig(options);
1858
2209
  } catch (error) {
1859
2210
  if (error instanceof Error && error.message === "canceled") {
1860
- console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2211
+ const config = await getConfig(process.cwd());
2212
+ const lang = config?.lang ?? DEFAULT_LANG;
2213
+ console.log(chalk6.yellow(`
2214
+ ${tr(lang, "cli", "common.canceled")}`));
1861
2215
  process.exit(0);
1862
2216
  }
1863
- console.error(chalk6.red("\uC624\uB958:"), error);
2217
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), error);
1864
2218
  process.exit(1);
1865
2219
  }
1866
2220
  });
@@ -1870,16 +2224,22 @@ async function runConfig(options) {
1870
2224
  const config = await getConfig(cwd);
1871
2225
  if (!config) {
1872
2226
  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.")
2227
+ chalk6.red(
2228
+ tr(DEFAULT_LANG, "cli", "common.configNotFound")
2229
+ )
1874
2230
  );
1875
2231
  process.exit(1);
1876
2232
  }
1877
2233
  const configPath = path4.join(config.docsDir, ".lee-spec-kit.json");
1878
2234
  if (!options.projectRoot) {
1879
2235
  console.log();
1880
- console.log(chalk6.blue("\u{1F4CB} \uD604\uC7AC \uC124\uC815:"));
2236
+ console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
1881
2237
  console.log();
1882
- console.log(chalk6.gray(` \uACBD\uB85C: ${configPath}`));
2238
+ console.log(
2239
+ chalk6.gray(
2240
+ ` ${tr(config.lang, "cli", "config.pathLabel")}: ${configPath}`
2241
+ )
2242
+ );
1883
2243
  console.log();
1884
2244
  const configFile2 = await fs8.readJson(configPath);
1885
2245
  console.log(JSON.stringify(configFile2, null, 2));
@@ -1889,7 +2249,9 @@ async function runConfig(options) {
1889
2249
  const configFile = await fs8.readJson(configPath);
1890
2250
  if (configFile.docsRepo !== "standalone") {
1891
2251
  console.log(
1892
- chalk6.yellow("\u26A0\uFE0F projectRoot\uB294 standalone \uBAA8\uB4DC\uC5D0\uC11C\uB9CC \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.")
2252
+ chalk6.yellow(
2253
+ tr(config.lang, "cli", "config.projectRootStandaloneOnly")
2254
+ )
1893
2255
  );
1894
2256
  return;
1895
2257
  }
@@ -1901,7 +2263,7 @@ async function runConfig(options) {
1901
2263
  {
1902
2264
  type: "select",
1903
2265
  name: "repo",
1904
- message: "\uC218\uC815\uD560 \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
2266
+ message: tr(config.lang, "cli", "config.selectRepoToUpdate"),
1905
2267
  choices: [
1906
2268
  { title: "Frontend (fe)", value: "fe" },
1907
2269
  { title: "Backend (be)", value: "be" }
@@ -1919,7 +2281,7 @@ async function runConfig(options) {
1919
2281
  if (!options.repo || !["fe", "be"].includes(options.repo)) {
1920
2282
  console.log(
1921
2283
  chalk6.red(
1922
- "Fullstack \uD504\uB85C\uC81D\uD2B8\uB294 --repo fe \uB610\uB294 --repo be\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
2284
+ tr(config.lang, "cli", "config.fullstackRepoRequired")
1923
2285
  )
1924
2286
  );
1925
2287
  return;
@@ -1936,13 +2298,20 @@ async function runConfig(options) {
1936
2298
  }
1937
2299
  console.log(
1938
2300
  chalk6.green(
1939
- `\u2705 ${options.repo.toUpperCase()} projectRoot \uC124\uC815 \uC644\uB8CC: ${options.projectRoot}`
2301
+ tr(config.lang, "cli", "config.projectRootSet", {
2302
+ repo: options.repo.toUpperCase(),
2303
+ path: options.projectRoot
2304
+ })
1940
2305
  )
1941
2306
  );
1942
2307
  } else {
1943
2308
  configFile.projectRoot = options.projectRoot;
1944
2309
  console.log(
1945
- chalk6.green(`\u2705 projectRoot \uC124\uC815 \uC644\uB8CC: ${options.projectRoot}`)
2310
+ chalk6.green(
2311
+ tr(config.lang, "cli", "config.projectRootSetSingle", {
2312
+ path: options.projectRoot
2313
+ })
2314
+ )
1946
2315
  );
1947
2316
  }
1948
2317
  await fs8.writeJson(configPath, configFile, { spaces: 2 });
@@ -1962,7 +2331,10 @@ function contextCommand(program2) {
1962
2331
  })
1963
2332
  );
1964
2333
  } else {
1965
- console.error(chalk6.red("\uC624\uB958:"), error);
2334
+ console.error(
2335
+ chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")),
2336
+ error
2337
+ );
1966
2338
  }
1967
2339
  process.exit(1);
1968
2340
  }
@@ -1985,12 +2357,43 @@ function detectFromBranch(branchName, features) {
1985
2357
  (f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
1986
2358
  );
1987
2359
  }
2360
+ function getListLabel(f, stepsMap, lang) {
2361
+ if (f.completion.implementationDone && !f.completion.workflowDone) {
2362
+ if (f.git.docsHasUncommittedChanges) {
2363
+ return tr(lang, "cli", "context.list.docsCommitNeeded");
2364
+ }
2365
+ if (!f.issueNumber) {
2366
+ return tr(lang, "cli", "context.list.issueNumberNeeded");
2367
+ }
2368
+ if (!f.docs.prFieldExists || !f.docs.prStatusFieldExists) {
2369
+ return tr(lang, "cli", "context.list.addPrMetadata");
2370
+ }
2371
+ if (!f.pr.link) {
2372
+ return tr(lang, "cli", "context.list.recordPrLink");
2373
+ }
2374
+ if (!f.pr.status) {
2375
+ return tr(lang, "cli", "context.list.setPrStatus");
2376
+ }
2377
+ if (f.pr.status !== "Approved") {
2378
+ return tr(lang, "cli", "context.list.prStatusToApproved", {
2379
+ status: f.pr.status
2380
+ });
2381
+ }
2382
+ if (f.specStatus !== "Approved") {
2383
+ return tr(lang, "cli", "context.list.approveSpec");
2384
+ }
2385
+ if (f.planStatus !== "Approved") {
2386
+ return tr(lang, "cli", "context.list.approvePlan");
2387
+ }
2388
+ }
2389
+ return stepsMap[f.currentStep] || "Unknown";
2390
+ }
1988
2391
  async function runContext(featureName, options) {
1989
2392
  const cwd = process.cwd();
1990
2393
  const config = await getConfig(cwd);
1991
- const lang = config?.lang ?? "ko";
2394
+ const lang = config?.lang ?? "en";
1992
2395
  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.");
2396
+ throw new Error(tr(DEFAULT_LANG, "cli", "common.configNotFound"));
1994
2397
  }
1995
2398
  const stepDefinitions = getStepDefinitions(lang);
1996
2399
  const stepsMap = getStepsMap(lang);
@@ -2104,12 +2507,16 @@ async function runContext(featureName, options) {
2104
2507
  console.log(chalk6.gray("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
2105
2508
  console.log();
2106
2509
  if (features.length === 0) {
2107
- console.log(chalk6.yellow("\u26A0\uFE0F \uC9C4\uD589 \uC911\uC778 Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2510
+ console.log(
2511
+ chalk6.yellow(
2512
+ tr(lang, "cli", "context.noActiveFeatures")
2513
+ )
2514
+ );
2108
2515
  console.log();
2109
2516
  return;
2110
2517
  }
2111
2518
  if (warnings.length > 0) {
2112
- console.log(chalk6.yellow("\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:"));
2519
+ console.log(chalk6.yellow(tr(lang, "cli", "context.envWarnings")));
2113
2520
  warnings.forEach((w) => console.log(chalk6.yellow(` - ${w}`)));
2114
2521
  console.log();
2115
2522
  }
@@ -2117,24 +2524,36 @@ async function runContext(featureName, options) {
2117
2524
  if (selectionMode === "open") {
2118
2525
  console.log(
2119
2526
  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)`
2527
+ ` ${tr(lang, "cli", "context.openFallbackSummary", {
2528
+ inProgress: inProgressFeatures.length,
2529
+ readyToClose: readyToCloseFeatures.length,
2530
+ done: doneFeatures.length
2531
+ })}`
2121
2532
  )
2122
2533
  );
2123
2534
  console.log();
2124
2535
  }
2125
2536
  if (selectionMode === "open") {
2126
- console.log(chalk6.blue(`\u{1F539} In Progress (${inProgressFeatures.length})`));
2537
+ console.log(
2538
+ chalk6.blue(
2539
+ `\u{1F539} ${tr(lang, "cli", "context.sectionInProgress")} (${inProgressFeatures.length})`
2540
+ )
2541
+ );
2127
2542
  inProgressFeatures.forEach((f2) => {
2128
- const stepName2 = stepsMap[f2.currentStep] || "Unknown";
2543
+ const stepName2 = getListLabel(f2, stepsMap, lang);
2129
2544
  const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
2130
2545
  console.log(
2131
2546
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
2132
2547
  );
2133
2548
  });
2134
2549
  console.log();
2135
- console.log(chalk6.blue(`\u{1F538} Ready To Close (${readyToCloseFeatures.length})`));
2550
+ console.log(
2551
+ chalk6.blue(
2552
+ `\u{1F538} ${tr(lang, "cli", "context.sectionReadyToClose")} (${readyToCloseFeatures.length})`
2553
+ )
2554
+ );
2136
2555
  readyToCloseFeatures.forEach((f2) => {
2137
- const stepName2 = stepsMap[f2.currentStep] || "Unknown";
2556
+ const stepName2 = getListLabel(f2, stepsMap, lang);
2138
2557
  const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
2139
2558
  console.log(
2140
2559
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -2145,7 +2564,7 @@ async function runContext(featureName, options) {
2145
2564
  console.log(chalk6.blue(title));
2146
2565
  console.log();
2147
2566
  targetFeatures.forEach((f2) => {
2148
- const stepName2 = stepsMap[f2.currentStep] || "Unknown";
2567
+ const stepName2 = getListLabel(f2, stepsMap, lang);
2149
2568
  const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
2150
2569
  console.log(
2151
2570
  ` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
@@ -2153,20 +2572,28 @@ async function runContext(featureName, options) {
2153
2572
  });
2154
2573
  }
2155
2574
  console.log();
2156
- console.log(chalk6.gray("Tip: \uD2B9\uC815 Feature\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uBCF4\uB824\uBA74:"));
2575
+ console.log(chalk6.gray(tr(lang, "cli", "context.tipDetails")));
2157
2576
  console.log(
2158
2577
  chalk6.gray(" $ npx lee-spec-kit context <slug|F001|F001-slug> [--repo fe|be]")
2159
2578
  );
2160
2579
  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"));
2580
+ console.log(
2581
+ chalk6.gray(
2582
+ ` $ npx lee-spec-kit context --all # ${tr(lang, "cli", "context.tipShowAll")}`
2583
+ )
2584
+ );
2585
+ console.log(
2586
+ chalk6.gray(
2587
+ ` $ npx lee-spec-kit context --done # ${tr(lang, "cli", "context.tipShowDone")}`
2588
+ )
2589
+ );
2163
2590
  }
2164
2591
  console.log();
2165
2592
  return;
2166
2593
  }
2167
2594
  const f = targetFeatures[0];
2168
2595
  const stepName = stepsMap[f.currentStep] || "Unknown";
2169
- const okTag = (requiresUserOk) => requiresUserOk ? chalk6.yellow(lang === "ko" ? "[OK \uD544\uC694] " : "[OK required] ") : "";
2596
+ const okTag = (requiresUserOk) => requiresUserOk ? chalk6.yellow(tr(lang, "cli", "context.okRequired")) : "";
2170
2597
  console.log(
2171
2598
  `\u{1F539} Feature: ${chalk6.bold(f.folderName)} ${config.projectType === "fullstack" ? chalk6.cyan(`(${f.type})`) : ""}`
2172
2599
  );
@@ -2240,9 +2667,6 @@ function printChecklist(f, stepDefinitions) {
2240
2667
  console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
2241
2668
  });
2242
2669
  }
2243
- function msg(lang, ko, en) {
2244
- return lang === "en" ? en : ko;
2245
- }
2246
2670
  function formatPath(cwd, p) {
2247
2671
  if (!p) return "";
2248
2672
  return path4.isAbsolute(p) ? path4.relative(cwd, p) : p;
@@ -2277,11 +2701,7 @@ async function checkDocsStructure(config, cwd) {
2277
2701
  issues.push({
2278
2702
  level: "error",
2279
2703
  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
- ),
2704
+ message: tr(config.lang, "cli", "doctor.issue.missingRequiredDir", { dir }),
2285
2705
  path: formatPath(cwd, p)
2286
2706
  });
2287
2707
  }
@@ -2291,11 +2711,7 @@ async function checkDocsStructure(config, cwd) {
2291
2711
  issues.push({
2292
2712
  level: "warn",
2293
2713
  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
- ),
2714
+ message: tr(config.lang, "cli", "doctor.issue.missingConfig"),
2299
2715
  path: formatPath(cwd, configPath)
2300
2716
  });
2301
2717
  }
@@ -2307,11 +2723,7 @@ async function checkFeatures(config, cwd, features) {
2307
2723
  issues.push({
2308
2724
  level: "warn",
2309
2725
  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
- )
2726
+ message: tr(config.lang, "cli", "doctor.issue.noFeatures")
2315
2727
  });
2316
2728
  return issues;
2317
2729
  }
@@ -2331,11 +2743,9 @@ async function checkFeatures(config, cwd, features) {
2331
2743
  issues.push({
2332
2744
  level: "warn",
2333
2745
  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
- ),
2746
+ message: tr(config.lang, "cli", "doctor.issue.placeholdersLeft", {
2747
+ placeholders: placeholders.join(", ")
2748
+ }),
2339
2749
  path: formatPath(cwd, p)
2340
2750
  });
2341
2751
  }
@@ -2343,22 +2753,14 @@ async function checkFeatures(config, cwd, features) {
2343
2753
  issues.push({
2344
2754
  level: "warn",
2345
2755
  code: "missing_spec",
2346
- message: msg(
2347
- config.lang,
2348
- "spec.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
2349
- "Missing spec.md."
2350
- ),
2756
+ message: tr(config.lang, "cli", "doctor.issue.missingSpec"),
2351
2757
  path: formatPath(cwd, f.path)
2352
2758
  });
2353
2759
  } else if (!f.specStatus) {
2354
2760
  issues.push({
2355
2761
  level: "warn",
2356
2762
  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
- ),
2763
+ message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
2362
2764
  path: formatPath(cwd, path4.join(f.path, "spec.md"))
2363
2765
  });
2364
2766
  }
@@ -2366,11 +2768,7 @@ async function checkFeatures(config, cwd, features) {
2366
2768
  issues.push({
2367
2769
  level: "warn",
2368
2770
  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
- ),
2771
+ message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
2374
2772
  path: formatPath(cwd, path4.join(f.path, "plan.md"))
2375
2773
  });
2376
2774
  }
@@ -2378,11 +2776,7 @@ async function checkFeatures(config, cwd, features) {
2378
2776
  issues.push({
2379
2777
  level: "warn",
2380
2778
  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
- ),
2779
+ message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
2386
2780
  path: formatPath(cwd, path4.join(f.path, "tasks.md"))
2387
2781
  });
2388
2782
  }
@@ -2394,11 +2788,10 @@ async function checkFeatures(config, cwd, features) {
2394
2788
  issues.push({
2395
2789
  level: "warn",
2396
2790
  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
- ),
2791
+ message: tr(config.lang, "cli", "doctor.issue.duplicateFeatureId", {
2792
+ id,
2793
+ count: String(paths.length)
2794
+ }),
2402
2795
  path: formatPath(cwd, paths[0])
2403
2796
  });
2404
2797
  }
@@ -2407,11 +2800,7 @@ async function checkFeatures(config, cwd, features) {
2407
2800
  issues.push({
2408
2801
  level: "warn",
2409
2802
  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
- ),
2803
+ message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
2415
2804
  path: formatPath(cwd, path4.join(config.docsDir, p))
2416
2805
  });
2417
2806
  }
@@ -2422,11 +2811,11 @@ function doctorCommand(program2) {
2422
2811
  const cwd = process.cwd();
2423
2812
  const config = await getConfig(cwd);
2424
2813
  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.";
2814
+ const message = tr(DEFAULT_LANG, "cli", "common.configNotFound");
2426
2815
  if (options.json) {
2427
2816
  console.log(JSON.stringify({ status: "error", error: message }, null, 2));
2428
2817
  } else {
2429
- console.error(chalk6.red("\uC624\uB958:"), message);
2818
+ console.error(chalk6.red(tr(DEFAULT_LANG, "cli", "common.errorLabel")), message);
2430
2819
  }
2431
2820
  process.exit(1);
2432
2821
  }
@@ -2461,32 +2850,40 @@ function doctorCommand(program2) {
2461
2850
  process.exit(exitCode);
2462
2851
  }
2463
2852
  console.log();
2464
- console.log(chalk6.bold("\u{1F50E} Docs Doctor"));
2853
+ console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
2465
2854
  console.log(chalk6.gray(`- Docs: ${path4.relative(cwd, docsDir)}`));
2466
2855
  console.log(chalk6.gray(`- Type: ${projectType}`));
2467
2856
  console.log(chalk6.gray(`- Lang: ${lang}`));
2468
2857
  console.log();
2469
2858
  if (warnings.length > 0) {
2470
- console.log(chalk6.yellow("\u26A0\uFE0F Environment warnings:"));
2859
+ console.log(
2860
+ chalk6.yellow(tr(lang, "cli", "doctor.envWarnings"))
2861
+ );
2471
2862
  warnings.forEach((w) => console.log(chalk6.yellow(` - ${w}`)));
2472
2863
  console.log();
2473
2864
  }
2474
2865
  if (!hasIssues) {
2475
- console.log(chalk6.green("\u2705 \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."));
2866
+ console.log(chalk6.green(tr(lang, "cli", "doctor.noIssues")));
2476
2867
  console.log();
2477
2868
  process.exit(0);
2478
2869
  }
2479
2870
  const errors = issues.filter((i) => i.level === "error");
2480
2871
  const warns = issues.filter((i) => i.level === "warn");
2481
2872
  if (errors.length > 0) {
2482
- console.log(chalk6.red(`\u274C Errors (${errors.length})`));
2873
+ console.log(
2874
+ chalk6.red(`\u274C ${tr(lang, "cli", "doctor.errorsTitle")} (${errors.length})`)
2875
+ );
2483
2876
  errors.forEach(
2484
2877
  (i) => console.log(chalk6.red(` - ${i.message}${i.path ? ` (${i.path})` : ""}`))
2485
2878
  );
2486
2879
  console.log();
2487
2880
  }
2488
2881
  if (warns.length > 0) {
2489
- console.log(chalk6.yellow(`\u26A0\uFE0F Warnings (${warns.length})`));
2882
+ console.log(
2883
+ chalk6.yellow(
2884
+ `\u26A0\uFE0F ${tr(lang, "cli", "doctor.warningsTitle")} (${warns.length})`
2885
+ )
2886
+ );
2490
2887
  warns.forEach(
2491
2888
  (i) => console.log(
2492
2889
  chalk6.yellow(` - ${i.message}${i.path ? ` (${i.path})` : ""}`)
@@ -2496,7 +2893,9 @@ function doctorCommand(program2) {
2496
2893
  }
2497
2894
  console.log(
2498
2895
  chalk6.gray(
2499
- `Tip: \uC5D0\uC774\uC804\uD2B8\uC6A9 JSON \uCD9C\uB825: npx lee-spec-kit doctor --json${options.strict ? " --strict" : ""}`
2896
+ tr(lang, "cli", "doctor.tipJson", {
2897
+ strictFlag: options.strict ? " --strict" : ""
2898
+ })
2500
2899
  )
2501
2900
  );
2502
2901
  console.log();