lee-spec-kit 0.6.0 → 0.6.2

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 (34) hide show
  1. package/README.en.md +3 -2
  2. package/README.md +3 -2
  3. package/dist/index.js +529 -199
  4. package/package.json +1 -1
  5. package/templates/en/common/agents/git-workflow.md +8 -8
  6. package/templates/en/common/agents/pr-template.md +1 -1
  7. package/templates/en/common/agents/skills/create-issue.md +16 -6
  8. package/templates/en/common/agents/skills/create-pr.md +17 -7
  9. package/templates/en/common/agents/skills/execute-task.md +1 -0
  10. package/templates/en/fullstack/README.md +2 -2
  11. package/templates/en/fullstack/agents/agents.md +16 -21
  12. package/templates/en/fullstack/features/feature-base/plan.md +1 -1
  13. package/templates/en/fullstack/features/feature-base/spec.md +1 -1
  14. package/templates/en/fullstack/features/feature-base/tasks.md +7 -3
  15. package/templates/en/single/README.md +2 -2
  16. package/templates/en/single/agents/agents.md +16 -22
  17. package/templates/en/single/features/feature-base/plan.md +1 -1
  18. package/templates/en/single/features/feature-base/spec.md +1 -1
  19. package/templates/en/single/features/feature-base/tasks.md +7 -3
  20. package/templates/ko/common/agents/git-workflow.md +8 -8
  21. package/templates/ko/common/agents/pr-template.md +1 -1
  22. package/templates/ko/common/agents/skills/create-issue.md +16 -6
  23. package/templates/ko/common/agents/skills/create-pr.md +17 -7
  24. package/templates/ko/common/agents/skills/execute-task.md +4 -3
  25. package/templates/ko/fullstack/README.md +2 -2
  26. package/templates/ko/fullstack/agents/agents.md +16 -21
  27. package/templates/ko/fullstack/features/feature-base/plan.md +1 -1
  28. package/templates/ko/fullstack/features/feature-base/spec.md +1 -1
  29. package/templates/ko/fullstack/features/feature-base/tasks.md +6 -2
  30. package/templates/ko/single/README.md +2 -2
  31. package/templates/ko/single/agents/agents.md +16 -21
  32. package/templates/ko/single/features/feature-base/plan.md +1 -1
  33. package/templates/ko/single/features/feature-base/spec.md +1 -1
  34. package/templates/ko/single/features/feature-base/tasks.md +6 -2
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import path6 from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { program } from 'commander';
5
- import fs2 from 'fs-extra';
5
+ import fs14 from 'fs-extra';
6
6
  import prompts from 'prompts';
7
7
  import chalk6 from 'chalk';
8
8
  import { glob } from 'glob';
@@ -14,7 +14,7 @@ var getFilename = () => fileURLToPath(import.meta.url);
14
14
  var getDirname = () => path6.dirname(getFilename());
15
15
  var __dirname$1 = /* @__PURE__ */ getDirname();
16
16
  async function copyTemplates(src, dest) {
17
- await fs2.copy(src, dest, {
17
+ await fs14.copy(src, dest, {
18
18
  overwrite: true,
19
19
  errorOnExist: false
20
20
  });
@@ -30,15 +30,15 @@ function applyReplacements(content, replacements) {
30
30
  async function replaceInFiles(dir, replacements) {
31
31
  const files = await glob("**/*.md", { cwd: dir, absolute: true });
32
32
  for (const file of files) {
33
- let content = await fs2.readFile(file, "utf-8");
33
+ let content = await fs14.readFile(file, "utf-8");
34
34
  content = applyReplacements(content, replacements);
35
- await fs2.writeFile(file, content, "utf-8");
35
+ await fs14.writeFile(file, content, "utf-8");
36
36
  }
37
37
  const shFiles = await glob("**/*.sh", { cwd: dir, absolute: true });
38
38
  for (const file of shFiles) {
39
- let content = await fs2.readFile(file, "utf-8");
39
+ let content = await fs14.readFile(file, "utf-8");
40
40
  content = applyReplacements(content, replacements);
41
- await fs2.writeFile(file, content, "utf-8");
41
+ await fs14.writeFile(file, content, "utf-8");
42
42
  }
43
43
  }
44
44
  var __filename2 = fileURLToPath(import.meta.url);
@@ -722,23 +722,23 @@ function getCliErrorSuggestions(code, lang = DEFAULT_LANG) {
722
722
  [
723
723
  {
724
724
  title: {
725
- ko: "--approve <\uB77C\uBCA8>\uACFC \uD568\uAED8 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
726
- en: "Re-run with --approve <label>."
725
+ ko: "context \uC2B9\uC778 \uD750\uB984\uC774\uBA74 --approve <\uB77C\uBCA8>\uACFC \uD568\uAED8 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
726
+ en: "For context approval flow, re-run with --approve <label>."
727
727
  },
728
728
  command: "npx lee-spec-kit context --approve A"
729
729
  },
730
730
  {
731
731
  title: {
732
- ko: "\uC2B9\uC778\uB41C \uC635\uC158\uC774 command\uC77C \uB54C\uB9CC --execute\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
733
- en: "Add --execute only when the approved option is a command."
734
- }
732
+ ko: "github \uC6D0\uACA9 \uC0DD\uC131/\uBA38\uC9C0\uBA74 --confirm OK\uB97C \uD568\uAED8 \uC804\uB2EC\uD558\uC138\uC694.",
733
+ en: "For github remote create/merge, pass --confirm OK."
734
+ },
735
+ command: "npx lee-spec-kit github pr F001 --create --confirm OK"
735
736
  },
736
737
  {
737
738
  title: {
738
- ko: "\uBA3C\uC800 \uC635\uC158\uC744 \uC870\uD68C\uD55C \uB4A4 \uB77C\uBCA8 1\uAC1C\uB97C \uC120\uD0DD\uD558\uC138\uC694.",
739
- en: "List options first, then choose one label."
740
- },
741
- command: "npx lee-spec-kit context"
739
+ ko: "\uC2E4\uD589 \uC804\uC5D0 \uC81C\uBAA9/\uBCF8\uBB38/\uB77C\uBCA8(\uB610\uB294 \uBA38\uC9C0 \uACC4\uD68D)\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uBA85\uC2DC\uC801 \uC2B9\uC778\uC744 \uBC1B\uC73C\uC138\uC694.",
740
+ en: "Share title/body/labels (or merge plan) and get explicit user approval first."
741
+ }
742
742
  }
743
743
  ],
744
744
  resolvedLang
@@ -1069,7 +1069,7 @@ function getInitLockPath(targetDir) {
1069
1069
  }
1070
1070
  async function isStaleLock(lockPath, staleMs) {
1071
1071
  try {
1072
- const stat = await fs2.stat(lockPath);
1072
+ const stat = await fs14.stat(lockPath);
1073
1073
  if (Date.now() - stat.mtimeMs <= staleMs) {
1074
1074
  return false;
1075
1075
  }
@@ -1084,7 +1084,7 @@ async function isStaleLock(lockPath, staleMs) {
1084
1084
  }
1085
1085
  async function readLockPayload(lockPath) {
1086
1086
  try {
1087
- const raw = await fs2.readFile(lockPath, "utf8");
1087
+ const raw = await fs14.readFile(lockPath, "utf8");
1088
1088
  const parsed = JSON.parse(raw);
1089
1089
  if (!parsed || typeof parsed !== "object") return null;
1090
1090
  return parsed;
@@ -1106,17 +1106,17 @@ function isProcessAlive(pid) {
1106
1106
  }
1107
1107
  }
1108
1108
  async function tryAcquire(lockPath, owner) {
1109
- await fs2.ensureDir(path6.dirname(lockPath));
1109
+ await fs14.ensureDir(path6.dirname(lockPath));
1110
1110
  try {
1111
- const fd = await fs2.open(lockPath, "wx");
1111
+ const fd = await fs14.open(lockPath, "wx");
1112
1112
  const payload = JSON.stringify(
1113
1113
  { pid: process.pid, owner: owner ?? "unknown", createdAt: (/* @__PURE__ */ new Date()).toISOString() },
1114
1114
  null,
1115
1115
  2
1116
1116
  );
1117
- await fs2.writeFile(fd, `${payload}
1117
+ await fs14.writeFile(fd, `${payload}
1118
1118
  `, { encoding: "utf8" });
1119
- await fs2.close(fd);
1119
+ await fs14.close(fd);
1120
1120
  return true;
1121
1121
  } catch (error) {
1122
1122
  if (error.code === "EEXIST") {
@@ -1130,9 +1130,9 @@ async function waitForLockRelease(lockPath, options = {}) {
1130
1130
  const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
1131
1131
  const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
1132
1132
  const startedAt = Date.now();
1133
- while (await fs2.pathExists(lockPath)) {
1133
+ while (await fs14.pathExists(lockPath)) {
1134
1134
  if (await isStaleLock(lockPath, staleMs)) {
1135
- await fs2.remove(lockPath);
1135
+ await fs14.remove(lockPath);
1136
1136
  break;
1137
1137
  }
1138
1138
  if (Date.now() - startedAt > timeoutMs) {
@@ -1150,7 +1150,7 @@ async function withFileLock(lockPath, task, options = {}) {
1150
1150
  const acquired = await tryAcquire(lockPath, options.owner);
1151
1151
  if (acquired) break;
1152
1152
  if (await isStaleLock(lockPath, staleMs)) {
1153
- await fs2.remove(lockPath);
1153
+ await fs14.remove(lockPath);
1154
1154
  continue;
1155
1155
  }
1156
1156
  if (Date.now() - startedAt > timeoutMs) {
@@ -1164,7 +1164,7 @@ async function withFileLock(lockPath, task, options = {}) {
1164
1164
  try {
1165
1165
  return await task();
1166
1166
  } finally {
1167
- await fs2.remove(lockPath).catch(() => {
1167
+ await fs14.remove(lockPath).catch(() => {
1168
1168
  });
1169
1169
  }
1170
1170
  }
@@ -1191,21 +1191,21 @@ async function pruneEngineManagedDocs(docsDir) {
1191
1191
  const removed = [];
1192
1192
  for (const file of ENGINE_MANAGED_AGENT_FILES) {
1193
1193
  const target = path6.join(docsDir, "agents", file);
1194
- if (await fs2.pathExists(target)) {
1195
- await fs2.remove(target);
1194
+ if (await fs14.pathExists(target)) {
1195
+ await fs14.remove(target);
1196
1196
  removed.push(path6.relative(docsDir, target));
1197
1197
  }
1198
1198
  }
1199
1199
  for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
1200
1200
  const target = path6.join(docsDir, "agents", dir);
1201
- if (await fs2.pathExists(target)) {
1202
- await fs2.remove(target);
1201
+ if (await fs14.pathExists(target)) {
1202
+ await fs14.remove(target);
1203
1203
  removed.push(path6.relative(docsDir, target));
1204
1204
  }
1205
1205
  }
1206
1206
  const featureBasePath = path6.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1207
- if (await fs2.pathExists(featureBasePath)) {
1208
- await fs2.remove(featureBasePath);
1207
+ if (await fs14.pathExists(featureBasePath)) {
1208
+ await fs14.remove(featureBasePath);
1209
1209
  removed.push(path6.relative(docsDir, featureBasePath));
1210
1210
  }
1211
1211
  return removed;
@@ -1571,8 +1571,8 @@ async function runInit(options) {
1571
1571
  await withFileLock(
1572
1572
  initLockPath,
1573
1573
  async () => {
1574
- if (await fs2.pathExists(targetDir)) {
1575
- const files = await fs2.readdir(targetDir);
1574
+ if (await fs14.pathExists(targetDir)) {
1575
+ const files = await fs14.readdir(targetDir);
1576
1576
  if (files.length > 0) {
1577
1577
  if (options.force) {
1578
1578
  } else if (options.nonInteractive) {
@@ -1611,10 +1611,10 @@ async function runInit(options) {
1611
1611
  const commonPath = path6.join(templatesDir, lang, "common");
1612
1612
  const templateProjectType = toTemplateProjectType(projectType);
1613
1613
  const typePath = path6.join(templatesDir, lang, templateProjectType);
1614
- if (await fs2.pathExists(commonPath)) {
1614
+ if (await fs14.pathExists(commonPath)) {
1615
1615
  await copyTemplates(commonPath, targetDir);
1616
1616
  }
1617
- if (!await fs2.pathExists(typePath)) {
1617
+ if (!await fs14.pathExists(typePath)) {
1618
1618
  throw new Error(
1619
1619
  tr(lang, "cli", "init.error.templateNotFound", { path: typePath })
1620
1620
  );
@@ -1622,14 +1622,14 @@ async function runInit(options) {
1622
1622
  await copyTemplates(typePath, targetDir);
1623
1623
  if (projectType === "multi" && !isDefaultFullstackComponents(components)) {
1624
1624
  const featuresRoot = path6.join(targetDir, "features");
1625
- await fs2.remove(path6.join(featuresRoot, "fe"));
1626
- await fs2.remove(path6.join(featuresRoot, "be"));
1625
+ await fs14.remove(path6.join(featuresRoot, "fe"));
1626
+ await fs14.remove(path6.join(featuresRoot, "be"));
1627
1627
  for (const component of components) {
1628
1628
  const componentDir = path6.join(featuresRoot, component);
1629
- await fs2.ensureDir(componentDir);
1629
+ await fs14.ensureDir(componentDir);
1630
1630
  const readmePath = path6.join(componentDir, "README.md");
1631
- if (!await fs2.pathExists(readmePath)) {
1632
- await fs2.writeFile(
1631
+ if (!await fs14.pathExists(readmePath)) {
1632
+ await fs14.writeFile(
1633
1633
  readmePath,
1634
1634
  `# ${component.toUpperCase()} Features
1635
1635
 
@@ -1679,7 +1679,7 @@ Store ${component} feature specs here.
1679
1679
  }
1680
1680
  }
1681
1681
  const configPath = path6.join(targetDir, ".lee-spec-kit.json");
1682
- await fs2.writeJson(configPath, config, { spaces: 2 });
1682
+ await fs14.writeJson(configPath, config, { spaces: 2 });
1683
1683
  console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
1684
1684
  console.log();
1685
1685
  await initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote);
@@ -1814,7 +1814,7 @@ function getAncestorDirs(startDir) {
1814
1814
  return dirs;
1815
1815
  }
1816
1816
  function hasWorkspaceBoundary(dir) {
1817
- return fs2.existsSync(path6.join(dir, "package.json")) || fs2.existsSync(path6.join(dir, ".git"));
1817
+ return fs14.existsSync(path6.join(dir, "package.json")) || fs14.existsSync(path6.join(dir, ".git"));
1818
1818
  }
1819
1819
  function getSearchBaseDirs(cwd) {
1820
1820
  const ancestors = getAncestorDirs(cwd);
@@ -1842,9 +1842,9 @@ async function getConfig(cwd) {
1842
1842
  if (visitedDocsDirs.has(resolvedDocsDir)) continue;
1843
1843
  visitedDocsDirs.add(resolvedDocsDir);
1844
1844
  const configPath = path6.join(resolvedDocsDir, ".lee-spec-kit.json");
1845
- if (await fs2.pathExists(configPath)) {
1845
+ if (await fs14.pathExists(configPath)) {
1846
1846
  try {
1847
- const configFile = await fs2.readJson(configPath);
1847
+ const configFile = await fs14.readJson(configPath);
1848
1848
  const projectType = normalizeProjectType(configFile.projectType);
1849
1849
  const components = resolveProjectComponents(
1850
1850
  projectType,
@@ -1869,10 +1869,10 @@ async function getConfig(cwd) {
1869
1869
  }
1870
1870
  const agentsPath = path6.join(resolvedDocsDir, "agents");
1871
1871
  const featuresPath = path6.join(resolvedDocsDir, "features");
1872
- if (await fs2.pathExists(agentsPath) && await fs2.pathExists(featuresPath)) {
1872
+ if (await fs14.pathExists(agentsPath) && await fs14.pathExists(featuresPath)) {
1873
1873
  const bePath = path6.join(featuresPath, "be");
1874
1874
  const fePath = path6.join(featuresPath, "fe");
1875
- const projectType = await fs2.pathExists(bePath) || await fs2.pathExists(fePath) ? "multi" : "single";
1875
+ const projectType = await fs14.pathExists(bePath) || await fs14.pathExists(fePath) ? "multi" : "single";
1876
1876
  const components = projectType === "multi" ? resolveProjectComponents("multi", ["fe", "be"]) : void 0;
1877
1877
  const langProbeCandidates = [
1878
1878
  path6.join(agentsPath, "custom.md"),
@@ -1881,8 +1881,8 @@ async function getConfig(cwd) {
1881
1881
  ];
1882
1882
  let lang = "en";
1883
1883
  for (const candidate of langProbeCandidates) {
1884
- if (!await fs2.pathExists(candidate)) continue;
1885
- const content = await fs2.readFile(candidate, "utf-8");
1884
+ if (!await fs14.pathExists(candidate)) continue;
1885
+ const content = await fs14.readFile(candidate, "utf-8");
1886
1886
  if (/[가-힣]/.test(content)) {
1887
1887
  lang = "ko";
1888
1888
  break;
@@ -1928,9 +1928,9 @@ function sanitizeTasksForLocal(content, lang) {
1928
1928
  return normalizeTrailingBlankLines(next);
1929
1929
  }
1930
1930
  async function patchMarkdownIfExists(filePath, transform) {
1931
- if (!await fs2.pathExists(filePath)) return;
1932
- const content = await fs2.readFile(filePath, "utf-8");
1933
- await fs2.writeFile(filePath, transform(content), "utf-8");
1931
+ if (!await fs14.pathExists(filePath)) return;
1932
+ const content = await fs14.readFile(filePath, "utf-8");
1933
+ await fs14.writeFile(filePath, transform(content), "utf-8");
1934
1934
  }
1935
1935
  async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
1936
1936
  await patchMarkdownIfExists(path6.join(featureDir, "spec.md"), sanitizeSpecForLocal);
@@ -2083,7 +2083,7 @@ async function runFeature(name, options) {
2083
2083
  }
2084
2084
  const featureFolderName = `${featureId}-${name}`;
2085
2085
  const featureDir = path6.join(featuresDir, featureFolderName);
2086
- if (await fs2.pathExists(featureDir)) {
2086
+ if (await fs14.pathExists(featureDir)) {
2087
2087
  throw createCliError(
2088
2088
  "INVALID_ARGUMENT",
2089
2089
  tr(lang, "cli", "feature.folderExists", { path: featureDir })
@@ -2096,10 +2096,10 @@ async function runFeature(name, options) {
2096
2096
  "features",
2097
2097
  "feature-base"
2098
2098
  );
2099
- if (!await fs2.pathExists(featureBasePath)) {
2099
+ if (!await fs14.pathExists(featureBasePath)) {
2100
2100
  throw createCliError("DOCS_NOT_FOUND", tr(lang, "cli", "feature.baseNotFound"));
2101
2101
  }
2102
- await fs2.copy(featureBasePath, featureDir);
2102
+ await fs14.copy(featureBasePath, featureDir);
2103
2103
  const idNumber = featureId.replace("F", "");
2104
2104
  const repoName = projectType === "multi" ? `{{projectName}}-${component}` : "{{projectName}}";
2105
2105
  const replacements = {
@@ -2109,7 +2109,6 @@ async function runFeature(name, options) {
2109
2109
  "{\uBC88\uD638}": idNumber,
2110
2110
  "{\uACB0\uC815 \uC81C\uBAA9}": `${name} \uACB0\uC815`,
2111
2111
  "{YYYY-MM-DD}": getLocalDateString(),
2112
- "YYYY-MM-DD": getLocalDateString(),
2113
2112
  "{be|fe}": component || "",
2114
2113
  "{\uC774\uC288\uBC88\uD638}": "",
2115
2114
  "{{description}}": options.desc || "",
@@ -2172,7 +2171,7 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2172
2171
  const initLockPath = getInitLockPath(dir);
2173
2172
  const docsLockPath = getDocsLockPath(dir);
2174
2173
  for (const lockPath of [initLockPath, docsLockPath]) {
2175
- if (await fs2.pathExists(lockPath)) {
2174
+ if (await fs14.pathExists(lockPath)) {
2176
2175
  sawLock = true;
2177
2176
  await waitForLockRelease(lockPath, {
2178
2177
  timeoutMs: Math.max(200, endAt - Date.now()),
@@ -2197,8 +2196,8 @@ async function getNextFeatureId(docsDir, projectType, components) {
2197
2196
  scanDirs.push(featuresDir);
2198
2197
  }
2199
2198
  for (const dir of scanDirs) {
2200
- if (!await fs2.pathExists(dir)) continue;
2201
- const entries = await fs2.readdir(dir, { withFileTypes: true });
2199
+ if (!await fs14.pathExists(dir)) continue;
2200
+ const entries = await fs14.readdir(dir, { withFileTypes: true });
2202
2201
  for (const entry of entries) {
2203
2202
  if (!entry.isDirectory()) continue;
2204
2203
  const match = entry.name.match(/^F(\d+)-/);
@@ -3154,7 +3153,7 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
3154
3153
  );
3155
3154
  const existing = [];
3156
3155
  for (const candidate of normalizedCandidates) {
3157
- if (await fs2.pathExists(path6.join(projectGitCwd, candidate))) {
3156
+ if (await fs14.pathExists(path6.join(projectGitCwd, candidate))) {
3158
3157
  existing.push(candidate);
3159
3158
  }
3160
3159
  }
@@ -3226,22 +3225,22 @@ async function parseFeature(featurePath, type, context, options) {
3226
3225
  const tasksPath = path6.join(featurePath, "tasks.md");
3227
3226
  let specStatus;
3228
3227
  let issueNumber;
3229
- const specExists = await fs2.pathExists(specPath);
3228
+ const specExists = await fs14.pathExists(specPath);
3230
3229
  if (specExists) {
3231
- const content = await fs2.readFile(specPath, "utf-8");
3230
+ const content = await fs14.readFile(specPath, "utf-8");
3232
3231
  const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
3233
3232
  specStatus = parseDocStatus(statusValue);
3234
3233
  const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
3235
3234
  issueNumber = parseIssueNumber(issueValue);
3236
3235
  }
3237
3236
  let planStatus;
3238
- const planExists = await fs2.pathExists(planPath);
3237
+ const planExists = await fs14.pathExists(planPath);
3239
3238
  if (planExists) {
3240
- const content = await fs2.readFile(planPath, "utf-8");
3239
+ const content = await fs14.readFile(planPath, "utf-8");
3241
3240
  const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
3242
3241
  planStatus = parseDocStatus(statusValue);
3243
3242
  }
3244
- const tasksExists = await fs2.pathExists(tasksPath);
3243
+ const tasksExists = await fs14.pathExists(tasksPath);
3245
3244
  const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
3246
3245
  let activeTask;
3247
3246
  let nextTodoTask;
@@ -3255,7 +3254,7 @@ async function parseFeature(featurePath, type, context, options) {
3255
3254
  let prStatusFieldExists = false;
3256
3255
  let prePrReviewFieldExists = false;
3257
3256
  if (tasksExists) {
3258
- const content = await fs2.readFile(tasksPath, "utf-8");
3257
+ const content = await fs14.readFile(tasksPath, "utf-8");
3259
3258
  const { summary, activeTask: active, nextTodoTask: nextTodo } = parseTasks(content);
3260
3259
  tasksSummary.total = summary.total;
3261
3260
  tasksSummary.todo = summary.todo;
@@ -3465,7 +3464,7 @@ async function scanFeatures(config) {
3465
3464
  ignore: ["**/feature-base/**"]
3466
3465
  });
3467
3466
  for (const dir of featureDirs) {
3468
- if ((await fs2.stat(dir)).isDirectory()) {
3467
+ if ((await fs14.stat(dir)).isDirectory()) {
3469
3468
  features.push(
3470
3469
  await parseFeature(
3471
3470
  dir,
@@ -3498,7 +3497,7 @@ async function scanFeatures(config) {
3498
3497
  absolute: true
3499
3498
  });
3500
3499
  for (const dir of componentDirs) {
3501
- if (!(await fs2.stat(dir)).isDirectory()) continue;
3500
+ if (!(await fs14.stat(dir)).isDirectory()) continue;
3502
3501
  features.push(
3503
3502
  await parseFeature(
3504
3503
  dir,
@@ -3664,7 +3663,7 @@ async function runStatus(options) {
3664
3663
  ),
3665
3664
  ""
3666
3665
  ].join("\n");
3667
- await fs2.writeFile(outputPath, content, "utf-8");
3666
+ await fs14.writeFile(outputPath, content, "utf-8");
3668
3667
  console.log(
3669
3668
  chalk6.green(
3670
3669
  tr(lang, "cli", "status.wrote", { path: outputPath })
@@ -3678,8 +3677,8 @@ function escapeRegExp2(value) {
3678
3677
  async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
3679
3678
  try {
3680
3679
  const specPath = path6.join(featureDir, "spec.md");
3681
- if (!await fs2.pathExists(specPath)) return fallbackSlug;
3682
- const content = await fs2.readFile(specPath, "utf-8");
3680
+ if (!await fs14.pathExists(specPath)) return fallbackSlug;
3681
+ const content = await fs14.readFile(specPath, "utf-8");
3683
3682
  const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
3684
3683
  for (const key of keys) {
3685
3684
  const regex = new RegExp(
@@ -3778,7 +3777,7 @@ async function runUpdate(options) {
3778
3777
  const typeReplacements = {
3779
3778
  "{{projectName}}": projectName
3780
3779
  };
3781
- if (await fs2.pathExists(commonAgents)) {
3780
+ if (await fs14.pathExists(commonAgents)) {
3782
3781
  const count = await updateFolder(
3783
3782
  commonAgents,
3784
3783
  targetAgents,
@@ -3796,7 +3795,7 @@ async function runUpdate(options) {
3796
3795
  );
3797
3796
  updatedCount += count;
3798
3797
  }
3799
- if (await fs2.pathExists(typeAgents)) {
3798
+ if (await fs14.pathExists(typeAgents)) {
3800
3799
  const count = await updateFolder(
3801
3800
  typeAgents,
3802
3801
  targetAgents,
@@ -3846,24 +3845,24 @@ async function runUpdate(options) {
3846
3845
  async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG, options = {}) {
3847
3846
  const protectedFiles = options.protectedFiles ?? /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
3848
3847
  const skipDirectories = options.skipDirectories ?? /* @__PURE__ */ new Set();
3849
- await fs2.ensureDir(targetDir);
3850
- const files = await fs2.readdir(sourceDir);
3848
+ await fs14.ensureDir(targetDir);
3849
+ const files = await fs14.readdir(sourceDir);
3851
3850
  let updatedCount = 0;
3852
3851
  for (const file of files) {
3853
3852
  const sourcePath = path6.join(sourceDir, file);
3854
3853
  const targetPath = path6.join(targetDir, file);
3855
- const stat = await fs2.stat(sourcePath);
3854
+ const stat = await fs14.stat(sourcePath);
3856
3855
  if (stat.isFile()) {
3857
3856
  if (protectedFiles.has(file)) {
3858
3857
  continue;
3859
3858
  }
3860
- let sourceContent = await fs2.readFile(sourcePath, "utf-8");
3859
+ let sourceContent = await fs14.readFile(sourcePath, "utf-8");
3861
3860
  if (replacements) {
3862
3861
  sourceContent = applyReplacements(sourceContent, replacements);
3863
3862
  }
3864
3863
  let shouldUpdate = true;
3865
- if (await fs2.pathExists(targetPath)) {
3866
- const targetContent = await fs2.readFile(targetPath, "utf-8");
3864
+ if (await fs14.pathExists(targetPath)) {
3865
+ const targetContent = await fs14.readFile(targetPath, "utf-8");
3867
3866
  if (sourceContent === targetContent) {
3868
3867
  continue;
3869
3868
  }
@@ -3877,7 +3876,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
3877
3876
  }
3878
3877
  }
3879
3878
  if (shouldUpdate) {
3880
- await fs2.writeFile(targetPath, sourceContent);
3879
+ await fs14.writeFile(targetPath, sourceContent);
3881
3880
  console.log(
3882
3881
  chalk6.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
3883
3882
  );
@@ -4022,7 +4021,7 @@ async function runConfig(options) {
4022
4021
  )
4023
4022
  );
4024
4023
  console.log();
4025
- const configFile = await fs2.readJson(configPath);
4024
+ const configFile = await fs14.readJson(configPath);
4026
4025
  console.log(JSON.stringify(configFile, null, 2));
4027
4026
  console.log();
4028
4027
  return;
@@ -4030,7 +4029,7 @@ async function runConfig(options) {
4030
4029
  await withFileLock(
4031
4030
  getDocsLockPath(config.docsDir),
4032
4031
  async () => {
4033
- const configFile = await fs2.readJson(configPath);
4032
+ const configFile = await fs14.readJson(configPath);
4034
4033
  if (configFile.docsRepo !== "standalone") {
4035
4034
  console.log(
4036
4035
  chalk6.yellow(tr(config.lang, "cli", "config.projectRootStandaloneOnly"))
@@ -4109,7 +4108,7 @@ async function runConfig(options) {
4109
4108
  )
4110
4109
  );
4111
4110
  }
4112
- await fs2.writeJson(configPath, configFile, { spaces: 2 });
4111
+ await fs14.writeJson(configPath, configFile, { spaces: 2 });
4113
4112
  console.log();
4114
4113
  },
4115
4114
  { owner: "config" }
@@ -5162,8 +5161,8 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
5162
5161
  ];
5163
5162
  for (const file of files) {
5164
5163
  const fullPath = path6.join(f.path, file);
5165
- if (!await fs2.pathExists(fullPath)) continue;
5166
- const original = await fs2.readFile(fullPath, "utf-8");
5164
+ if (!await fs14.pathExists(fullPath)) continue;
5165
+ const original = await fs14.readFile(fullPath, "utf-8");
5167
5166
  let next = original;
5168
5167
  const changes = [];
5169
5168
  const placeholderFix = applyPlaceholderFixes(next, placeholderContext, config.lang);
@@ -5187,7 +5186,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
5187
5186
  }
5188
5187
  if (next === original) continue;
5189
5188
  if (!dryRun) {
5190
- await fs2.writeFile(fullPath, next, "utf-8");
5189
+ await fs14.writeFile(fullPath, next, "utf-8");
5191
5190
  }
5192
5191
  entries.push({
5193
5192
  path: formatPath(cwd, fullPath),
@@ -5207,7 +5206,7 @@ async function checkDocsStructure(config, cwd) {
5207
5206
  const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
5208
5207
  for (const dir of requiredDirs) {
5209
5208
  const p = path6.join(config.docsDir, dir);
5210
- if (!await fs2.pathExists(p)) {
5209
+ if (!await fs14.pathExists(p)) {
5211
5210
  issues.push({
5212
5211
  level: "error",
5213
5212
  code: "missing_dir",
@@ -5217,7 +5216,7 @@ async function checkDocsStructure(config, cwd) {
5217
5216
  }
5218
5217
  }
5219
5218
  const configPath = path6.join(config.docsDir, ".lee-spec-kit.json");
5220
- if (!await fs2.pathExists(configPath)) {
5219
+ if (!await fs14.pathExists(configPath)) {
5221
5220
  issues.push({
5222
5221
  level: "warn",
5223
5222
  code: "missing_config",
@@ -5248,8 +5247,8 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5248
5247
  const featureDocs = ["spec.md", "plan.md", "tasks.md"];
5249
5248
  for (const file of featureDocs) {
5250
5249
  const p = path6.join(f.path, file);
5251
- if (!await fs2.pathExists(p)) continue;
5252
- const content = await fs2.readFile(p, "utf-8");
5250
+ if (!await fs14.pathExists(p)) continue;
5251
+ const content = await fs14.readFile(p, "utf-8");
5253
5252
  const placeholders = detectPlaceholders(content);
5254
5253
  if (placeholders.length === 0) continue;
5255
5254
  issues.push({
@@ -5263,8 +5262,8 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5263
5262
  }
5264
5263
  if (decisionsPlaceholderMode !== "off") {
5265
5264
  const decisionsPath = path6.join(f.path, "decisions.md");
5266
- if (await fs2.pathExists(decisionsPath)) {
5267
- const content = await fs2.readFile(decisionsPath, "utf-8");
5265
+ if (await fs14.pathExists(decisionsPath)) {
5266
+ const content = await fs14.readFile(decisionsPath, "utf-8");
5268
5267
  const placeholders = detectPlaceholders(content);
5269
5268
  if (placeholders.length > 0) {
5270
5269
  issues.push({
@@ -5929,26 +5928,243 @@ async function runFlow(featureName, options) {
5929
5928
  console.log(chalk6.gray("Tip: add --approve <LABEL> [--execute] to run the selected atomic action."));
5930
5929
  console.log();
5931
5930
  }
5932
- function resolveComponentOption4(options) {
5931
+ var GITHUB_TEXT = {
5932
+ ko: {
5933
+ cmdGithubDescription: "GitHub \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uB3C4\uC6B0\uBBF8 (issue/pr \uCD08\uC548, \uAC80\uC99D, merge \uC7AC\uC2DC\uB3C4)",
5934
+ cmdIssueDescription: "feature \uBB38\uC11C \uAE30\uBC18 GitHub issue \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131",
5935
+ cmdPrDescription: "GitHub PR \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131 + tasks \uB3D9\uAE30\uD654 + merge \uC7AC\uC2DC\uB3C4",
5936
+ optJson: "\uC5D0\uC774\uC804\uD2B8\uC6A9 JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825",
5937
+ optRepo: "\uBA40\uD2F0 \uD504\uB85C\uC81D\uD2B8 \uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984",
5938
+ optComponent: "\uBA40\uD2F0 \uD504\uB85C\uC81D\uD2B8 \uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984",
5939
+ optIssueTitle: "Issue \uC81C\uBAA9",
5940
+ optLabels: "\uC27C\uD45C \uAD6C\uBD84 \uB77C\uBCA8 \uBAA9\uB85D (\uAE30\uBCF8: enhancement)",
5941
+ optIssueBodyFile: "Issue \uBCF8\uBB38 \uD30C\uC77C \uCD9C\uB825 \uACBD\uB85C",
5942
+ optIssueAssignee: "Issue \uB2F4\uB2F9\uC790 (\uAE30\uBCF8: @me)",
5943
+ optIssueCreate: "gh CLI\uB85C issue \uC0DD\uC131",
5944
+ optIssueConfirm: "\uC6D0\uACA9 \uC791\uC5C5(--create)\uC6A9 \uBA85\uC2DC\uC801 \uC2B9\uC778 \uD1A0\uD070. \uC0AC\uC6A9\uAC12: OK",
5945
+ optPrTitle: "PR \uC81C\uBAA9",
5946
+ optPrBodyFile: "PR \uBCF8\uBB38 \uD30C\uC77C \uCD9C\uB825 \uACBD\uB85C",
5947
+ optPrAssignee: "PR \uB2F4\uB2F9\uC790 (\uAE30\uBCF8: @me)",
5948
+ optPrBase: "PR base \uBE0C\uB79C\uCE58 (\uAE30\uBCF8: main)",
5949
+ optPrCreate: "gh CLI\uB85C PR \uC0DD\uC131",
5950
+ optPrRef: "--merge \uC2DC \uC0AC\uC6A9\uD560 \uAE30\uC874 PR URL/\uBC88\uD638",
5951
+ optPrMerge: "\uC7AC\uC2DC\uB3C4/\uD5E4\uB4DC \uAC31\uC2E0\uACFC \uD568\uAED8 PR merge \uC218\uD589",
5952
+ optPrConfirm: "\uC6D0\uACA9 \uC791\uC5C5(--create/--merge)\uC6A9 \uBA85\uC2DC\uC801 \uC2B9\uC778 \uD1A0\uD070. \uC0AC\uC6A9\uAC12: OK",
5953
+ optPrRetry: "merge \uC7AC\uC2DC\uB3C4 \uD69F\uC218 (\uAE30\uBCF8: 3)",
5954
+ optPrNoSyncTasks: "tasks.md PR URL/PR \uC0C1\uD0DC \uB3D9\uAE30\uD654\uB97C \uAC74\uB108\uB700",
5955
+ optPrCommitSync: "tasks.md \uB3D9\uAE30\uD654 \uBCC0\uACBD\uC744 \uC790\uB3D9 commit/push",
5956
+ invalidRepoComponentMismatch: "`--repo`\uC640 `--component`\uB97C \uD568\uAED8 \uC4F8 \uB54C\uB294 \uAC19\uC740 \uAC12\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.",
5957
+ labelsRequired: "\uCD5C\uC18C 1\uAC1C \uB77C\uBCA8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. `--labels enhancement`\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
5958
+ approvalRequired: "{operation}\uC740(\uB294) \uC0AC\uC6A9\uC790 \uBA85\uC2DC \uC2B9\uC778 \uD6C4\uC5D0\uB9CC \uC2E4\uD589\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uACC4\uD68D \uACF5\uC720 \uD6C4 `--confirm OK`\uB85C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
5959
+ ghCommandFailed: "GitHub CLI \uBA85\uB839 \uC2E4\uD589\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5960
+ ghEmptyJson: "GitHub CLI JSON \uCD9C\uB825\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
5961
+ ghInvalidJson: "GitHub CLI JSON \uD30C\uC2F1\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: {snippet}",
5962
+ sectionsMissing: "{kind} \uBCF8\uBB38\uC5D0 \uD544\uC218 \uC139\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: {sections}",
5963
+ docsMissing: "\uAD00\uB828 \uBB38\uC11C \uACBD\uB85C\uAC00 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {paths}",
5964
+ noFeatures: "Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
5965
+ multipleFeaturesMatched: "\uC5EC\uB7EC Feature\uAC00 \uB9E4\uCE6D\uB418\uC5C8\uC2B5\uB2C8\uB2E4. feature \uC774\uB984(slug | F001 | F001-slug)\uC744 \uBA85\uC2DC\uD558\uC138\uC694.",
5966
+ featureSelectFailed: "Feature \uC790\uB3D9 \uC120\uD0DD\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. feature \uC774\uB984\uC744 \uBA85\uC2DC\uD574\uC11C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
5967
+ tasksNotFound: "tasks.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
5968
+ detectBranchFailed: "\uD604\uC7AC git \uBE0C\uB79C\uCE58 \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5969
+ inspectWorktreeFailed: "git \uC6CC\uD06C\uD2B8\uB9AC \uC0C1\uD0DC \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5970
+ worktreeNotClean: "git \uC6CC\uD06C\uD2B8\uB9AC\uAC00 \uAE68\uB057\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. merge \uC7AC\uC2DC\uB3C4 \uB3D9\uAE30\uD654 \uC804\uC5D0 \uCEE4\uBC0B/\uC2A4\uD0DC\uC2DC\uD558\uC138\uC694.",
5971
+ inspectFileStatusFailed: "\uD30C\uC77C git \uC0C1\uD0DC \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5972
+ stageFileFailed: "\uB3D9\uAE30\uD654 \uD30C\uC77C stage\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5973
+ commitSyncFailed: "\uB3D9\uAE30\uD654 \uBA54\uD0C0\uB370\uC774\uD130 commit\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5974
+ pushSyncFailed: "\uB3D9\uAE30\uD654 \uBA54\uD0C0\uB370\uC774\uD130 push\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5975
+ fetchPrBranchesFailed: "PR \uBE0C\uB79C\uCE58 fetch\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5976
+ checkoutHeadFailed: "PR \uD5E4\uB4DC \uBE0C\uB79C\uCE58 checkout\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5977
+ createLocalHeadFailed: "\uB85C\uCEEC PR \uD5E4\uB4DC \uBE0C\uB79C\uCE58 \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5978
+ rebaseHeadFailed: "PR \uD5E4\uB4DC \uBE0C\uB79C\uCE58 rebase\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5979
+ pushRebasedHeadFailed: "rebase\uB41C PR \uD5E4\uB4DC \uBE0C\uB79C\uCE58 push\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5980
+ restoreBranchFailed: "PR \uD5E4\uB4DC \uAC31\uC2E0 \uD6C4 \uC774\uC804 \uBE0C\uB79C\uCE58 \uBCF5\uC6D0\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5981
+ mergeRetryFailed: "\uC7AC\uC2DC\uB3C4 \uD6C4\uC5D0\uB3C4 PR merge\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.{lastError}",
5982
+ retryInvalid: "`--retry`\uB294 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
5983
+ operationIssueCreate: "GitHub issue \uC0DD\uC131",
5984
+ operationPrCreate: "GitHub PR \uC0DD\uC131",
5985
+ operationPrMerge: "GitHub PR merge",
5986
+ createIssueFailed: "GitHub issue \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5987
+ createPrFailed: "GitHub PR \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5988
+ mergeRequiresPr: "`--merge`\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 `--create` \uB610\uB294 `--pr <url|number>`\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5989
+ checkoutBaseAfterMergeFailed: "merge \uD6C4 {base} \uBE0C\uB79C\uCE58 checkout\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5990
+ pullBaseAfterMergeFailed: "merge \uD6C4 {base} \uBE0C\uB79C\uCE58 \uCD5C\uC2E0\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
5991
+ issueDefaultTitle: "{slug} ({folder} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8)",
5992
+ prDefaultTitleWithIssue: "feat(#{issue}): {slug} (\uAD6C\uD604 \uC5C5\uB370\uC774\uD2B8)",
5993
+ prDefaultTitleNoIssue: "feat: {slug} (\uAD6C\uD604 \uC5C5\uB370\uC774\uD2B8)",
5994
+ issueHeader: "\u{1F9FE} GitHub Issue \uB3C4\uC6B0\uBBF8",
5995
+ prHeader: "\u{1F500} GitHub PR \uB3C4\uC6B0\uBBF8",
5996
+ labelFeature: "Feature",
5997
+ labelBodyFile: "\uBCF8\uBB38 \uD30C\uC77C",
5998
+ labelLabels: "\uB77C\uBCA8",
5999
+ labelPr: "PR",
6000
+ issueCreated: "\u2705 \uC0DD\uC131 \uC644\uB8CC: {url}",
6001
+ issueTemplateGenerated: "\uCD08\uC548\uC744 \uC0DD\uC131\uD588\uC2B5\uB2C8\uB2E4. \uC790\uB3D9 \uC0DD\uC131\uD558\uB824\uBA74 `--create`\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
6002
+ prTasksSynced: "\u2705 tasks.md PR \uBA54\uD0C0\uB370\uC774\uD130\uB97C \uB3D9\uAE30\uD654\uD588\uC2B5\uB2C8\uB2E4.",
6003
+ prMerged: "\u2705 PR merge \uC644\uB8CC (\uC2DC\uB3C4 \uD69F\uC218: {attempts})",
6004
+ prTemplateGenerated: "\uCD08\uC548\uC744 \uC0DD\uC131\uD588\uC2B5\uB2C8\uB2E4. \uC790\uB3D9 \uC0DD\uC131\uD558\uB824\uBA74 `--create`\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
6005
+ syncCommitWithIssue: "docs(#{issue}): {folder} PR \uBA54\uD0C0\uB370\uC774\uD130 \uB3D9\uAE30\uD654",
6006
+ syncCommitNoIssue: "docs: {folder} PR \uBA54\uD0C0\uB370\uC774\uD130 \uB3D9\uAE30\uD654",
6007
+ kindIssue: "Issue",
6008
+ kindPr: "PR"
6009
+ },
6010
+ en: {
6011
+ cmdGithubDescription: "GitHub workflow helpers (issue/pr templates, validation, merge retry)",
6012
+ cmdIssueDescription: "Generate/create GitHub issue body from feature docs with validation",
6013
+ cmdPrDescription: "Generate/create GitHub PR body with validation, tasks PR sync, and merge retry",
6014
+ optJson: "Output in JSON format for agents",
6015
+ optRepo: "Component name for multi projects",
6016
+ optComponent: "Component name for multi projects",
6017
+ optIssueTitle: "Issue title",
6018
+ optLabels: "Comma-separated labels (default: enhancement)",
6019
+ optIssueBodyFile: "Issue body file output path",
6020
+ optIssueAssignee: "Issue assignee (default: @me)",
6021
+ optIssueCreate: "Create issue via gh CLI",
6022
+ optIssueConfirm: "Explicit user approval token for remote operations (--create). Use: OK",
6023
+ optPrTitle: "PR title",
6024
+ optPrBodyFile: "PR body file output path",
6025
+ optPrAssignee: "PR assignee (default: @me)",
6026
+ optPrBase: "PR base branch (default: main)",
6027
+ optPrCreate: "Create PR via gh CLI",
6028
+ optPrRef: "Existing PR URL/number (used by --merge)",
6029
+ optPrMerge: "Merge PR with retry and head-branch refresh",
6030
+ optPrConfirm: "Explicit user approval token for remote operations (--create/--merge). Use: OK",
6031
+ optPrRetry: "Retry count for merge (default: 3)",
6032
+ optPrNoSyncTasks: "Do not sync PR URL/PR status into tasks.md",
6033
+ optPrCommitSync: "Commit and push tasks.md metadata sync automatically",
6034
+ invalidRepoComponentMismatch: "`--repo` and `--component` must reference the same value when both are provided.",
6035
+ labelsRequired: "At least one label is required. Use `--labels enhancement`.",
6036
+ approvalRequired: "{operation} requires explicit user approval. Re-run with `--confirm OK` after sharing the plan with the user.",
6037
+ ghCommandFailed: "GitHub CLI command failed",
6038
+ ghEmptyJson: "GitHub CLI returned empty JSON output.",
6039
+ ghInvalidJson: "GitHub CLI returned invalid JSON: {snippet}",
6040
+ sectionsMissing: "{kind} body is missing required sections: {sections}",
6041
+ docsMissing: "Related document paths do not exist: {paths}",
6042
+ noFeatures: "No features found.",
6043
+ multipleFeaturesMatched: "Multiple features matched. Specify feature name (slug | F001 | F001-slug).",
6044
+ featureSelectFailed: "Failed to auto-select a feature. Specify feature name explicitly.",
6045
+ tasksNotFound: "tasks.md not found: {path}",
6046
+ detectBranchFailed: "Failed to detect current git branch",
6047
+ inspectWorktreeFailed: "Failed to inspect git worktree",
6048
+ worktreeNotClean: "Git worktree is not clean. Commit or stash changes before merge retry sync.",
6049
+ inspectFileStatusFailed: "Failed to inspect git file status",
6050
+ stageFileFailed: "Failed to stage file",
6051
+ commitSyncFailed: "Failed to commit synced metadata",
6052
+ pushSyncFailed: "Failed to push synced metadata commit",
6053
+ fetchPrBranchesFailed: "Failed to fetch PR branches",
6054
+ checkoutHeadFailed: "Failed to checkout PR head branch",
6055
+ createLocalHeadFailed: "Failed to create local PR head branch",
6056
+ rebaseHeadFailed: "Failed to rebase PR head branch",
6057
+ pushRebasedHeadFailed: "Failed to push rebased PR head branch",
6058
+ restoreBranchFailed: "Failed to restore previous branch after PR refresh",
6059
+ mergeRetryFailed: "Failed to merge PR after retry attempts.{lastError}",
6060
+ retryInvalid: "`--retry` must be a positive integer.",
6061
+ operationIssueCreate: "GitHub issue creation",
6062
+ operationPrCreate: "GitHub PR creation",
6063
+ operationPrMerge: "GitHub PR merge",
6064
+ createIssueFailed: "Failed to create GitHub issue",
6065
+ createPrFailed: "Failed to create GitHub PR",
6066
+ mergeRequiresPr: "`--merge` requires `--create` or `--pr <url|number>`.",
6067
+ checkoutBaseAfterMergeFailed: "Failed to checkout {base} after merge",
6068
+ pullBaseAfterMergeFailed: "Failed to update {base} after merge",
6069
+ issueDefaultTitle: "{slug} ({folder} documentation update)",
6070
+ prDefaultTitleWithIssue: "feat(#{issue}): {slug} (implementation update)",
6071
+ prDefaultTitleNoIssue: "feat: {slug} (implementation update)",
6072
+ issueHeader: "\u{1F9FE} GitHub Issue Helper",
6073
+ prHeader: "\u{1F500} GitHub PR Helper",
6074
+ labelFeature: "Feature",
6075
+ labelBodyFile: "Body file",
6076
+ labelLabels: "Labels",
6077
+ labelPr: "PR",
6078
+ issueCreated: "\u2705 Created: {url}",
6079
+ issueTemplateGenerated: "Template generated. Add --create to open the issue automatically.",
6080
+ prTasksSynced: "\u2705 tasks.md PR metadata synced.",
6081
+ prMerged: "\u2705 PR merged (attempts: {attempts}).",
6082
+ prTemplateGenerated: "Template generated. Add --create to open the PR automatically.",
6083
+ syncCommitWithIssue: "docs(#{issue}): sync PR metadata for {folder}",
6084
+ syncCommitNoIssue: "docs: sync PR metadata for {folder}",
6085
+ kindIssue: "Issue",
6086
+ kindPr: "PR"
6087
+ }
6088
+ };
6089
+ function tg(lang, key, vars = {}) {
6090
+ const template = GITHUB_TEXT[lang]?.[key] ?? GITHUB_TEXT.en[key];
6091
+ return formatTemplate(template, vars);
6092
+ }
6093
+ function detectGithubCliLangSync(cwd) {
6094
+ const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
6095
+ const startDirs = [explicitDocsDir ? path6.resolve(explicitDocsDir) : "", path6.resolve(cwd)].filter(Boolean);
6096
+ const scanOrder = [];
6097
+ const seen = /* @__PURE__ */ new Set();
6098
+ for (const start of startDirs) {
6099
+ let current = start;
6100
+ while (true) {
6101
+ const abs = path6.resolve(current);
6102
+ if (!seen.has(abs)) {
6103
+ scanOrder.push(abs);
6104
+ seen.add(abs);
6105
+ }
6106
+ const parent = path6.dirname(abs);
6107
+ if (parent === abs) break;
6108
+ current = parent;
6109
+ }
6110
+ }
6111
+ for (const base of scanOrder) {
6112
+ for (const docsDir of [path6.join(base, "docs"), base]) {
6113
+ const configPath = path6.join(docsDir, ".lee-spec-kit.json");
6114
+ if (fs14.existsSync(configPath)) {
6115
+ try {
6116
+ const parsed = fs14.readJsonSync(configPath);
6117
+ if (parsed?.lang === "ko" || parsed?.lang === "en") return parsed.lang;
6118
+ } catch {
6119
+ }
6120
+ }
6121
+ const agentsPath = path6.join(docsDir, "agents");
6122
+ const featuresPath = path6.join(docsDir, "features");
6123
+ if (!fs14.existsSync(agentsPath) || !fs14.existsSync(featuresPath)) continue;
6124
+ for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
6125
+ const file = path6.join(agentsPath, probe);
6126
+ if (!fs14.existsSync(file)) continue;
6127
+ try {
6128
+ const content = fs14.readFileSync(file, "utf-8");
6129
+ if (/[가-힣]/.test(content)) return "ko";
6130
+ } catch {
6131
+ }
6132
+ }
6133
+ return "en";
6134
+ }
6135
+ }
6136
+ return DEFAULT_LANG;
6137
+ }
6138
+ function resolveComponentOption4(options, lang) {
5933
6139
  if (options.repo && options.component && options.repo.trim().toLowerCase() !== options.component.trim().toLowerCase()) {
5934
6140
  throw createCliError(
5935
6141
  "INVALID_ARGUMENT",
5936
- "`--repo` and `--component` must reference the same value when both are provided."
6142
+ tg(lang, "invalidRepoComponentMismatch")
5937
6143
  );
5938
6144
  }
5939
6145
  const component = (options.component || options.repo || "").trim().toLowerCase();
5940
6146
  return component || void 0;
5941
6147
  }
5942
- function parseLabels(raw) {
6148
+ function parseLabels(raw, lang) {
5943
6149
  const labels = (raw || "enhancement").split(",").map((part) => part.trim()).filter(Boolean);
5944
6150
  if (labels.length === 0) {
5945
6151
  throw createCliError(
5946
6152
  "INVALID_ARGUMENT",
5947
- "At least one label is required. Use `--labels enhancement`."
6153
+ tg(lang, "labelsRequired")
5948
6154
  );
5949
6155
  }
5950
6156
  return [...new Set(labels)];
5951
6157
  }
6158
+ function hasExplicitRemoteApproval(raw) {
6159
+ return (raw || "").trim().toUpperCase() === "OK";
6160
+ }
6161
+ function assertRemoteApproval(raw, operation, lang) {
6162
+ if (hasExplicitRemoteApproval(raw)) return;
6163
+ throw createCliError(
6164
+ "APPROVAL_REQUIRED",
6165
+ tg(lang, "approvalRequired", { operation })
6166
+ );
6167
+ }
5952
6168
  function runProcess(bin, args, cwd) {
5953
6169
  const result = spawnSync(bin, args, {
5954
6170
  cwd,
@@ -5976,22 +6192,24 @@ function runProcessOrThrow(bin, args, cwd, failureMessage) {
5976
6192
  }
5977
6193
  return result;
5978
6194
  }
5979
- function runGhJson(args, cwd) {
5980
- const result = runProcessOrThrow("gh", args, cwd, "GitHub CLI command failed");
6195
+ function runGhJson(args, cwd, lang) {
6196
+ const result = runProcessOrThrow("gh", args, cwd, tg(lang, "ghCommandFailed"));
5981
6197
  const text = result.stdout.trim();
5982
6198
  if (!text) {
5983
- throw createCliError("EXECUTION_FAILED", "GitHub CLI returned empty JSON output.");
6199
+ throw createCliError("EXECUTION_FAILED", tg(lang, "ghEmptyJson"));
5984
6200
  }
5985
6201
  try {
5986
6202
  return JSON.parse(text);
5987
6203
  } catch {
5988
6204
  throw createCliError(
5989
6205
  "EXECUTION_FAILED",
5990
- `GitHub CLI returned invalid JSON: ${text.slice(0, 160)}`
6206
+ tg(lang, "ghInvalidJson", {
6207
+ snippet: text.slice(0, 160)
6208
+ })
5991
6209
  );
5992
6210
  }
5993
6211
  }
5994
- function ensureSections(body, sections, kind) {
6212
+ function ensureSections(body, sections, kind, lang) {
5995
6213
  const missing = sections.filter((section) => {
5996
6214
  const re = new RegExp(`^##\\s+${section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
5997
6215
  return !re.test(body);
@@ -5999,18 +6217,21 @@ function ensureSections(body, sections, kind) {
5999
6217
  if (missing.length > 0) {
6000
6218
  throw createCliError(
6001
6219
  "PRECONDITION_FAILED",
6002
- `${kind} body is missing required sections: ${missing.join(", ")}`
6220
+ tg(lang, "sectionsMissing", {
6221
+ kind,
6222
+ sections: missing.join(", ")
6223
+ })
6003
6224
  );
6004
6225
  }
6005
6226
  }
6006
- function ensureDocsExist(docsDir, relativePaths) {
6227
+ function ensureDocsExist(docsDir, relativePaths, lang) {
6007
6228
  const missing = relativePaths.filter(
6008
- (relativePath) => !fs2.existsSync(path6.join(docsDir, relativePath))
6229
+ (relativePath) => !fs14.existsSync(path6.join(docsDir, relativePath))
6009
6230
  );
6010
6231
  if (missing.length > 0) {
6011
6232
  throw createCliError(
6012
6233
  "PRECONDITION_FAILED",
6013
- `Related document paths do not exist: ${missing.join(", ")}`
6234
+ tg(lang, "docsMissing", { paths: missing.join(", ") })
6014
6235
  );
6015
6236
  }
6016
6237
  }
@@ -6018,28 +6239,28 @@ function toBodyFilePath(raw, fallbackName) {
6018
6239
  const selected = raw?.trim() || path6.join(os.tmpdir(), fallbackName);
6019
6240
  return path6.resolve(selected);
6020
6241
  }
6021
- async function resolveFeatureOrThrow(featureName, options) {
6242
+ async function resolveFeatureOrThrow(featureName, options, lang) {
6022
6243
  const config = await getConfig(process.cwd());
6023
6244
  if (!config) {
6024
6245
  throw createCliError(
6025
6246
  "CONFIG_NOT_FOUND",
6026
- tr(DEFAULT_LANG, "cli", "common.configNotFound")
6247
+ tr(lang, "cli", "common.configNotFound")
6027
6248
  );
6028
6249
  }
6029
6250
  const state = await resolveContextSelection(config, featureName, options);
6030
6251
  if (!state.matchedFeature) {
6031
6252
  if (state.status === "no_features") {
6032
- throw createCliError("PRECONDITION_FAILED", "No features found.");
6253
+ throw createCliError("PRECONDITION_FAILED", tg(lang, "noFeatures"));
6033
6254
  }
6034
6255
  if (state.status === "multiple_active") {
6035
6256
  throw createCliError(
6036
6257
  "CONTEXT_SELECTION_REQUIRED",
6037
- "Multiple features matched. Specify feature name (slug | F001 | F001-slug)."
6258
+ tg(lang, "multipleFeaturesMatched")
6038
6259
  );
6039
6260
  }
6040
6261
  throw createCliError(
6041
6262
  "CONTEXT_SELECTION_REQUIRED",
6042
- "Failed to auto-select a feature. Specify feature name explicitly."
6263
+ tg(lang, "featureSelectFailed")
6043
6264
  );
6044
6265
  }
6045
6266
  return { config, feature: state.matchedFeature };
@@ -6053,7 +6274,34 @@ function getFeatureDocPaths(feature) {
6053
6274
  tasksPath: `${featurePathFromDocs}/tasks.md`
6054
6275
  };
6055
6276
  }
6056
- function buildIssueBody(feature, labels, paths) {
6277
+ function buildIssueBody(feature, labels, paths, lang) {
6278
+ if (lang === "ko") {
6279
+ return `## \uAC1C\uC694
6280
+
6281
+ \`${feature.folderName}\` \uAE30\uB2A5\uC744 \uAD6C\uD604\uD569\uB2C8\uB2E4.
6282
+
6283
+ ## \uBAA9\uD45C
6284
+
6285
+ - \uAE30\uB2A5 \uBC94\uC704\uC640 \uAD6C\uD604 \uACB0\uACFC\uB97C \uBA85\uD655\uD788 \uC815\uB9AC\uD569\uB2C8\uB2E4
6286
+ - spec/plan/tasks\uB97C \uACB0\uACFC\uC640 \uB3D9\uAE30\uD654\uD569\uB2C8\uB2E4
6287
+
6288
+ ## \uC644\uB8CC \uAE30\uC900
6289
+
6290
+ - [ ] \uBC94\uC704\uC640 \uC811\uADFC \uBC29\uC2DD\uC774 \uBB38\uC11C\uD654\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4
6291
+ - [ ] \uD0DC\uC2A4\uD06C\uAC00 \uC644\uB8CC\uB418\uACE0 \uAC80\uC99D \uAC00\uB2A5\uD569\uB2C8\uB2E4
6292
+ - [ ] \uAD00\uB828 \uBB38\uC11C\uAC00 \uB3D9\uAE30\uD654\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4
6293
+
6294
+ ## \uAD00\uB828 \uBB38\uC11C
6295
+
6296
+ - **Spec**: \`${paths.specPath}\`
6297
+ - **Plan**: \`${paths.planPath}\`
6298
+ - **Tasks**: \`${paths.tasksPath}\`
6299
+
6300
+ ## \uB77C\uBCA8
6301
+
6302
+ ${labels.map((label) => `- \`${label}\``).join("\n")}
6303
+ `;
6304
+ }
6057
6305
  return `## Overview
6058
6306
 
6059
6307
  Implement feature \`${feature.folderName}\`.
@@ -6080,10 +6328,32 @@ Implement feature \`${feature.folderName}\`.
6080
6328
  ${labels.map((label) => `- \`${label}\``).join("\n")}
6081
6329
  `;
6082
6330
  }
6083
- function buildPrBody(feature, paths) {
6331
+ function buildPrBody(feature, paths, lang) {
6084
6332
  const closes = feature.issueNumber ? `
6085
6333
  Closes #${feature.issueNumber}
6086
6334
  ` : "\n";
6335
+ if (lang === "ko") {
6336
+ return `## \uAC1C\uC694
6337
+
6338
+ \`${feature.folderName}\` \uAE30\uB2A5 \uAD6C\uD604\uACFC \uBB38\uC11C \uB3D9\uAE30\uD654 \uB0B4\uC6A9\uC744 \uC815\uB9AC\uD569\uB2C8\uB2E4.
6339
+
6340
+ ## \uBCC0\uACBD \uC0AC\uD56D
6341
+
6342
+ - \uAE30\uB2A5 \uBC94\uC704 \uAD6C\uD604\uC744 \uC644\uB8CC\uD588\uC2B5\uB2C8\uB2E4
6343
+ - \uAD6C\uD604 \uACB0\uACFC\uC5D0 \uB9DE\uCDB0 \uBB38\uC11C\uB97C \uB3D9\uAE30\uD654\uD588\uC2B5\uB2C8\uB2E4
6344
+ - tasks.md\uC758 PR \uBA54\uD0C0\uB370\uC774\uD130\uB97C \uB3D9\uAE30\uD654\uD588\uC2B5\uB2C8\uB2E4
6345
+
6346
+ ## \uD14C\uC2A4\uD2B8
6347
+
6348
+ ### \uC2E4\uD589\uD55C \uD14C\uC2A4\uD2B8
6349
+
6350
+ - [x] \`<\uD14C\uC2A4\uD2B8 \uBA85\uB839\uC5B4>\` \u2014 PASS
6351
+
6352
+ ## \uAD00\uB828 \uBB38\uC11C
6353
+
6354
+ - **Spec**: \`${paths.specPath}\`
6355
+ - **Tasks**: \`${paths.tasksPath}\`${closes}`;
6356
+ }
6087
6357
  return `## Overview
6088
6358
 
6089
6359
  Implement and document feature \`${feature.folderName}\`.
@@ -6105,6 +6375,12 @@ Implement and document feature \`${feature.folderName}\`.
6105
6375
  - **Spec**: \`${paths.specPath}\`
6106
6376
  - **Tasks**: \`${paths.tasksPath}\`${closes}`;
6107
6377
  }
6378
+ function getRequiredIssueSections(lang) {
6379
+ return lang === "ko" ? ["\uAC1C\uC694", "\uBAA9\uD45C", "\uC644\uB8CC \uAE30\uC900", "\uAD00\uB828 \uBB38\uC11C", "\uB77C\uBCA8"] : ["Overview", "Goals", "Completion Criteria", "Related Documents", "Labels"];
6380
+ }
6381
+ function getRequiredPrSections(lang) {
6382
+ return lang === "ko" ? ["\uAC1C\uC694", "\uBCC0\uACBD \uC0AC\uD56D", "\uD14C\uC2A4\uD2B8", "\uAD00\uB828 \uBB38\uC11C"] : ["Overview", "Changes", "Tests", "Related Documents"];
6383
+ }
6108
6384
  function replaceListField(content, keys, value) {
6109
6385
  for (const key of keys) {
6110
6386
  const re = new RegExp(
@@ -6133,11 +6409,11 @@ function insertFieldInGithubIssueSection(content, key, value) {
6133
6409
  lines.splice(end, 0, `- **${key}**: ${value}`);
6134
6410
  return { content: lines.join("\n"), changed: true };
6135
6411
  }
6136
- function syncTasksPrMetadata(tasksPath, prUrl, nextStatus) {
6137
- if (!fs2.existsSync(tasksPath)) {
6138
- throw createCliError("DOCS_NOT_FOUND", `tasks.md not found: ${tasksPath}`);
6412
+ function syncTasksPrMetadata(tasksPath, prUrl, nextStatus, lang) {
6413
+ if (!fs14.existsSync(tasksPath)) {
6414
+ throw createCliError("DOCS_NOT_FOUND", tg(lang, "tasksNotFound", { path: tasksPath }));
6139
6415
  }
6140
- const original = fs2.readFileSync(tasksPath, "utf-8");
6416
+ const original = fs14.readFileSync(tasksPath, "utf-8");
6141
6417
  let next = original;
6142
6418
  let changed = false;
6143
6419
  const prReplaced = replaceListField(next, ["PR", "Pull Request"], prUrl);
@@ -6161,50 +6437,50 @@ function syncTasksPrMetadata(tasksPath, prUrl, nextStatus) {
6161
6437
  changed = changed || inserted.changed;
6162
6438
  }
6163
6439
  if (changed) {
6164
- fs2.writeFileSync(tasksPath, next, "utf-8");
6440
+ fs14.writeFileSync(tasksPath, next, "utf-8");
6165
6441
  }
6166
6442
  return { changed, path: tasksPath };
6167
6443
  }
6168
- function gitCurrentBranch(cwd) {
6444
+ function gitCurrentBranch(cwd, lang) {
6169
6445
  const result = runProcessOrThrow(
6170
6446
  "git",
6171
6447
  ["rev-parse", "--abbrev-ref", "HEAD"],
6172
6448
  cwd,
6173
- "Failed to detect current git branch"
6449
+ tg(lang, "detectBranchFailed")
6174
6450
  );
6175
6451
  return result.stdout.trim();
6176
6452
  }
6177
- function ensureCleanWorktree(cwd) {
6453
+ function ensureCleanWorktree(cwd, lang) {
6178
6454
  const result = runProcessOrThrow(
6179
6455
  "git",
6180
6456
  ["status", "--porcelain=v1"],
6181
6457
  cwd,
6182
- "Failed to inspect git worktree"
6458
+ tg(lang, "inspectWorktreeFailed")
6183
6459
  );
6184
6460
  if (result.stdout.trim().length > 0) {
6185
6461
  throw createCliError(
6186
6462
  "PRECONDITION_FAILED",
6187
- "Git worktree is not clean. Commit or stash changes before merge retry sync."
6463
+ tg(lang, "worktreeNotClean")
6188
6464
  );
6189
6465
  }
6190
6466
  }
6191
- function commitAndPushPath(cwd, absPath, message) {
6467
+ function commitAndPushPath(cwd, absPath, message, lang) {
6192
6468
  const relativePath = path6.relative(cwd, absPath) || absPath;
6193
6469
  const status = runProcessOrThrow(
6194
6470
  "git",
6195
6471
  ["status", "--porcelain=v1", "--", relativePath],
6196
6472
  cwd,
6197
- "Failed to inspect git file status"
6473
+ tg(lang, "inspectFileStatusFailed")
6198
6474
  );
6199
6475
  if (status.stdout.trim().length === 0) return;
6200
- runProcessOrThrow("git", ["add", "--", relativePath], cwd, "Failed to stage file");
6201
- runProcessOrThrow("git", ["commit", "-m", message], cwd, "Failed to commit synced metadata");
6202
- const branch = gitCurrentBranch(cwd);
6476
+ runProcessOrThrow("git", ["add", "--", relativePath], cwd, tg(lang, "stageFileFailed"));
6477
+ runProcessOrThrow("git", ["commit", "-m", message], cwd, tg(lang, "commitSyncFailed"));
6478
+ const branch = gitCurrentBranch(cwd, lang);
6203
6479
  runProcessOrThrow(
6204
6480
  "git",
6205
6481
  ["push", "-u", "origin", branch],
6206
6482
  cwd,
6207
- "Failed to push synced metadata commit"
6483
+ tg(lang, "pushSyncFailed")
6208
6484
  );
6209
6485
  }
6210
6486
  function shouldRefreshHeadBranch(stderr, stdout) {
@@ -6214,18 +6490,19 @@ ${stdout}`;
6214
6490
  text
6215
6491
  );
6216
6492
  }
6217
- function refreshPrHeadBranch(prRef, cwd) {
6218
- ensureCleanWorktree(cwd);
6493
+ function refreshPrHeadBranch(prRef, cwd, lang) {
6494
+ ensureCleanWorktree(cwd, lang);
6219
6495
  const meta = runGhJson(
6220
6496
  ["pr", "view", prRef, "--json", "url,headRefName,baseRefName"],
6221
- cwd
6497
+ cwd,
6498
+ lang
6222
6499
  );
6223
- const originalBranch = gitCurrentBranch(cwd);
6500
+ const originalBranch = gitCurrentBranch(cwd, lang);
6224
6501
  runProcessOrThrow(
6225
6502
  "git",
6226
6503
  ["fetch", "origin", meta.baseRefName, meta.headRefName],
6227
6504
  cwd,
6228
- "Failed to fetch PR branches"
6505
+ tg(lang, "fetchPrBranchesFailed")
6229
6506
  );
6230
6507
  const hasLocalHead = runProcess(
6231
6508
  "git",
@@ -6237,38 +6514,38 @@ function refreshPrHeadBranch(prRef, cwd) {
6237
6514
  "git",
6238
6515
  ["checkout", meta.headRefName],
6239
6516
  cwd,
6240
- "Failed to checkout PR head branch"
6517
+ tg(lang, "checkoutHeadFailed")
6241
6518
  );
6242
6519
  } else {
6243
6520
  runProcessOrThrow(
6244
6521
  "git",
6245
6522
  ["checkout", "-B", meta.headRefName, `origin/${meta.headRefName}`],
6246
6523
  cwd,
6247
- "Failed to create local PR head branch"
6524
+ tg(lang, "createLocalHeadFailed")
6248
6525
  );
6249
6526
  }
6250
6527
  runProcessOrThrow(
6251
6528
  "git",
6252
6529
  ["rebase", `origin/${meta.baseRefName}`],
6253
6530
  cwd,
6254
- "Failed to rebase PR head branch"
6531
+ tg(lang, "rebaseHeadFailed")
6255
6532
  );
6256
6533
  runProcessOrThrow(
6257
6534
  "git",
6258
6535
  ["push", "--force-with-lease", "origin", meta.headRefName],
6259
6536
  cwd,
6260
- "Failed to push rebased PR head branch"
6537
+ tg(lang, "pushRebasedHeadFailed")
6261
6538
  );
6262
6539
  if (originalBranch !== meta.headRefName) {
6263
6540
  runProcessOrThrow(
6264
6541
  "git",
6265
6542
  ["checkout", originalBranch],
6266
6543
  cwd,
6267
- "Failed to restore previous branch after PR refresh"
6544
+ tg(lang, "restoreBranchFailed")
6268
6545
  );
6269
6546
  }
6270
6547
  }
6271
- function mergePrWithRetry(prRef, cwd, retryCount) {
6548
+ function mergePrWithRetry(prRef, cwd, retryCount, lang) {
6272
6549
  const attempts = Number.isFinite(retryCount) ? Math.max(1, retryCount) : 3;
6273
6550
  let lastError = "";
6274
6551
  for (let attempt = 1; attempt <= attempts; attempt++) {
@@ -6282,45 +6559,68 @@ function mergePrWithRetry(prRef, cwd, retryCount) {
6282
6559
  }
6283
6560
  lastError = (merged.stderr || merged.stdout || "").trim();
6284
6561
  if (shouldRefreshHeadBranch(merged.stderr, merged.stdout)) {
6285
- refreshPrHeadBranch(prRef, cwd);
6562
+ refreshPrHeadBranch(prRef, cwd, lang);
6286
6563
  continue;
6287
6564
  }
6288
6565
  }
6289
6566
  throw createCliError(
6290
6567
  "EXECUTION_FAILED",
6291
- `Failed to merge PR after retry attempts.${lastError ? ` Last error: ${lastError}` : ""}`
6568
+ tg(lang, "mergeRetryFailed", {
6569
+ lastError: lastError ? ` ${lastError}` : ""
6570
+ })
6292
6571
  );
6293
6572
  }
6294
- function toRetryCount(raw) {
6573
+ function toRetryCount(raw, lang) {
6295
6574
  if (!raw) return 3;
6296
6575
  const parsed = Number.parseInt(raw, 10);
6297
6576
  if (!Number.isFinite(parsed) || parsed <= 0) {
6298
- throw createCliError("INVALID_ARGUMENT", "`--retry` must be a positive integer.");
6577
+ throw createCliError("INVALID_ARGUMENT", tg(lang, "retryInvalid"));
6299
6578
  }
6300
6579
  return parsed;
6301
6580
  }
6302
6581
  function githubCommand(program2) {
6303
- const github = program2.command("github").description("GitHub workflow helpers (issue/pr templates, validation, merge retry)");
6304
- github.command("issue [feature-name]").description("Generate/create GitHub issue body from feature docs with validation").option("--json", "Output in JSON format for agents").option("--repo <repo>", "Component name for multi projects").option("--component <component>", "Component name for multi projects").option("--title <title>", "Issue title").option("--labels <labels>", "Comma-separated labels (default: enhancement)").option("--body-file <path>", "Issue body file output path").option("--assignee <assignee>", "Issue assignee (default: @me)").option("--create", "Create issue via gh CLI").action(async (featureName, options) => {
6582
+ const commandLang = detectGithubCliLangSync(process.cwd());
6583
+ const github = program2.command("github").description(tg(commandLang, "cmdGithubDescription"));
6584
+ github.command("issue [feature-name]").description(tg(commandLang, "cmdIssueDescription")).option("--json", tg(commandLang, "optJson")).option("--repo <repo>", tg(commandLang, "optRepo")).option("--component <component>", tg(commandLang, "optComponent")).option("--title <title>", tg(commandLang, "optIssueTitle")).option("--labels <labels>", tg(commandLang, "optLabels")).option("--body-file <path>", tg(commandLang, "optIssueBodyFile")).option("--assignee <assignee>", tg(commandLang, "optIssueAssignee")).option("--create", tg(commandLang, "optIssueCreate")).option(
6585
+ "--confirm <reply>",
6586
+ tg(commandLang, "optIssueConfirm")
6587
+ ).action(async (featureName, options) => {
6305
6588
  try {
6306
- const selectedComponent = resolveComponentOption4(options);
6589
+ const selectedComponent = resolveComponentOption4(options, commandLang);
6307
6590
  const { config, feature } = await resolveFeatureOrThrow(featureName, {
6308
6591
  component: selectedComponent
6309
- });
6310
- const labels = parseLabels(options.labels);
6592
+ }, commandLang);
6593
+ const labels = parseLabels(options.labels, config.lang);
6311
6594
  const paths = getFeatureDocPaths(feature);
6312
- ensureDocsExist(config.docsDir, [paths.specPath, paths.planPath, paths.tasksPath]);
6313
- const title = options.title?.trim() || `${feature.slug} (${feature.folderName} documentation update)`;
6314
- const body = buildIssueBody(feature, labels, paths);
6315
- ensureSections(body, ["Overview", "Goals", "Completion Criteria", "Related Documents", "Labels"], "Issue");
6595
+ ensureDocsExist(
6596
+ config.docsDir,
6597
+ [paths.specPath, paths.planPath, paths.tasksPath],
6598
+ config.lang
6599
+ );
6600
+ const title = options.title?.trim() || tg(config.lang, "issueDefaultTitle", {
6601
+ slug: feature.slug,
6602
+ folder: feature.folderName
6603
+ });
6604
+ const body = buildIssueBody(feature, labels, paths, config.lang);
6605
+ ensureSections(
6606
+ body,
6607
+ getRequiredIssueSections(config.lang),
6608
+ tg(config.lang, "kindIssue"),
6609
+ config.lang
6610
+ );
6316
6611
  const bodyFile = toBodyFilePath(
6317
6612
  options.bodyFile,
6318
6613
  `lee-spec-kit.issue.${feature.folderName}.md`
6319
6614
  );
6320
- await fs2.ensureDir(path6.dirname(bodyFile));
6321
- await fs2.writeFile(bodyFile, body, "utf-8");
6615
+ await fs14.ensureDir(path6.dirname(bodyFile));
6616
+ await fs14.writeFile(bodyFile, body, "utf-8");
6322
6617
  let issueUrl;
6323
6618
  if (options.create) {
6619
+ assertRemoteApproval(
6620
+ options.confirm,
6621
+ tg(config.lang, "operationIssueCreate"),
6622
+ config.lang
6623
+ );
6324
6624
  const args = [
6325
6625
  "issue",
6326
6626
  "create",
@@ -6338,7 +6638,7 @@ function githubCommand(program2) {
6338
6638
  "gh",
6339
6639
  args,
6340
6640
  process.cwd(),
6341
- "Failed to create GitHub issue"
6641
+ tg(config.lang, "createIssueFailed")
6342
6642
  );
6343
6643
  issueUrl = created.stdout.trim() || void 0;
6344
6644
  }
@@ -6362,19 +6662,18 @@ function githubCommand(program2) {
6362
6662
  return;
6363
6663
  }
6364
6664
  console.log();
6365
- console.log(chalk6.bold("\u{1F9FE} GitHub Issue Helper"));
6366
- console.log(chalk6.gray(`- Feature: ${feature.folderName}`));
6367
- console.log(chalk6.gray(`- Body file: ${bodyFile}`));
6368
- console.log(chalk6.gray(`- Labels: ${labels.join(", ")}`));
6665
+ console.log(chalk6.bold(tg(config.lang, "issueHeader")));
6666
+ console.log(chalk6.gray(`- ${tg(config.lang, "labelFeature")}: ${feature.folderName}`));
6667
+ console.log(chalk6.gray(`- ${tg(config.lang, "labelBodyFile")}: ${bodyFile}`));
6668
+ console.log(chalk6.gray(`- ${tg(config.lang, "labelLabels")}: ${labels.join(", ")}`));
6369
6669
  if (issueUrl) {
6370
- console.log(chalk6.green(`\u2705 Created: ${issueUrl}`));
6670
+ console.log(chalk6.green(tg(config.lang, "issueCreated", { url: issueUrl })));
6371
6671
  } else {
6372
- console.log(chalk6.blue("Template generated. Add --create to open the issue automatically."));
6672
+ console.log(chalk6.blue(tg(config.lang, "issueTemplateGenerated")));
6373
6673
  }
6374
6674
  console.log();
6375
6675
  } catch (error) {
6376
- const config = await getConfig(process.cwd());
6377
- const lang = config?.lang ?? DEFAULT_LANG;
6676
+ const lang = detectGithubCliLangSync(process.cwd());
6378
6677
  const cliError = toCliError(error);
6379
6678
  const suggestions = getCliErrorSuggestions(cliError.code, lang);
6380
6679
  if (options.json) {
@@ -6396,32 +6695,48 @@ function githubCommand(program2) {
6396
6695
  process.exit(1);
6397
6696
  }
6398
6697
  });
6399
- github.command("pr [feature-name]").description(
6400
- "Generate/create GitHub PR body with validation, tasks PR sync, and merge retry"
6401
- ).option("--json", "Output in JSON format for agents").option("--repo <repo>", "Component name for multi projects").option("--component <component>", "Component name for multi projects").option("--title <title>", "PR title").option("--labels <labels>", "Comma-separated labels (default: enhancement)").option("--body-file <path>", "PR body file output path").option("--assignee <assignee>", "PR assignee (default: @me)").option("--base <branch>", "PR base branch (default: main)", "main").option("--create", "Create PR via gh CLI").option("--pr <ref>", "Existing PR URL/number (used by --merge)").option("--merge", "Merge PR with retry and head-branch refresh").option("--retry <count>", "Retry count for merge (default: 3)").option("--no-sync-tasks", "Do not sync PR URL/PR status into tasks.md").option("--commit-sync", "Commit and push tasks.md metadata sync automatically").action(async (featureName, options) => {
6698
+ github.command("pr [feature-name]").description(tg(commandLang, "cmdPrDescription")).option("--json", tg(commandLang, "optJson")).option("--repo <repo>", tg(commandLang, "optRepo")).option("--component <component>", tg(commandLang, "optComponent")).option("--title <title>", tg(commandLang, "optPrTitle")).option("--labels <labels>", tg(commandLang, "optLabels")).option("--body-file <path>", tg(commandLang, "optPrBodyFile")).option("--assignee <assignee>", tg(commandLang, "optPrAssignee")).option("--base <branch>", tg(commandLang, "optPrBase"), "main").option("--create", tg(commandLang, "optPrCreate")).option("--pr <ref>", tg(commandLang, "optPrRef")).option("--merge", tg(commandLang, "optPrMerge")).option(
6699
+ "--confirm <reply>",
6700
+ tg(commandLang, "optPrConfirm")
6701
+ ).option("--retry <count>", tg(commandLang, "optPrRetry")).option("--no-sync-tasks", tg(commandLang, "optPrNoSyncTasks")).option("--commit-sync", tg(commandLang, "optPrCommitSync")).action(async (featureName, options) => {
6402
6702
  try {
6403
- const selectedComponent = resolveComponentOption4(options);
6703
+ const selectedComponent = resolveComponentOption4(options, commandLang);
6404
6704
  const { config, feature } = await resolveFeatureOrThrow(featureName, {
6405
6705
  component: selectedComponent
6406
- });
6407
- const labels = parseLabels(options.labels);
6706
+ }, commandLang);
6707
+ const labels = parseLabels(options.labels, config.lang);
6408
6708
  const paths = getFeatureDocPaths(feature);
6409
- ensureDocsExist(config.docsDir, [paths.specPath, paths.tasksPath]);
6410
- const defaultTitle = feature.issueNumber ? `feat(#${feature.issueNumber}): ${feature.slug} (implementation update)` : `feat: ${feature.slug} (implementation update)`;
6709
+ ensureDocsExist(config.docsDir, [paths.specPath, paths.tasksPath], config.lang);
6710
+ const defaultTitle = feature.issueNumber ? tg(config.lang, "prDefaultTitleWithIssue", {
6711
+ issue: feature.issueNumber,
6712
+ slug: feature.slug
6713
+ }) : tg(config.lang, "prDefaultTitleNoIssue", {
6714
+ slug: feature.slug
6715
+ });
6411
6716
  const title = options.title?.trim() || defaultTitle;
6412
- const body = buildPrBody(feature, paths);
6413
- ensureSections(body, ["Overview", "Changes", "Tests", "Related Documents"], "PR");
6717
+ const body = buildPrBody(feature, paths, config.lang);
6718
+ ensureSections(
6719
+ body,
6720
+ getRequiredPrSections(config.lang),
6721
+ tg(config.lang, "kindPr"),
6722
+ config.lang
6723
+ );
6414
6724
  const bodyFile = toBodyFilePath(
6415
6725
  options.bodyFile,
6416
6726
  `lee-spec-kit.pr.${feature.folderName}.md`
6417
6727
  );
6418
- await fs2.ensureDir(path6.dirname(bodyFile));
6419
- await fs2.writeFile(bodyFile, body, "utf-8");
6420
- const retryCount = toRetryCount(options.retry);
6728
+ await fs14.ensureDir(path6.dirname(bodyFile));
6729
+ await fs14.writeFile(bodyFile, body, "utf-8");
6730
+ const retryCount = toRetryCount(options.retry, config.lang);
6421
6731
  let prUrl = options.pr?.trim() || "";
6422
6732
  let mergedAttempts;
6423
6733
  let syncChanged = false;
6424
6734
  if (options.create) {
6735
+ assertRemoteApproval(
6736
+ options.confirm,
6737
+ tg(config.lang, "operationPrCreate"),
6738
+ config.lang
6739
+ );
6425
6740
  const args = [
6426
6741
  "pr",
6427
6742
  "create",
@@ -6441,48 +6756,62 @@ function githubCommand(program2) {
6441
6756
  "gh",
6442
6757
  args,
6443
6758
  process.cwd(),
6444
- "Failed to create GitHub PR"
6759
+ tg(config.lang, "createPrFailed")
6445
6760
  );
6446
6761
  prUrl = created.stdout.trim();
6447
6762
  }
6448
6763
  if (!prUrl && options.merge) {
6449
6764
  throw createCliError(
6450
6765
  "INVALID_ARGUMENT",
6451
- "`--merge` requires `--create` or `--pr <url|number>`."
6766
+ tg(config.lang, "mergeRequiresPr")
6767
+ );
6768
+ }
6769
+ if (options.merge) {
6770
+ assertRemoteApproval(
6771
+ options.confirm,
6772
+ tg(config.lang, "operationPrMerge"),
6773
+ config.lang
6452
6774
  );
6453
6775
  }
6454
6776
  if (prUrl && options.syncTasks !== false) {
6455
6777
  const synced = syncTasksPrMetadata(
6456
6778
  path6.join(config.docsDir, paths.tasksPath),
6457
6779
  prUrl,
6458
- "Review"
6780
+ "Review",
6781
+ config.lang
6459
6782
  );
6460
6783
  syncChanged = synced.changed;
6461
6784
  const shouldCommitSync = !!options.commitSync || !!options.merge;
6462
6785
  if (syncChanged && shouldCommitSync) {
6463
- const issueSuffix = feature.issueNumber ? `#${feature.issueNumber}` : feature.folderName;
6786
+ const message = feature.issueNumber ? tg(config.lang, "syncCommitWithIssue", {
6787
+ issue: feature.issueNumber,
6788
+ folder: feature.folderName
6789
+ }) : tg(config.lang, "syncCommitNoIssue", {
6790
+ folder: feature.folderName
6791
+ });
6464
6792
  commitAndPushPath(
6465
6793
  process.cwd(),
6466
6794
  synced.path,
6467
- `docs(${issueSuffix}): sync PR metadata for ${feature.folderName}`
6795
+ message,
6796
+ config.lang
6468
6797
  );
6469
6798
  }
6470
6799
  }
6471
6800
  if (options.merge) {
6472
- const merged = mergePrWithRetry(prUrl, process.cwd(), retryCount);
6801
+ const merged = mergePrWithRetry(prUrl, process.cwd(), retryCount, config.lang);
6473
6802
  mergedAttempts = merged.attempts;
6474
6803
  const baseBranch = options.base || "main";
6475
6804
  runProcessOrThrow(
6476
6805
  "git",
6477
6806
  ["checkout", baseBranch],
6478
6807
  process.cwd(),
6479
- `Failed to checkout ${baseBranch} after merge`
6808
+ tg(config.lang, "checkoutBaseAfterMergeFailed", { base: baseBranch })
6480
6809
  );
6481
6810
  runProcessOrThrow(
6482
6811
  "git",
6483
6812
  ["pull", "--rebase", "origin", baseBranch],
6484
6813
  process.cwd(),
6485
- `Failed to update ${baseBranch} after merge`
6814
+ tg(config.lang, "pullBaseAfterMergeFailed", { base: baseBranch })
6486
6815
  );
6487
6816
  }
6488
6817
  if (options.json) {
@@ -6508,25 +6837,26 @@ function githubCommand(program2) {
6508
6837
  return;
6509
6838
  }
6510
6839
  console.log();
6511
- console.log(chalk6.bold("\u{1F500} GitHub PR Helper"));
6512
- console.log(chalk6.gray(`- Feature: ${feature.folderName}`));
6513
- console.log(chalk6.gray(`- Body file: ${bodyFile}`));
6514
- console.log(chalk6.gray(`- Labels: ${labels.join(", ")}`));
6840
+ console.log(chalk6.bold(tg(config.lang, "prHeader")));
6841
+ console.log(chalk6.gray(`- ${tg(config.lang, "labelFeature")}: ${feature.folderName}`));
6842
+ console.log(chalk6.gray(`- ${tg(config.lang, "labelBodyFile")}: ${bodyFile}`));
6843
+ console.log(chalk6.gray(`- ${tg(config.lang, "labelLabels")}: ${labels.join(", ")}`));
6515
6844
  if (prUrl) {
6516
- console.log(chalk6.gray(`- PR: ${prUrl}`));
6845
+ console.log(chalk6.gray(`- ${tg(config.lang, "labelPr")}: ${prUrl}`));
6517
6846
  }
6518
6847
  if (syncChanged) {
6519
- console.log(chalk6.green("\u2705 tasks.md PR metadata synced."));
6848
+ console.log(chalk6.green(tg(config.lang, "prTasksSynced")));
6520
6849
  }
6521
6850
  if (options.merge) {
6522
- console.log(chalk6.green(`\u2705 PR merged (attempts: ${mergedAttempts ?? 1}).`));
6851
+ console.log(
6852
+ chalk6.green(tg(config.lang, "prMerged", { attempts: mergedAttempts ?? 1 }))
6853
+ );
6523
6854
  } else if (!options.create) {
6524
- console.log(chalk6.blue("Template generated. Add --create to open the PR automatically."));
6855
+ console.log(chalk6.blue(tg(config.lang, "prTemplateGenerated")));
6525
6856
  }
6526
6857
  console.log();
6527
6858
  } catch (error) {
6528
- const config = await getConfig(process.cwd());
6529
- const lang = config?.lang ?? DEFAULT_LANG;
6859
+ const lang = detectGithubCliLangSync(process.cwd());
6530
6860
  const cliError = toCliError(error);
6531
6861
  const suggestions = getCliErrorSuggestions(cliError.code, lang);
6532
6862
  if (options.json) {
@@ -6597,8 +6927,8 @@ var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
6597
6927
  function getCurrentVersion() {
6598
6928
  try {
6599
6929
  const packageJsonPath = path6.join(__dirname$1, "..", "package.json");
6600
- if (fs2.existsSync(packageJsonPath)) {
6601
- const pkg = fs2.readJsonSync(packageJsonPath);
6930
+ if (fs14.existsSync(packageJsonPath)) {
6931
+ const pkg = fs14.readJsonSync(packageJsonPath);
6602
6932
  return pkg.version;
6603
6933
  }
6604
6934
  } catch {
@@ -6607,8 +6937,8 @@ function getCurrentVersion() {
6607
6937
  }
6608
6938
  function readCache() {
6609
6939
  try {
6610
- if (fs2.existsSync(CACHE_FILE)) {
6611
- return fs2.readJsonSync(CACHE_FILE);
6940
+ if (fs14.existsSync(CACHE_FILE)) {
6941
+ return fs14.readJsonSync(CACHE_FILE);
6612
6942
  }
6613
6943
  } catch {
6614
6944
  }
@@ -6693,8 +7023,8 @@ if (shouldCheckForUpdates()) checkForUpdates();
6693
7023
  function getCliVersion() {
6694
7024
  try {
6695
7025
  const packageJsonPath = path6.join(__dirname$1, "..", "package.json");
6696
- if (fs2.existsSync(packageJsonPath)) {
6697
- const pkg = fs2.readJsonSync(packageJsonPath);
7026
+ if (fs14.existsSync(packageJsonPath)) {
7027
+ const pkg = fs14.readJsonSync(packageJsonPath);
6698
7028
  if (pkg?.version) return String(pkg.version);
6699
7029
  }
6700
7030
  } catch {