lee-spec-kit 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +497 -310
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import path17 from 'path';
2
+ import path18 from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { program } from 'commander';
5
- import fs15 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';
@@ -11,10 +11,10 @@ import { createHash } from 'crypto';
11
11
  import os from 'os';
12
12
 
13
13
  var getFilename = () => fileURLToPath(import.meta.url);
14
- var getDirname = () => path17.dirname(getFilename());
14
+ var getDirname = () => path18.dirname(getFilename());
15
15
  var __dirname$1 = /* @__PURE__ */ getDirname();
16
16
  async function copyTemplates(src, dest) {
17
- await fs15.copy(src, dest, {
17
+ await fs14.copy(src, dest, {
18
18
  overwrite: true,
19
19
  errorOnExist: false
20
20
  });
@@ -30,22 +30,22 @@ 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 fs15.readFile(file, "utf-8");
33
+ let content = await fs14.readFile(file, "utf-8");
34
34
  content = applyReplacements(content, replacements);
35
- await fs15.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 fs15.readFile(file, "utf-8");
39
+ let content = await fs14.readFile(file, "utf-8");
40
40
  content = applyReplacements(content, replacements);
41
- await fs15.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);
45
- var __dirname2 = path17.dirname(__filename2);
45
+ var __dirname2 = path18.dirname(__filename2);
46
46
  function getTemplatesDir() {
47
- const rootDir = path17.resolve(__dirname2, "..");
48
- return path17.join(rootDir, "templates");
47
+ const rootDir = path18.resolve(__dirname2, "..");
48
+ return path18.join(rootDir, "templates");
49
49
  }
50
50
 
51
51
  // src/utils/i18n.ts
@@ -1203,17 +1203,17 @@ function sleep(ms) {
1203
1203
  return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
1204
1204
  }
1205
1205
  function getDocsLockPath(docsDir) {
1206
- return path17.join(docsDir, ".lee-spec-kit.lock");
1206
+ return path18.join(docsDir, ".lee-spec-kit.lock");
1207
1207
  }
1208
1208
  function getInitLockPath(targetDir) {
1209
- return path17.join(
1210
- path17.dirname(targetDir),
1211
- `.lee-spec-kit.${path17.basename(targetDir)}.lock`
1209
+ return path18.join(
1210
+ path18.dirname(targetDir),
1211
+ `.lee-spec-kit.${path18.basename(targetDir)}.lock`
1212
1212
  );
1213
1213
  }
1214
1214
  async function isStaleLock(lockPath, staleMs) {
1215
1215
  try {
1216
- const stat = await fs15.stat(lockPath);
1216
+ const stat = await fs14.stat(lockPath);
1217
1217
  if (Date.now() - stat.mtimeMs <= staleMs) {
1218
1218
  return false;
1219
1219
  }
@@ -1228,7 +1228,7 @@ async function isStaleLock(lockPath, staleMs) {
1228
1228
  }
1229
1229
  async function readLockPayload(lockPath) {
1230
1230
  try {
1231
- const raw = await fs15.readFile(lockPath, "utf8");
1231
+ const raw = await fs14.readFile(lockPath, "utf8");
1232
1232
  const parsed = JSON.parse(raw);
1233
1233
  if (!parsed || typeof parsed !== "object") return null;
1234
1234
  return parsed;
@@ -1250,17 +1250,17 @@ function isProcessAlive(pid) {
1250
1250
  }
1251
1251
  }
1252
1252
  async function tryAcquire(lockPath, owner) {
1253
- await fs15.ensureDir(path17.dirname(lockPath));
1253
+ await fs14.ensureDir(path18.dirname(lockPath));
1254
1254
  try {
1255
- const fd = await fs15.open(lockPath, "wx");
1255
+ const fd = await fs14.open(lockPath, "wx");
1256
1256
  const payload = JSON.stringify(
1257
1257
  { pid: process.pid, owner: owner ?? "unknown", createdAt: (/* @__PURE__ */ new Date()).toISOString() },
1258
1258
  null,
1259
1259
  2
1260
1260
  );
1261
- await fs15.writeFile(fd, `${payload}
1261
+ await fs14.writeFile(fd, `${payload}
1262
1262
  `, { encoding: "utf8" });
1263
- await fs15.close(fd);
1263
+ await fs14.close(fd);
1264
1264
  return true;
1265
1265
  } catch (error) {
1266
1266
  if (error.code === "EEXIST") {
@@ -1274,9 +1274,9 @@ async function waitForLockRelease(lockPath, options = {}) {
1274
1274
  const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
1275
1275
  const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
1276
1276
  const startedAt = Date.now();
1277
- while (await fs15.pathExists(lockPath)) {
1277
+ while (await fs14.pathExists(lockPath)) {
1278
1278
  if (await isStaleLock(lockPath, staleMs)) {
1279
- await fs15.remove(lockPath);
1279
+ await fs14.remove(lockPath);
1280
1280
  break;
1281
1281
  }
1282
1282
  if (Date.now() - startedAt > timeoutMs) {
@@ -1294,7 +1294,7 @@ async function withFileLock(lockPath, task, options = {}) {
1294
1294
  const acquired = await tryAcquire(lockPath, options.owner);
1295
1295
  if (acquired) break;
1296
1296
  if (await isStaleLock(lockPath, staleMs)) {
1297
- await fs15.remove(lockPath);
1297
+ await fs14.remove(lockPath);
1298
1298
  continue;
1299
1299
  }
1300
1300
  if (Date.now() - startedAt > timeoutMs) {
@@ -1308,7 +1308,7 @@ async function withFileLock(lockPath, task, options = {}) {
1308
1308
  try {
1309
1309
  return await task();
1310
1310
  } finally {
1311
- await fs15.remove(lockPath).catch(() => {
1311
+ await fs14.remove(lockPath).catch(() => {
1312
1312
  });
1313
1313
  }
1314
1314
  }
@@ -1327,30 +1327,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
1327
1327
  "pr-template.md"
1328
1328
  ];
1329
1329
  var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
1330
- var ENGINE_MANAGED_FEATURE_PATH = path17.join(
1330
+ var ENGINE_MANAGED_FEATURE_PATH = path18.join(
1331
1331
  "features",
1332
1332
  "feature-base"
1333
1333
  );
1334
1334
  async function pruneEngineManagedDocs(docsDir) {
1335
1335
  const removed = [];
1336
1336
  for (const file of ENGINE_MANAGED_AGENT_FILES) {
1337
- const target = path17.join(docsDir, "agents", file);
1338
- if (await fs15.pathExists(target)) {
1339
- await fs15.remove(target);
1340
- removed.push(path17.relative(docsDir, target));
1337
+ const target = path18.join(docsDir, "agents", file);
1338
+ if (await fs14.pathExists(target)) {
1339
+ await fs14.remove(target);
1340
+ removed.push(path18.relative(docsDir, target));
1341
1341
  }
1342
1342
  }
1343
1343
  for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
1344
- const target = path17.join(docsDir, "agents", dir);
1345
- if (await fs15.pathExists(target)) {
1346
- await fs15.remove(target);
1347
- removed.push(path17.relative(docsDir, target));
1344
+ const target = path18.join(docsDir, "agents", dir);
1345
+ if (await fs14.pathExists(target)) {
1346
+ await fs14.remove(target);
1347
+ removed.push(path18.relative(docsDir, target));
1348
1348
  }
1349
1349
  }
1350
- const featureBasePath = path17.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1351
- if (await fs15.pathExists(featureBasePath)) {
1352
- await fs15.remove(featureBasePath);
1353
- removed.push(path17.relative(docsDir, featureBasePath));
1350
+ const featureBasePath = path18.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1351
+ if (await fs14.pathExists(featureBasePath)) {
1352
+ await fs14.remove(featureBasePath);
1353
+ removed.push(path18.relative(docsDir, featureBasePath));
1354
1354
  }
1355
1355
  return removed;
1356
1356
  }
@@ -1403,7 +1403,7 @@ ${tr(lang2, "cli", "common.canceled")}`)
1403
1403
  }
1404
1404
  async function runInit(options) {
1405
1405
  const cwd = process.cwd();
1406
- const defaultName = path17.basename(cwd);
1406
+ const defaultName = path18.basename(cwd);
1407
1407
  let projectName = options.name || defaultName;
1408
1408
  let projectType = options.type;
1409
1409
  let components = parseComponentsOption(options.components);
@@ -1413,7 +1413,7 @@ async function runInit(options) {
1413
1413
  let pushDocs = typeof options.pushDocs === "boolean" ? options.pushDocs : void 0;
1414
1414
  let docsRemote = options.docsRemote;
1415
1415
  let projectRoot;
1416
- const targetDir = path17.resolve(cwd, options.dir || "./docs");
1416
+ const targetDir = path18.resolve(cwd, options.dir || "./docs");
1417
1417
  const skipPrompts = !!options.yes || !!options.nonInteractive;
1418
1418
  if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
1419
1419
  throw createCliError(
@@ -1731,8 +1731,8 @@ async function runInit(options) {
1731
1731
  await withFileLock(
1732
1732
  initLockPath,
1733
1733
  async () => {
1734
- if (await fs15.pathExists(targetDir)) {
1735
- const files = await fs15.readdir(targetDir);
1734
+ if (await fs14.pathExists(targetDir)) {
1735
+ const files = await fs14.readdir(targetDir);
1736
1736
  if (files.length > 0) {
1737
1737
  if (options.force) {
1738
1738
  } else if (options.nonInteractive) {
@@ -1768,28 +1768,28 @@ async function runInit(options) {
1768
1768
  );
1769
1769
  console.log();
1770
1770
  const templatesDir = getTemplatesDir();
1771
- const commonPath = path17.join(templatesDir, lang, "common");
1771
+ const commonPath = path18.join(templatesDir, lang, "common");
1772
1772
  const templateProjectType = toTemplateProjectType(projectType);
1773
- const typePath = path17.join(templatesDir, lang, templateProjectType);
1774
- if (await fs15.pathExists(commonPath)) {
1773
+ const typePath = path18.join(templatesDir, lang, templateProjectType);
1774
+ if (await fs14.pathExists(commonPath)) {
1775
1775
  await copyTemplates(commonPath, targetDir);
1776
1776
  }
1777
- if (!await fs15.pathExists(typePath)) {
1777
+ if (!await fs14.pathExists(typePath)) {
1778
1778
  throw new Error(
1779
1779
  tr(lang, "cli", "init.error.templateNotFound", { path: typePath })
1780
1780
  );
1781
1781
  }
1782
1782
  await copyTemplates(typePath, targetDir);
1783
1783
  if (projectType === "multi" && !isDefaultFullstackComponents(components)) {
1784
- const featuresRoot = path17.join(targetDir, "features");
1785
- await fs15.remove(path17.join(featuresRoot, "fe"));
1786
- await fs15.remove(path17.join(featuresRoot, "be"));
1784
+ const featuresRoot = path18.join(targetDir, "features");
1785
+ await fs14.remove(path18.join(featuresRoot, "fe"));
1786
+ await fs14.remove(path18.join(featuresRoot, "be"));
1787
1787
  for (const component of components) {
1788
- const componentDir = path17.join(featuresRoot, component);
1789
- await fs15.ensureDir(componentDir);
1790
- const readmePath = path17.join(componentDir, "README.md");
1791
- if (!await fs15.pathExists(readmePath)) {
1792
- await fs15.writeFile(
1788
+ const componentDir = path18.join(featuresRoot, component);
1789
+ await fs14.ensureDir(componentDir);
1790
+ const readmePath = path18.join(componentDir, "README.md");
1791
+ if (!await fs14.pathExists(readmePath)) {
1792
+ await fs14.writeFile(
1793
1793
  readmePath,
1794
1794
  `# ${component.toUpperCase()} Features
1795
1795
 
@@ -1838,8 +1838,8 @@ Store ${component} feature specs here.
1838
1838
  config.projectRoot = projectRoot;
1839
1839
  }
1840
1840
  }
1841
- const configPath = path17.join(targetDir, ".lee-spec-kit.json");
1842
- await fs15.writeJson(configPath, config, { spaces: 2 });
1841
+ const configPath = path18.join(targetDir, ".lee-spec-kit.json");
1842
+ await fs14.writeJson(configPath, config, { spaces: 2 });
1843
1843
  console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
1844
1844
  console.log();
1845
1845
  await initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote);
@@ -1907,7 +1907,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
1907
1907
  console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
1908
1908
  runGit(["init"], cwd);
1909
1909
  }
1910
- const relativePath = path17.relative(cwd, targetDir);
1910
+ const relativePath = path18.relative(cwd, targetDir);
1911
1911
  const stagedBeforeAdd = getCachedStagedFiles(cwd);
1912
1912
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
1913
1913
  console.log(
@@ -1964,17 +1964,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
1964
1964
  }
1965
1965
  function getAncestorDirs(startDir) {
1966
1966
  const dirs = [];
1967
- let current = path17.resolve(startDir);
1967
+ let current = path18.resolve(startDir);
1968
1968
  while (true) {
1969
1969
  dirs.push(current);
1970
- const parent = path17.dirname(current);
1970
+ const parent = path18.dirname(current);
1971
1971
  if (parent === current) break;
1972
1972
  current = parent;
1973
1973
  }
1974
1974
  return dirs;
1975
1975
  }
1976
1976
  function hasWorkspaceBoundary(dir) {
1977
- return fs15.existsSync(path17.join(dir, "package.json")) || fs15.existsSync(path17.join(dir, ".git"));
1977
+ return fs14.existsSync(path18.join(dir, "package.json")) || fs14.existsSync(path18.join(dir, ".git"));
1978
1978
  }
1979
1979
  function getSearchBaseDirs(cwd) {
1980
1980
  const ancestors = getAncestorDirs(cwd);
@@ -1987,24 +1987,24 @@ function getSearchBaseDirs(cwd) {
1987
1987
  async function getConfig(cwd) {
1988
1988
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
1989
1989
  const baseDirs = [
1990
- ...explicitDocsDir ? [path17.resolve(explicitDocsDir)] : [],
1990
+ ...explicitDocsDir ? [path18.resolve(explicitDocsDir)] : [],
1991
1991
  ...getSearchBaseDirs(cwd)
1992
1992
  ];
1993
1993
  const visitedBaseDirs = /* @__PURE__ */ new Set();
1994
1994
  const visitedDocsDirs = /* @__PURE__ */ new Set();
1995
1995
  for (const baseDir of baseDirs) {
1996
- const resolvedBaseDir = path17.resolve(baseDir);
1996
+ const resolvedBaseDir = path18.resolve(baseDir);
1997
1997
  if (visitedBaseDirs.has(resolvedBaseDir)) continue;
1998
1998
  visitedBaseDirs.add(resolvedBaseDir);
1999
- const possibleDocsDirs = [path17.join(resolvedBaseDir, "docs"), resolvedBaseDir];
1999
+ const possibleDocsDirs = [path18.join(resolvedBaseDir, "docs"), resolvedBaseDir];
2000
2000
  for (const docsDir of possibleDocsDirs) {
2001
- const resolvedDocsDir = path17.resolve(docsDir);
2001
+ const resolvedDocsDir = path18.resolve(docsDir);
2002
2002
  if (visitedDocsDirs.has(resolvedDocsDir)) continue;
2003
2003
  visitedDocsDirs.add(resolvedDocsDir);
2004
- const configPath = path17.join(resolvedDocsDir, ".lee-spec-kit.json");
2005
- if (await fs15.pathExists(configPath)) {
2004
+ const configPath = path18.join(resolvedDocsDir, ".lee-spec-kit.json");
2005
+ if (await fs14.pathExists(configPath)) {
2006
2006
  try {
2007
- const configFile = await fs15.readJson(configPath);
2007
+ const configFile = await fs14.readJson(configPath);
2008
2008
  const projectType = normalizeProjectType(configFile.projectType);
2009
2009
  const components = resolveProjectComponents(
2010
2010
  projectType,
@@ -2027,22 +2027,22 @@ async function getConfig(cwd) {
2027
2027
  } catch {
2028
2028
  }
2029
2029
  }
2030
- const agentsPath = path17.join(resolvedDocsDir, "agents");
2031
- const featuresPath = path17.join(resolvedDocsDir, "features");
2032
- if (await fs15.pathExists(agentsPath) && await fs15.pathExists(featuresPath)) {
2033
- const bePath = path17.join(featuresPath, "be");
2034
- const fePath = path17.join(featuresPath, "fe");
2035
- const projectType = await fs15.pathExists(bePath) || await fs15.pathExists(fePath) ? "multi" : "single";
2030
+ const agentsPath = path18.join(resolvedDocsDir, "agents");
2031
+ const featuresPath = path18.join(resolvedDocsDir, "features");
2032
+ if (await fs14.pathExists(agentsPath) && await fs14.pathExists(featuresPath)) {
2033
+ const bePath = path18.join(featuresPath, "be");
2034
+ const fePath = path18.join(featuresPath, "fe");
2035
+ const projectType = await fs14.pathExists(bePath) || await fs14.pathExists(fePath) ? "multi" : "single";
2036
2036
  const components = projectType === "multi" ? resolveProjectComponents("multi", ["fe", "be"]) : void 0;
2037
2037
  const langProbeCandidates = [
2038
- path17.join(agentsPath, "custom.md"),
2039
- path17.join(agentsPath, "constitution.md"),
2040
- path17.join(agentsPath, "agents.md")
2038
+ path18.join(agentsPath, "custom.md"),
2039
+ path18.join(agentsPath, "constitution.md"),
2040
+ path18.join(agentsPath, "agents.md")
2041
2041
  ];
2042
2042
  let lang = "en";
2043
2043
  for (const candidate of langProbeCandidates) {
2044
- if (!await fs15.pathExists(candidate)) continue;
2045
- const content = await fs15.readFile(candidate, "utf-8");
2044
+ if (!await fs14.pathExists(candidate)) continue;
2045
+ const content = await fs14.readFile(candidate, "utf-8");
2046
2046
  if (/[가-힣]/.test(content)) {
2047
2047
  lang = "ko";
2048
2048
  break;
@@ -2088,14 +2088,14 @@ function sanitizeTasksForLocal(content, lang) {
2088
2088
  return normalizeTrailingBlankLines(next);
2089
2089
  }
2090
2090
  async function patchMarkdownIfExists(filePath, transform) {
2091
- if (!await fs15.pathExists(filePath)) return;
2092
- const content = await fs15.readFile(filePath, "utf-8");
2093
- await fs15.writeFile(filePath, transform(content), "utf-8");
2091
+ if (!await fs14.pathExists(filePath)) return;
2092
+ const content = await fs14.readFile(filePath, "utf-8");
2093
+ await fs14.writeFile(filePath, transform(content), "utf-8");
2094
2094
  }
2095
2095
  async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
2096
- await patchMarkdownIfExists(path17.join(featureDir, "spec.md"), sanitizeSpecForLocal);
2096
+ await patchMarkdownIfExists(path18.join(featureDir, "spec.md"), sanitizeSpecForLocal);
2097
2097
  await patchMarkdownIfExists(
2098
- path17.join(featureDir, "tasks.md"),
2098
+ path18.join(featureDir, "tasks.md"),
2099
2099
  (content) => sanitizeTasksForLocal(content, lang)
2100
2100
  );
2101
2101
  }
@@ -2245,29 +2245,29 @@ async function runFeature(name, options) {
2245
2245
  }
2246
2246
  let featuresDir;
2247
2247
  if (projectType === "multi") {
2248
- featuresDir = path17.join(docsDir, "features", component);
2248
+ featuresDir = path18.join(docsDir, "features", component);
2249
2249
  } else {
2250
- featuresDir = path17.join(docsDir, "features");
2250
+ featuresDir = path18.join(docsDir, "features");
2251
2251
  }
2252
2252
  const featureFolderName = `${featureId}-${name}`;
2253
- const featureDir = path17.join(featuresDir, featureFolderName);
2254
- if (await fs15.pathExists(featureDir)) {
2253
+ const featureDir = path18.join(featuresDir, featureFolderName);
2254
+ if (await fs14.pathExists(featureDir)) {
2255
2255
  throw createCliError(
2256
2256
  "INVALID_ARGUMENT",
2257
2257
  tr(lang, "cli", "feature.folderExists", { path: featureDir })
2258
2258
  );
2259
2259
  }
2260
- const featureBasePath = path17.join(
2260
+ const featureBasePath = path18.join(
2261
2261
  getTemplatesDir(),
2262
2262
  lang,
2263
2263
  toTemplateProjectType(projectType),
2264
2264
  "features",
2265
2265
  "feature-base"
2266
2266
  );
2267
- if (!await fs15.pathExists(featureBasePath)) {
2267
+ if (!await fs14.pathExists(featureBasePath)) {
2268
2268
  throw createCliError("DOCS_NOT_FOUND", tr(lang, "cli", "feature.baseNotFound"));
2269
2269
  }
2270
- await fs15.copy(featureBasePath, featureDir);
2270
+ await fs14.copy(featureBasePath, featureDir);
2271
2271
  const idNumber = featureId.replace("F", "");
2272
2272
  const repoName = projectType === "multi" ? `{{projectName}}-${component}` : "{{projectName}}";
2273
2273
  const replacements = {
@@ -2314,7 +2314,7 @@ async function runFeature(name, options) {
2314
2314
  featureName: name,
2315
2315
  component: projectType === "multi" ? component : void 0,
2316
2316
  featurePath: featureDir,
2317
- featurePathFromDocs: path17.relative(docsDir, featureDir)
2317
+ featurePathFromDocs: path18.relative(docsDir, featureDir)
2318
2318
  };
2319
2319
  },
2320
2320
  { owner: "feature" }
@@ -2326,9 +2326,9 @@ function sleep2(ms) {
2326
2326
  async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2327
2327
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
2328
2328
  const candidates = [
2329
- ...explicitDocsDir ? [path17.resolve(explicitDocsDir)] : [],
2330
- path17.resolve(cwd, "docs"),
2331
- path17.resolve(cwd)
2329
+ ...explicitDocsDir ? [path18.resolve(explicitDocsDir)] : [],
2330
+ path18.resolve(cwd, "docs"),
2331
+ path18.resolve(cwd)
2332
2332
  ];
2333
2333
  const endAt = Date.now() + timeoutMs;
2334
2334
  while (Date.now() < endAt) {
@@ -2339,7 +2339,7 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2339
2339
  const initLockPath = getInitLockPath(dir);
2340
2340
  const docsLockPath = getDocsLockPath(dir);
2341
2341
  for (const lockPath of [initLockPath, docsLockPath]) {
2342
- if (await fs15.pathExists(lockPath)) {
2342
+ if (await fs14.pathExists(lockPath)) {
2343
2343
  sawLock = true;
2344
2344
  await waitForLockRelease(lockPath, {
2345
2345
  timeoutMs: Math.max(200, endAt - Date.now()),
@@ -2355,17 +2355,17 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2355
2355
  return getConfig(cwd);
2356
2356
  }
2357
2357
  async function getNextFeatureId(docsDir, projectType, components) {
2358
- const featuresDir = path17.join(docsDir, "features");
2358
+ const featuresDir = path18.join(docsDir, "features");
2359
2359
  let max = 0;
2360
2360
  const scanDirs = [];
2361
2361
  if (projectType === "multi") {
2362
- scanDirs.push(...components.map((component) => path17.join(featuresDir, component)));
2362
+ scanDirs.push(...components.map((component) => path18.join(featuresDir, component)));
2363
2363
  } else {
2364
2364
  scanDirs.push(featuresDir);
2365
2365
  }
2366
2366
  for (const dir of scanDirs) {
2367
- if (!await fs15.pathExists(dir)) continue;
2368
- const entries = await fs15.readdir(dir, { withFileTypes: true });
2367
+ if (!await fs14.pathExists(dir)) continue;
2368
+ const entries = await fs14.readdir(dir, { withFileTypes: true });
2369
2369
  for (const entry of entries) {
2370
2370
  if (!entry.isDirectory()) continue;
2371
2371
  const match = entry.name.match(/^F(\d+)-/);
@@ -3133,6 +3133,59 @@ function getGitStatusPorcelain(cwd, relativePaths) {
3133
3133
  return void 0;
3134
3134
  }
3135
3135
  }
3136
+ function normalizeInputPath(value) {
3137
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
3138
+ }
3139
+ function toUniqueNormalizedPaths(relativePaths) {
3140
+ const seen = /* @__PURE__ */ new Set();
3141
+ const out = [];
3142
+ for (const value of relativePaths) {
3143
+ const normalized = normalizeInputPath(value);
3144
+ if (!normalized) continue;
3145
+ if (seen.has(normalized)) continue;
3146
+ seen.add(normalized);
3147
+ out.push(normalized);
3148
+ }
3149
+ return out;
3150
+ }
3151
+ function getTrackedGitPaths(cwd, relativePaths) {
3152
+ const inputs = toUniqueNormalizedPaths(relativePaths);
3153
+ if (inputs.length === 0) return /* @__PURE__ */ new Set();
3154
+ try {
3155
+ const out = execFileSync("git", ["ls-files", "--", ...inputs], {
3156
+ cwd,
3157
+ encoding: "utf-8",
3158
+ stdio: ["ignore", "pipe", "pipe"]
3159
+ });
3160
+ return new Set(
3161
+ out.split("\n").map((line) => normalizeInputPath(line)).filter(Boolean)
3162
+ );
3163
+ } catch {
3164
+ return void 0;
3165
+ }
3166
+ }
3167
+ function getIgnoredGitPaths(cwd, relativePaths) {
3168
+ const inputs = toUniqueNormalizedPaths(relativePaths);
3169
+ if (inputs.length === 0) return /* @__PURE__ */ new Set();
3170
+ try {
3171
+ const out = execFileSync("git", ["check-ignore", "--stdin"], {
3172
+ cwd,
3173
+ encoding: "utf-8",
3174
+ input: `${inputs.join("\n")}
3175
+ `,
3176
+ stdio: ["pipe", "pipe", "pipe"]
3177
+ });
3178
+ return new Set(
3179
+ out.split("\n").map((line) => normalizeInputPath(line)).filter(Boolean)
3180
+ );
3181
+ } catch (error) {
3182
+ if (error && typeof error === "object" && "status" in error) {
3183
+ const status = error.status;
3184
+ if (status === 1) return /* @__PURE__ */ new Set();
3185
+ }
3186
+ return void 0;
3187
+ }
3188
+ }
3136
3189
  function getLastCommitForPath(cwd, relativePath) {
3137
3190
  try {
3138
3191
  const out = execSync(`git rev-list -n 1 HEAD -- "${relativePath}"`, {
@@ -3280,13 +3333,13 @@ function parsePrLink(value) {
3280
3333
  return trimmed;
3281
3334
  }
3282
3335
  function normalizeGitPath(value) {
3283
- return value.split(path17.sep).join("/");
3336
+ return value.split(path18.sep).join("/");
3284
3337
  }
3285
3338
  function resolveProjectStatusPaths(projectGitCwd, docsDir) {
3286
- const relativeDocsDir = path17.relative(projectGitCwd, docsDir);
3339
+ const relativeDocsDir = path18.relative(projectGitCwd, docsDir);
3287
3340
  if (!relativeDocsDir) return [];
3288
- if (path17.isAbsolute(relativeDocsDir)) return [];
3289
- if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path17.sep}`)) {
3341
+ if (path18.isAbsolute(relativeDocsDir)) return [];
3342
+ if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path18.sep}`)) {
3290
3343
  return [];
3291
3344
  }
3292
3345
  const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(/\/+$/, "");
@@ -3305,6 +3358,8 @@ function uniqueNormalizedPaths(values) {
3305
3358
  }
3306
3359
  return out;
3307
3360
  }
3361
+ var PROJECT_DIRTY_STATUS_CACHE = /* @__PURE__ */ new Map();
3362
+ var COMPONENT_STATUS_PATH_CACHE = /* @__PURE__ */ new Map();
3308
3363
  async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
3309
3364
  const configured = workflow?.componentPaths?.[component];
3310
3365
  const configuredCandidates = Array.isArray(configured) ? configured.map((value) => String(value).trim()).filter(Boolean) : [];
@@ -3318,19 +3373,27 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
3318
3373
  const normalizedCandidates = uniqueNormalizedPaths(
3319
3374
  candidates.map((candidate) => {
3320
3375
  if (!candidate) return "";
3321
- if (!path17.isAbsolute(candidate)) return candidate;
3322
- const relative = path17.relative(projectGitCwd, candidate);
3376
+ if (!path18.isAbsolute(candidate)) return candidate;
3377
+ const relative = path18.relative(projectGitCwd, candidate);
3323
3378
  if (!relative) return "";
3324
- if (relative === ".." || relative.startsWith(`..${path17.sep}`)) return "";
3379
+ if (relative === ".." || relative.startsWith(`..${path18.sep}`)) return "";
3325
3380
  return relative;
3326
3381
  }).filter(Boolean)
3327
3382
  );
3383
+ const cacheKey = JSON.stringify({
3384
+ projectGitCwd,
3385
+ component,
3386
+ normalizedCandidates
3387
+ });
3388
+ const cached = COMPONENT_STATUS_PATH_CACHE.get(cacheKey);
3389
+ if (cached) return [...cached];
3328
3390
  const existing = [];
3329
3391
  for (const candidate of normalizedCandidates) {
3330
- if (await fs15.pathExists(path17.join(projectGitCwd, candidate))) {
3392
+ if (await fs14.pathExists(path18.join(projectGitCwd, candidate))) {
3331
3393
  existing.push(candidate);
3332
3394
  }
3333
3395
  }
3396
+ COMPONENT_STATUS_PATH_CACHE.set(cacheKey, [...existing]);
3334
3397
  return existing;
3335
3398
  }
3336
3399
  function parseTasks(content) {
@@ -3390,31 +3453,31 @@ async function parseFeature(featurePath, type, context, options) {
3390
3453
  const lang = options.lang;
3391
3454
  const workflowPolicy = resolveWorkflowPolicy(options.workflow);
3392
3455
  const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
3393
- const folderName = path17.basename(featurePath);
3456
+ const folderName = path18.basename(featurePath);
3394
3457
  const match = folderName.match(/^(F\d+)-(.+)$/);
3395
3458
  const id = match?.[1];
3396
3459
  const slug = match?.[2] || folderName;
3397
- const specPath = path17.join(featurePath, "spec.md");
3398
- const planPath = path17.join(featurePath, "plan.md");
3399
- const tasksPath = path17.join(featurePath, "tasks.md");
3460
+ const specPath = path18.join(featurePath, "spec.md");
3461
+ const planPath = path18.join(featurePath, "plan.md");
3462
+ const tasksPath = path18.join(featurePath, "tasks.md");
3400
3463
  let specStatus;
3401
3464
  let issueNumber;
3402
- const specExists = await fs15.pathExists(specPath);
3465
+ const specExists = await fs14.pathExists(specPath);
3403
3466
  if (specExists) {
3404
- const content = await fs15.readFile(specPath, "utf-8");
3467
+ const content = await fs14.readFile(specPath, "utf-8");
3405
3468
  const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
3406
3469
  specStatus = parseDocStatus(statusValue);
3407
3470
  const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
3408
3471
  issueNumber = parseIssueNumber(issueValue);
3409
3472
  }
3410
3473
  let planStatus;
3411
- const planExists = await fs15.pathExists(planPath);
3474
+ const planExists = await fs14.pathExists(planPath);
3412
3475
  if (planExists) {
3413
- const content = await fs15.readFile(planPath, "utf-8");
3476
+ const content = await fs14.readFile(planPath, "utf-8");
3414
3477
  const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
3415
3478
  planStatus = parseDocStatus(statusValue);
3416
3479
  }
3417
- const tasksExists = await fs15.pathExists(tasksPath);
3480
+ const tasksExists = await fs14.pathExists(tasksPath);
3418
3481
  const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
3419
3482
  let activeTask;
3420
3483
  let nextTodoTask;
@@ -3428,7 +3491,7 @@ async function parseFeature(featurePath, type, context, options) {
3428
3491
  let prStatusFieldExists = false;
3429
3492
  let prePrReviewFieldExists = false;
3430
3493
  if (tasksExists) {
3431
- const content = await fs15.readFile(tasksPath, "utf-8");
3494
+ const content = await fs14.readFile(tasksPath, "utf-8");
3432
3495
  const { summary, activeTask: active, nextTodoTask: nextTodo } = parseTasks(content);
3433
3496
  tasksSummary.total = summary.total;
3434
3497
  tasksSummary.todo = summary.todo;
@@ -3470,44 +3533,77 @@ async function parseFeature(featurePath, type, context, options) {
3470
3533
  slug,
3471
3534
  folderName
3472
3535
  );
3473
- const relativeFeaturePathFromDocs = path17.relative(context.docsDir, featurePath);
3474
- const docsPathIgnored = isGitPathIgnored(
3475
- context.docsGitCwd,
3476
- relativeFeaturePathFromDocs
3477
- );
3478
- const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [relativeFeaturePathFromDocs]);
3479
- const docsHasUncommittedChanges = docsStatus === void 0 ? true : docsStatus.trim().length > 0;
3480
- const dirtyScopePolicy = resolveCodeDirtyScopePolicy(options.workflow, options.projectType);
3481
- let projectStatusPaths = [];
3482
- if (context.projectGitCwd) {
3483
- if (dirtyScopePolicy === "component" && type !== "single") {
3484
- const componentStatusPaths = await resolveComponentStatusPaths(
3485
- context.projectGitCwd,
3486
- type,
3487
- options.workflow
3488
- );
3489
- projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(context.projectGitCwd, context.docsDir);
3536
+ const relativeFeaturePathFromDocs = path18.relative(context.docsDir, featurePath);
3537
+ const normalizedFeaturePathFromDocs = normalizeGitPath(relativeFeaturePathFromDocs);
3538
+ const docsPathIgnored = typeof context.docsPathIgnored === "boolean" ? context.docsPathIgnored : isGitPathIgnored(context.docsGitCwd, normalizedFeaturePathFromDocs);
3539
+ let docsHasUncommittedChanges = typeof context.docsHasUncommittedChanges === "boolean" ? context.docsHasUncommittedChanges : false;
3540
+ let docsEverCommitted = typeof context.docsEverCommitted === "boolean" ? context.docsEverCommitted : false;
3541
+ let docsGitUnavailable = !!context.docsGitUnavailable;
3542
+ if (typeof context.docsHasUncommittedChanges !== "boolean") {
3543
+ const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [normalizedFeaturePathFromDocs]);
3544
+ if (docsStatus === void 0) {
3545
+ docsGitUnavailable = true;
3546
+ docsHasUncommittedChanges = true;
3547
+ } else {
3548
+ docsHasUncommittedChanges = docsStatus.trim().length > 0;
3549
+ }
3550
+ }
3551
+ if (typeof context.docsEverCommitted !== "boolean") {
3552
+ const docsLastCommit = getLastCommitForPath(
3553
+ context.docsGitCwd,
3554
+ normalizedFeaturePathFromDocs
3555
+ );
3556
+ docsEverCommitted = !!docsLastCommit;
3557
+ }
3558
+ let projectHasUncommittedChanges = typeof context.projectHasUncommittedChanges === "boolean" ? context.projectHasUncommittedChanges : false;
3559
+ let projectStatusUnavailable = !!context.projectStatusUnavailable;
3560
+ if (typeof context.projectHasUncommittedChanges !== "boolean" && context.projectGitCwd) {
3561
+ const dirtyScopePolicy = resolveCodeDirtyScopePolicy(options.workflow, options.projectType);
3562
+ const projectCacheKey = JSON.stringify({
3563
+ projectGitCwd: context.projectGitCwd,
3564
+ docsDir: context.docsDir,
3565
+ type,
3566
+ dirtyScopePolicy,
3567
+ componentPaths: options.workflow?.componentPaths?.[type] || []
3568
+ });
3569
+ const cachedStatus = PROJECT_DIRTY_STATUS_CACHE.get(projectCacheKey);
3570
+ if (cachedStatus) {
3571
+ projectHasUncommittedChanges = cachedStatus.hasUncommittedChanges;
3572
+ projectStatusUnavailable = cachedStatus.statusUnavailable;
3490
3573
  } else {
3491
- projectStatusPaths = resolveProjectStatusPaths(
3574
+ let projectStatusPaths = [];
3575
+ if (dirtyScopePolicy === "component" && type !== "single") {
3576
+ const componentStatusPaths = await resolveComponentStatusPaths(
3577
+ context.projectGitCwd,
3578
+ type,
3579
+ options.workflow
3580
+ );
3581
+ projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(context.projectGitCwd, context.docsDir);
3582
+ } else {
3583
+ projectStatusPaths = resolveProjectStatusPaths(
3584
+ context.projectGitCwd,
3585
+ context.docsDir
3586
+ );
3587
+ }
3588
+ const projectStatus = getGitStatusPorcelain(
3492
3589
  context.projectGitCwd,
3493
- context.docsDir
3590
+ projectStatusPaths
3494
3591
  );
3592
+ projectStatusUnavailable = projectStatus === void 0;
3593
+ projectHasUncommittedChanges = projectStatus === void 0 ? false : projectStatus.trim().length > 0;
3594
+ PROJECT_DIRTY_STATUS_CACHE.set(projectCacheKey, {
3595
+ hasUncommittedChanges: projectHasUncommittedChanges,
3596
+ statusUnavailable: projectStatusUnavailable
3597
+ });
3495
3598
  }
3496
3599
  }
3497
- const projectStatus = context.projectGitCwd ? getGitStatusPorcelain(context.projectGitCwd, projectStatusPaths) : void 0;
3498
- const projectHasUncommittedChanges = projectStatus === void 0 ? false : projectStatus.trim().length > 0;
3499
- const docsLastCommit = getLastCommitForPath(
3500
- context.docsGitCwd,
3501
- relativeFeaturePathFromDocs
3502
- );
3503
- const docsEverCommitted = !!docsLastCommit;
3504
- if (docsStatus === void 0) {
3600
+ if (docsGitUnavailable) {
3505
3601
  warnings.push(tr(lang, "warnings", "docsGitUnavailable"));
3506
3602
  }
3507
3603
  if (docsPathIgnored === true) {
3508
3604
  warnings.push(
3509
3605
  tr(lang, "warnings", "docsPathIgnored", {
3510
- path: relativeFeaturePathFromDocs
3606
+ path: normalizedFeaturePathFromDocs
3511
3607
  })
3512
3608
  );
3513
3609
  }
@@ -3612,90 +3708,181 @@ async function parseFeature(featurePath, type, context, options) {
3612
3708
  );
3613
3709
  return { ...featureState, currentStep, actions, nextAction, warnings };
3614
3710
  }
3711
+ function normalizeRelPath(value) {
3712
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
3713
+ }
3714
+ function parsePorcelainChangedPaths(porcelain) {
3715
+ const changed = [];
3716
+ for (const rawLine of porcelain.split("\n")) {
3717
+ if (!rawLine.trim()) continue;
3718
+ const payload = rawLine.slice(3).trim();
3719
+ if (!payload) continue;
3720
+ const pathCandidate = payload.includes(" -> ") ? payload.split(" -> ").at(-1) || "" : payload;
3721
+ const normalized = normalizeRelPath(pathCandidate.replace(/^"+|"+$/g, ""));
3722
+ if (!normalized) continue;
3723
+ changed.push(normalized);
3724
+ }
3725
+ return changed;
3726
+ }
3727
+ function findFeaturePathPrefix(normalizedPath, relativeFeaturePaths) {
3728
+ for (const featurePath of relativeFeaturePaths) {
3729
+ if (normalizedPath === featurePath) return featurePath;
3730
+ if (normalizedPath.startsWith(`${featurePath}/`)) return featurePath;
3731
+ const nestedPrefix = `/${featurePath}`;
3732
+ if (normalizedPath.endsWith(nestedPrefix)) return featurePath;
3733
+ if (normalizedPath.includes(`${nestedPrefix}/`)) return featurePath;
3734
+ }
3735
+ return void 0;
3736
+ }
3737
+ function buildDefaultDocsFeatureGitMeta(relativeFeaturePaths) {
3738
+ const map = /* @__PURE__ */ new Map();
3739
+ for (const featurePath of relativeFeaturePaths) {
3740
+ map.set(featurePath, {
3741
+ docsPathIgnored: false,
3742
+ docsHasUncommittedChanges: false,
3743
+ docsEverCommitted: false,
3744
+ docsGitUnavailable: false
3745
+ });
3746
+ }
3747
+ return map;
3748
+ }
3749
+ function buildDocsFeatureGitMeta(docsGitCwd, relativeFeaturePaths) {
3750
+ const normalizedFeaturePaths = relativeFeaturePaths.map(
3751
+ (value) => normalizeRelPath(value)
3752
+ );
3753
+ const map = buildDefaultDocsFeatureGitMeta(normalizedFeaturePaths);
3754
+ if (normalizedFeaturePaths.length === 0) return map;
3755
+ const docsStatus = getGitStatusPorcelain(docsGitCwd, normalizedFeaturePaths);
3756
+ if (docsStatus === void 0) {
3757
+ for (const featurePath of normalizedFeaturePaths) {
3758
+ const current = map.get(featurePath);
3759
+ if (!current) continue;
3760
+ current.docsGitUnavailable = true;
3761
+ current.docsHasUncommittedChanges = true;
3762
+ }
3763
+ } else {
3764
+ const changedPaths = parsePorcelainChangedPaths(docsStatus);
3765
+ for (const changedPath of changedPaths) {
3766
+ const featurePath = findFeaturePathPrefix(changedPath, normalizedFeaturePaths);
3767
+ if (!featurePath) continue;
3768
+ const current = map.get(featurePath);
3769
+ if (!current) continue;
3770
+ current.docsHasUncommittedChanges = true;
3771
+ }
3772
+ }
3773
+ const trackedPaths = getTrackedGitPaths(docsGitCwd, normalizedFeaturePaths);
3774
+ if (trackedPaths) {
3775
+ for (const trackedPath of trackedPaths) {
3776
+ const featurePath = findFeaturePathPrefix(
3777
+ normalizeRelPath(trackedPath),
3778
+ normalizedFeaturePaths
3779
+ );
3780
+ if (!featurePath) continue;
3781
+ const current = map.get(featurePath);
3782
+ if (!current) continue;
3783
+ current.docsEverCommitted = true;
3784
+ }
3785
+ }
3786
+ const ignoredPaths = getIgnoredGitPaths(docsGitCwd, normalizedFeaturePaths);
3787
+ if (ignoredPaths) {
3788
+ for (const ignoredPath of ignoredPaths) {
3789
+ const featurePath = findFeaturePathPrefix(
3790
+ normalizeRelPath(ignoredPath),
3791
+ normalizedFeaturePaths
3792
+ );
3793
+ if (!featurePath) continue;
3794
+ const current = map.get(featurePath);
3795
+ if (!current) continue;
3796
+ current.docsPathIgnored = true;
3797
+ }
3798
+ }
3799
+ return map;
3800
+ }
3615
3801
  async function scanFeatures(config) {
3616
3802
  const features = [];
3617
3803
  const warnings = [];
3618
3804
  const stepDefinitions = getStepDefinitions(config.lang, config.workflow);
3619
3805
  const docsBranch = getCurrentBranch(config.docsDir);
3620
3806
  const projectBranches = {};
3807
+ const projectGitCwds = {};
3621
3808
  let singleProject;
3622
3809
  if (config.projectType === "single") {
3623
3810
  singleProject = resolveProjectGitCwd(config, "single", config.lang);
3624
3811
  if (singleProject.warning) warnings.push(singleProject.warning);
3625
3812
  projectBranches.single = singleProject.cwd ? getCurrentBranch(singleProject.cwd) : "";
3813
+ projectGitCwds.single = singleProject.cwd ?? void 0;
3626
3814
  } else {
3627
3815
  const components = resolveProjectComponents(config.projectType, config.components);
3628
3816
  for (const component of components) {
3629
3817
  const project = resolveProjectGitCwd(config, component, config.lang);
3630
3818
  if (project.warning) warnings.push(project.warning);
3631
3819
  projectBranches[component] = project.cwd ? getCurrentBranch(project.cwd) : "";
3820
+ projectGitCwds[component] = project.cwd ?? void 0;
3632
3821
  }
3633
3822
  }
3823
+ const allFeatureDirs = [];
3824
+ const componentFeatureDirs = /* @__PURE__ */ new Map();
3634
3825
  if (config.projectType === "single") {
3635
3826
  const featureDirs = await glob("features/*/", {
3636
3827
  cwd: config.docsDir,
3637
3828
  absolute: true,
3638
3829
  ignore: ["**/feature-base/**"]
3639
3830
  });
3640
- for (const dir of featureDirs) {
3641
- if ((await fs15.stat(dir)).isDirectory()) {
3642
- features.push(
3643
- await parseFeature(
3644
- dir,
3645
- "single",
3646
- {
3647
- projectBranch: projectBranches.single,
3648
- docsBranch,
3649
- docsGitCwd: config.docsDir,
3650
- projectGitCwd: singleProject?.cwd ?? void 0,
3651
- docsDir: config.docsDir,
3652
- projectBranchAvailable: Boolean(singleProject?.cwd)
3653
- },
3654
- {
3655
- lang: config.lang,
3656
- stepDefinitions,
3657
- approval: config.approval,
3658
- workflow: config.workflow,
3659
- projectType: config.projectType
3660
- }
3661
- )
3662
- );
3663
- }
3664
- }
3831
+ componentFeatureDirs.set("single", featureDirs);
3832
+ allFeatureDirs.push(...featureDirs);
3665
3833
  } else {
3666
3834
  const components = resolveProjectComponents(config.projectType, config.components);
3667
3835
  for (const component of components) {
3668
- const project = resolveProjectGitCwd(config, component, config.lang);
3669
3836
  const componentDirs = await glob(`features/${component}/*/`, {
3670
3837
  cwd: config.docsDir,
3671
3838
  absolute: true
3672
3839
  });
3673
- for (const dir of componentDirs) {
3674
- if (!(await fs15.stat(dir)).isDirectory()) continue;
3675
- features.push(
3676
- await parseFeature(
3677
- dir,
3678
- component,
3679
- {
3680
- projectBranch: projectBranches[component] || "",
3681
- docsBranch,
3682
- docsGitCwd: config.docsDir,
3683
- projectGitCwd: project.cwd ?? void 0,
3684
- docsDir: config.docsDir,
3685
- projectBranchAvailable: Boolean(project.cwd)
3686
- },
3687
- {
3688
- lang: config.lang,
3689
- stepDefinitions,
3690
- approval: config.approval,
3691
- workflow: config.workflow,
3692
- projectType: config.projectType
3693
- }
3694
- )
3695
- );
3696
- }
3840
+ componentFeatureDirs.set(component, componentDirs);
3841
+ allFeatureDirs.push(...componentDirs);
3697
3842
  }
3698
3843
  }
3844
+ const relativeFeaturePaths = allFeatureDirs.map(
3845
+ (dir) => normalizeRelPath(path18.relative(config.docsDir, dir))
3846
+ );
3847
+ const docsGitMeta = buildDocsFeatureGitMeta(config.docsDir, relativeFeaturePaths);
3848
+ const parseTargets = config.projectType === "single" ? [{ type: "single", dirs: componentFeatureDirs.get("single") || [] }] : resolveProjectComponents(config.projectType, config.components).map((component) => ({
3849
+ type: component,
3850
+ dirs: componentFeatureDirs.get(component) || []
3851
+ }));
3852
+ for (const target of parseTargets) {
3853
+ const parsed = await Promise.all(
3854
+ target.dirs.map(async (dir) => {
3855
+ const relativeFeaturePathFromDocs = normalizeRelPath(
3856
+ path18.relative(config.docsDir, dir)
3857
+ );
3858
+ const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
3859
+ return parseFeature(
3860
+ dir,
3861
+ target.type,
3862
+ {
3863
+ projectBranch: projectBranches[target.type] || "",
3864
+ docsBranch,
3865
+ docsGitCwd: config.docsDir,
3866
+ projectGitCwd: projectGitCwds[target.type],
3867
+ docsDir: config.docsDir,
3868
+ projectBranchAvailable: Boolean(projectGitCwds[target.type]),
3869
+ docsPathIgnored: docsMeta?.docsPathIgnored,
3870
+ docsHasUncommittedChanges: docsMeta?.docsHasUncommittedChanges,
3871
+ docsEverCommitted: docsMeta?.docsEverCommitted,
3872
+ docsGitUnavailable: docsMeta?.docsGitUnavailable
3873
+ },
3874
+ {
3875
+ lang: config.lang,
3876
+ stepDefinitions,
3877
+ approval: config.approval,
3878
+ workflow: config.workflow,
3879
+ projectType: config.projectType
3880
+ }
3881
+ );
3882
+ })
3883
+ );
3884
+ features.push(...parsed);
3885
+ }
3699
3886
  return {
3700
3887
  features,
3701
3888
  branches: {
@@ -3735,13 +3922,13 @@ async function runStatus(options) {
3735
3922
  );
3736
3923
  }
3737
3924
  const { docsDir, projectType, projectName, lang } = config;
3738
- const featuresDir = path17.join(docsDir, "features");
3925
+ const featuresDir = path18.join(docsDir, "features");
3739
3926
  const scan = await scanFeatures(config);
3740
3927
  const features = [];
3741
3928
  const idMap = /* @__PURE__ */ new Map();
3742
3929
  for (const f of scan.features) {
3743
3930
  const id = f.id || "UNKNOWN";
3744
- const relPath = path17.relative(docsDir, f.path);
3931
+ const relPath = path18.relative(docsDir, f.path);
3745
3932
  if (!idMap.has(id)) idMap.set(id, []);
3746
3933
  idMap.get(id).push(relPath);
3747
3934
  if (!f.docs.specExists || !f.docs.tasksExists) continue;
@@ -3822,7 +4009,7 @@ async function runStatus(options) {
3822
4009
  }
3823
4010
  console.log();
3824
4011
  if (options.write) {
3825
- const outputPath = path17.join(featuresDir, "status.md");
4012
+ const outputPath = path18.join(featuresDir, "status.md");
3826
4013
  const date = getLocalDateString();
3827
4014
  const content = [
3828
4015
  "# Feature Status",
@@ -3837,7 +4024,7 @@ async function runStatus(options) {
3837
4024
  ),
3838
4025
  ""
3839
4026
  ].join("\n");
3840
- await fs15.writeFile(outputPath, content, "utf-8");
4027
+ await fs14.writeFile(outputPath, content, "utf-8");
3841
4028
  console.log(
3842
4029
  chalk6.green(
3843
4030
  tr(lang, "cli", "status.wrote", { path: outputPath })
@@ -3850,9 +4037,9 @@ function escapeRegExp2(value) {
3850
4037
  }
3851
4038
  async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
3852
4039
  try {
3853
- const specPath = path17.join(featureDir, "spec.md");
3854
- if (!await fs15.pathExists(specPath)) return fallbackSlug;
3855
- const content = await fs15.readFile(specPath, "utf-8");
4040
+ const specPath = path18.join(featureDir, "spec.md");
4041
+ if (!await fs14.pathExists(specPath)) return fallbackSlug;
4042
+ const content = await fs14.readFile(specPath, "utf-8");
3856
4043
  const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
3857
4044
  for (const key of keys) {
3858
4045
  const regex = new RegExp(
@@ -3931,17 +4118,17 @@ async function runUpdate(options) {
3931
4118
  console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
3932
4119
  }
3933
4120
  if (agentsMode === "all") {
3934
- const commonAgentsBase = path17.join(templatesDir, lang, "common", "agents");
3935
- const typeAgentsBase = path17.join(
4121
+ const commonAgentsBase = path18.join(templatesDir, lang, "common", "agents");
4122
+ const typeAgentsBase = path18.join(
3936
4123
  templatesDir,
3937
4124
  lang,
3938
4125
  toTemplateProjectType(projectType),
3939
4126
  "agents"
3940
4127
  );
3941
- const targetAgentsBase = path17.join(docsDir, "agents");
3942
- const commonAgents = agentsMode === "skills" ? path17.join(commonAgentsBase, "skills") : commonAgentsBase;
3943
- const typeAgents = agentsMode === "skills" ? path17.join(typeAgentsBase, "skills") : typeAgentsBase;
3944
- const targetAgents = agentsMode === "skills" ? path17.join(targetAgentsBase, "skills") : targetAgentsBase;
4128
+ const targetAgentsBase = path18.join(docsDir, "agents");
4129
+ const commonAgents = agentsMode === "skills" ? path18.join(commonAgentsBase, "skills") : commonAgentsBase;
4130
+ const typeAgents = agentsMode === "skills" ? path18.join(typeAgentsBase, "skills") : typeAgentsBase;
4131
+ const targetAgents = agentsMode === "skills" ? path18.join(targetAgentsBase, "skills") : targetAgentsBase;
3945
4132
  const featurePath = projectType === "multi" ? isDefaultFullstackComponents(config.components || []) ? "docs/features/{be|fe}" : "docs/features/{component}" : "docs/features";
3946
4133
  const projectName = config.projectName ?? "{{projectName}}";
3947
4134
  const commonReplacements = {
@@ -3951,7 +4138,7 @@ async function runUpdate(options) {
3951
4138
  const typeReplacements = {
3952
4139
  "{{projectName}}": projectName
3953
4140
  };
3954
- if (await fs15.pathExists(commonAgents)) {
4141
+ if (await fs14.pathExists(commonAgents)) {
3955
4142
  const count = await updateFolder(
3956
4143
  commonAgents,
3957
4144
  targetAgents,
@@ -3969,7 +4156,7 @@ async function runUpdate(options) {
3969
4156
  );
3970
4157
  updatedCount += count;
3971
4158
  }
3972
- if (await fs15.pathExists(typeAgents)) {
4159
+ if (await fs14.pathExists(typeAgents)) {
3973
4160
  const count = await updateFolder(
3974
4161
  typeAgents,
3975
4162
  targetAgents,
@@ -4019,24 +4206,24 @@ async function runUpdate(options) {
4019
4206
  async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG, options = {}) {
4020
4207
  const protectedFiles = options.protectedFiles ?? /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
4021
4208
  const skipDirectories = options.skipDirectories ?? /* @__PURE__ */ new Set();
4022
- await fs15.ensureDir(targetDir);
4023
- const files = await fs15.readdir(sourceDir);
4209
+ await fs14.ensureDir(targetDir);
4210
+ const files = await fs14.readdir(sourceDir);
4024
4211
  let updatedCount = 0;
4025
4212
  for (const file of files) {
4026
- const sourcePath = path17.join(sourceDir, file);
4027
- const targetPath = path17.join(targetDir, file);
4028
- const stat = await fs15.stat(sourcePath);
4213
+ const sourcePath = path18.join(sourceDir, file);
4214
+ const targetPath = path18.join(targetDir, file);
4215
+ const stat = await fs14.stat(sourcePath);
4029
4216
  if (stat.isFile()) {
4030
4217
  if (protectedFiles.has(file)) {
4031
4218
  continue;
4032
4219
  }
4033
- let sourceContent = await fs15.readFile(sourcePath, "utf-8");
4220
+ let sourceContent = await fs14.readFile(sourcePath, "utf-8");
4034
4221
  if (replacements) {
4035
4222
  sourceContent = applyReplacements(sourceContent, replacements);
4036
4223
  }
4037
4224
  let shouldUpdate = true;
4038
- if (await fs15.pathExists(targetPath)) {
4039
- const targetContent = await fs15.readFile(targetPath, "utf-8");
4225
+ if (await fs14.pathExists(targetPath)) {
4226
+ const targetContent = await fs14.readFile(targetPath, "utf-8");
4040
4227
  if (sourceContent === targetContent) {
4041
4228
  continue;
4042
4229
  }
@@ -4050,7 +4237,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
4050
4237
  }
4051
4238
  }
4052
4239
  if (shouldUpdate) {
4053
- await fs15.writeFile(targetPath, sourceContent);
4240
+ await fs14.writeFile(targetPath, sourceContent);
4054
4241
  console.log(
4055
4242
  chalk6.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
4056
4243
  );
@@ -4107,7 +4294,7 @@ function extractPorcelainPaths(line) {
4107
4294
  function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
4108
4295
  const top = getGitTopLevel2(docsDir);
4109
4296
  if (!top) return null;
4110
- const rel = path17.relative(top, docsDir) || ".";
4297
+ const rel = path18.relative(top, docsDir) || ".";
4111
4298
  try {
4112
4299
  const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
4113
4300
  cwd: top,
@@ -4119,7 +4306,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
4119
4306
  }
4120
4307
  const ignoredRelPaths = new Set(
4121
4308
  ignoredAbsPaths.map(
4122
- (absPath) => normalizeGitPath2(path17.relative(top, absPath) || ".")
4309
+ (absPath) => normalizeGitPath2(path18.relative(top, absPath) || ".")
4123
4310
  )
4124
4311
  );
4125
4312
  const filtered = output.split("\n").filter((line) => {
@@ -4176,7 +4363,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
4176
4363
  }
4177
4364
  async function runConfig(options) {
4178
4365
  const cwd = process.cwd();
4179
- const targetCwd = options.dir ? path17.resolve(cwd, options.dir) : cwd;
4366
+ const targetCwd = options.dir ? path18.resolve(cwd, options.dir) : cwd;
4180
4367
  const config = await getConfig(targetCwd);
4181
4368
  if (!config) {
4182
4369
  throw createCliError(
@@ -4184,7 +4371,7 @@ async function runConfig(options) {
4184
4371
  tr(DEFAULT_LANG, "cli", "common.configNotFound")
4185
4372
  );
4186
4373
  }
4187
- const configPath = path17.join(config.docsDir, ".lee-spec-kit.json");
4374
+ const configPath = path18.join(config.docsDir, ".lee-spec-kit.json");
4188
4375
  if (!options.projectRoot) {
4189
4376
  console.log();
4190
4377
  console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
@@ -4195,7 +4382,7 @@ async function runConfig(options) {
4195
4382
  )
4196
4383
  );
4197
4384
  console.log();
4198
- const configFile = await fs15.readJson(configPath);
4385
+ const configFile = await fs14.readJson(configPath);
4199
4386
  console.log(JSON.stringify(configFile, null, 2));
4200
4387
  console.log();
4201
4388
  return;
@@ -4203,7 +4390,7 @@ async function runConfig(options) {
4203
4390
  await withFileLock(
4204
4391
  getDocsLockPath(config.docsDir),
4205
4392
  async () => {
4206
- const configFile = await fs15.readJson(configPath);
4393
+ const configFile = await fs14.readJson(configPath);
4207
4394
  if (configFile.docsRepo !== "standalone") {
4208
4395
  console.log(
4209
4396
  chalk6.yellow(tr(config.lang, "cli", "config.projectRootStandaloneOnly"))
@@ -4282,7 +4469,7 @@ async function runConfig(options) {
4282
4469
  )
4283
4470
  );
4284
4471
  }
4285
- await fs15.writeJson(configPath, configFile, { spaces: 2 });
4472
+ await fs14.writeJson(configPath, configFile, { spaces: 2 });
4286
4473
  console.log();
4287
4474
  },
4288
4475
  { owner: "config" }
@@ -4536,42 +4723,42 @@ var BUILTIN_DOC_DEFINITIONS = [
4536
4723
  {
4537
4724
  id: "agents",
4538
4725
  title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
4539
- relativePath: (projectType, lang) => path17.join(lang, toTemplateProjectType(projectType), "agents", "agents.md")
4726
+ relativePath: (projectType, lang) => path18.join(lang, toTemplateProjectType(projectType), "agents", "agents.md")
4540
4727
  },
4541
4728
  {
4542
4729
  id: "git-workflow",
4543
4730
  title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
4544
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "git-workflow.md")
4731
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "git-workflow.md")
4545
4732
  },
4546
4733
  {
4547
4734
  id: "issue-template",
4548
4735
  title: { ko: "Issue \uD15C\uD50C\uB9BF", en: "Issue Template" },
4549
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "issue-template.md")
4736
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "issue-template.md")
4550
4737
  },
4551
4738
  {
4552
4739
  id: "pr-template",
4553
4740
  title: { ko: "PR \uD15C\uD50C\uB9BF", en: "PR Template" },
4554
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "pr-template.md")
4741
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "pr-template.md")
4555
4742
  },
4556
4743
  {
4557
4744
  id: "create-feature",
4558
4745
  title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
4559
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "skills", "create-feature.md")
4746
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "skills", "create-feature.md")
4560
4747
  },
4561
4748
  {
4562
4749
  id: "execute-task",
4563
4750
  title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
4564
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "skills", "execute-task.md")
4751
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "skills", "execute-task.md")
4565
4752
  },
4566
4753
  {
4567
4754
  id: "create-issue",
4568
4755
  title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
4569
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "skills", "create-issue.md")
4756
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "skills", "create-issue.md")
4570
4757
  },
4571
4758
  {
4572
4759
  id: "create-pr",
4573
4760
  title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
4574
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "skills", "create-pr.md")
4761
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "skills", "create-pr.md")
4575
4762
  }
4576
4763
  ];
4577
4764
  var DOC_FOLLOWUPS = {
@@ -4655,7 +4842,7 @@ function listBuiltinDocs(projectType, lang) {
4655
4842
  id: doc.id,
4656
4843
  title: doc.title[lang],
4657
4844
  relativePath,
4658
- absolutePath: path17.join(templatesDir, relativePath)
4845
+ absolutePath: path18.join(templatesDir, relativePath)
4659
4846
  };
4660
4847
  });
4661
4848
  }
@@ -4664,7 +4851,7 @@ async function getBuiltinDoc(docId, projectType, lang) {
4664
4851
  if (!entry) {
4665
4852
  throw new Error(`Unknown builtin doc: ${docId}`);
4666
4853
  }
4667
- const content = await fs15.readFile(entry.absolutePath, "utf-8");
4854
+ const content = await fs14.readFile(entry.absolutePath, "utf-8");
4668
4855
  const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
4669
4856
  return {
4670
4857
  entry,
@@ -4749,7 +4936,7 @@ function getCommandExecutionLockPath(action, config) {
4749
4936
  if (action.scope === "docs") {
4750
4937
  return getDocsLockPath(config.docsDir);
4751
4938
  }
4752
- return path17.join(action.cwd, ".lee-spec-kit.project.lock");
4939
+ return path18.join(action.cwd, ".lee-spec-kit.project.lock");
4753
4940
  }
4754
4941
  function contextCommand(program2) {
4755
4942
  program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option("--repo <repo>", "Component name for multi projects").option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option("--approve <reply>", "Approve one labeled option: A or A OK").option("--execute", "Execute approved option when it is a command").option(
@@ -5143,7 +5330,7 @@ async function runContext(featureName, options) {
5143
5330
  if (f.issueNumber) {
5144
5331
  console.log(` \u2022 Issue: #${f.issueNumber}`);
5145
5332
  }
5146
- console.log(` \u2022 Path: ${path17.relative(cwd, f.path)}`);
5333
+ console.log(` \u2022 Path: ${path18.relative(cwd, f.path)}`);
5147
5334
  if (f.git.projectBranch) {
5148
5335
  console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
5149
5336
  }
@@ -5397,7 +5584,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
5397
5584
  ]);
5398
5585
  function formatPath(cwd, p) {
5399
5586
  if (!p) return "";
5400
- return path17.isAbsolute(p) ? path17.relative(cwd, p) : p;
5587
+ return path18.isAbsolute(p) ? path18.relative(cwd, p) : p;
5401
5588
  }
5402
5589
  function detectPlaceholders(content) {
5403
5590
  const patterns = [
@@ -5540,7 +5727,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
5540
5727
  const placeholderContext = {
5541
5728
  projectName: config.projectName,
5542
5729
  featureName: f.slug,
5543
- featurePath: f.docs.featurePathFromDocs || path17.relative(config.docsDir, f.path),
5730
+ featurePath: f.docs.featurePathFromDocs || path18.relative(config.docsDir, f.path),
5544
5731
  repoType: f.type,
5545
5732
  featureNumber
5546
5733
  };
@@ -5550,9 +5737,9 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
5550
5737
  "tasks.md"
5551
5738
  ];
5552
5739
  for (const file of files) {
5553
- const fullPath = path17.join(f.path, file);
5554
- if (!await fs15.pathExists(fullPath)) continue;
5555
- const original = await fs15.readFile(fullPath, "utf-8");
5740
+ const fullPath = path18.join(f.path, file);
5741
+ if (!await fs14.pathExists(fullPath)) continue;
5742
+ const original = await fs14.readFile(fullPath, "utf-8");
5556
5743
  let next = original;
5557
5744
  const changes = [];
5558
5745
  const placeholderFix = applyPlaceholderFixes(next, placeholderContext, config.lang);
@@ -5576,7 +5763,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
5576
5763
  }
5577
5764
  if (next === original) continue;
5578
5765
  if (!dryRun) {
5579
- await fs15.writeFile(fullPath, next, "utf-8");
5766
+ await fs14.writeFile(fullPath, next, "utf-8");
5580
5767
  }
5581
5768
  entries.push({
5582
5769
  path: formatPath(cwd, fullPath),
@@ -5595,8 +5782,8 @@ async function checkDocsStructure(config, cwd) {
5595
5782
  const issues = [];
5596
5783
  const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
5597
5784
  for (const dir of requiredDirs) {
5598
- const p = path17.join(config.docsDir, dir);
5599
- if (!await fs15.pathExists(p)) {
5785
+ const p = path18.join(config.docsDir, dir);
5786
+ if (!await fs14.pathExists(p)) {
5600
5787
  issues.push({
5601
5788
  level: "error",
5602
5789
  code: "missing_dir",
@@ -5605,8 +5792,8 @@ async function checkDocsStructure(config, cwd) {
5605
5792
  });
5606
5793
  }
5607
5794
  }
5608
- const configPath = path17.join(config.docsDir, ".lee-spec-kit.json");
5609
- if (!await fs15.pathExists(configPath)) {
5795
+ const configPath = path18.join(config.docsDir, ".lee-spec-kit.json");
5796
+ if (!await fs14.pathExists(configPath)) {
5610
5797
  issues.push({
5611
5798
  level: "warn",
5612
5799
  code: "missing_config",
@@ -5628,7 +5815,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5628
5815
  }
5629
5816
  const idMap = /* @__PURE__ */ new Map();
5630
5817
  for (const f of features) {
5631
- const rel = f.docs.featurePathFromDocs || path17.relative(config.docsDir, f.path);
5818
+ const rel = f.docs.featurePathFromDocs || path18.relative(config.docsDir, f.path);
5632
5819
  const id = f.id || "UNKNOWN";
5633
5820
  if (!idMap.has(id)) idMap.set(id, []);
5634
5821
  idMap.get(id).push(rel);
@@ -5636,9 +5823,9 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5636
5823
  if (!isInitialTemplateState) {
5637
5824
  const featureDocs = ["spec.md", "plan.md", "tasks.md"];
5638
5825
  for (const file of featureDocs) {
5639
- const p = path17.join(f.path, file);
5640
- if (!await fs15.pathExists(p)) continue;
5641
- const content = await fs15.readFile(p, "utf-8");
5826
+ const p = path18.join(f.path, file);
5827
+ if (!await fs14.pathExists(p)) continue;
5828
+ const content = await fs14.readFile(p, "utf-8");
5642
5829
  const placeholders = detectPlaceholders(content);
5643
5830
  if (placeholders.length === 0) continue;
5644
5831
  issues.push({
@@ -5651,9 +5838,9 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5651
5838
  });
5652
5839
  }
5653
5840
  if (decisionsPlaceholderMode !== "off") {
5654
- const decisionsPath = path17.join(f.path, "decisions.md");
5655
- if (await fs15.pathExists(decisionsPath)) {
5656
- const content = await fs15.readFile(decisionsPath, "utf-8");
5841
+ const decisionsPath = path18.join(f.path, "decisions.md");
5842
+ if (await fs14.pathExists(decisionsPath)) {
5843
+ const content = await fs14.readFile(decisionsPath, "utf-8");
5657
5844
  const placeholders = detectPlaceholders(content);
5658
5845
  if (placeholders.length > 0) {
5659
5846
  issues.push({
@@ -5680,7 +5867,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5680
5867
  level: "warn",
5681
5868
  code: "spec_status_unset",
5682
5869
  message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
5683
- path: formatPath(cwd, path17.join(f.path, "spec.md"))
5870
+ path: formatPath(cwd, path18.join(f.path, "spec.md"))
5684
5871
  });
5685
5872
  }
5686
5873
  if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
@@ -5688,7 +5875,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5688
5875
  level: "warn",
5689
5876
  code: "plan_status_unset",
5690
5877
  message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
5691
- path: formatPath(cwd, path17.join(f.path, "plan.md"))
5878
+ path: formatPath(cwd, path18.join(f.path, "plan.md"))
5692
5879
  });
5693
5880
  }
5694
5881
  if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
@@ -5696,7 +5883,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5696
5883
  level: "warn",
5697
5884
  code: "tasks_empty",
5698
5885
  message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
5699
- path: formatPath(cwd, path17.join(f.path, "tasks.md"))
5886
+ path: formatPath(cwd, path18.join(f.path, "tasks.md"))
5700
5887
  });
5701
5888
  }
5702
5889
  if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
@@ -5704,7 +5891,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5704
5891
  level: "warn",
5705
5892
  code: "tasks_doc_status_missing",
5706
5893
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
5707
- path: formatPath(cwd, path17.join(f.path, "tasks.md"))
5894
+ path: formatPath(cwd, path18.join(f.path, "tasks.md"))
5708
5895
  });
5709
5896
  }
5710
5897
  if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
@@ -5712,7 +5899,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5712
5899
  level: "warn",
5713
5900
  code: "tasks_doc_status_unset",
5714
5901
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
5715
- path: formatPath(cwd, path17.join(f.path, "tasks.md"))
5902
+ path: formatPath(cwd, path18.join(f.path, "tasks.md"))
5716
5903
  });
5717
5904
  }
5718
5905
  }
@@ -5736,7 +5923,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5736
5923
  level: "warn",
5737
5924
  code: "missing_feature_id",
5738
5925
  message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
5739
- path: formatPath(cwd, path17.join(config.docsDir, p))
5926
+ path: formatPath(cwd, path18.join(config.docsDir, p))
5740
5927
  });
5741
5928
  }
5742
5929
  return issues;
@@ -5858,7 +6045,7 @@ function doctorCommand(program2) {
5858
6045
  }
5859
6046
  console.log();
5860
6047
  console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
5861
- console.log(chalk6.gray(`- Docs: ${path17.relative(cwd, docsDir)}`));
6048
+ console.log(chalk6.gray(`- Docs: ${path18.relative(cwd, docsDir)}`));
5862
6049
  console.log(chalk6.gray(`- Type: ${projectType}`));
5863
6050
  console.log(chalk6.gray(`- Lang: ${lang}`));
5864
6051
  console.log();
@@ -6037,7 +6224,7 @@ async function runView(featureName, options) {
6037
6224
  }
6038
6225
  console.log();
6039
6226
  console.log(chalk6.bold("\u{1F4CA} Workflow View"));
6040
- console.log(chalk6.gray(`- Docs: ${path17.relative(cwd, config.docsDir)}`));
6227
+ console.log(chalk6.gray(`- Docs: ${path18.relative(cwd, config.docsDir)}`));
6041
6228
  console.log(
6042
6229
  chalk6.gray(
6043
6230
  `- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
@@ -6387,40 +6574,40 @@ function tg(lang, key, vars = {}) {
6387
6574
  }
6388
6575
  function detectGithubCliLangSync(cwd) {
6389
6576
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
6390
- const startDirs = [explicitDocsDir ? path17.resolve(explicitDocsDir) : "", path17.resolve(cwd)].filter(Boolean);
6577
+ const startDirs = [explicitDocsDir ? path18.resolve(explicitDocsDir) : "", path18.resolve(cwd)].filter(Boolean);
6391
6578
  const scanOrder = [];
6392
6579
  const seen = /* @__PURE__ */ new Set();
6393
6580
  for (const start of startDirs) {
6394
6581
  let current = start;
6395
6582
  while (true) {
6396
- const abs = path17.resolve(current);
6583
+ const abs = path18.resolve(current);
6397
6584
  if (!seen.has(abs)) {
6398
6585
  scanOrder.push(abs);
6399
6586
  seen.add(abs);
6400
6587
  }
6401
- const parent = path17.dirname(abs);
6588
+ const parent = path18.dirname(abs);
6402
6589
  if (parent === abs) break;
6403
6590
  current = parent;
6404
6591
  }
6405
6592
  }
6406
6593
  for (const base of scanOrder) {
6407
- for (const docsDir of [path17.join(base, "docs"), base]) {
6408
- const configPath = path17.join(docsDir, ".lee-spec-kit.json");
6409
- if (fs15.existsSync(configPath)) {
6594
+ for (const docsDir of [path18.join(base, "docs"), base]) {
6595
+ const configPath = path18.join(docsDir, ".lee-spec-kit.json");
6596
+ if (fs14.existsSync(configPath)) {
6410
6597
  try {
6411
- const parsed = fs15.readJsonSync(configPath);
6598
+ const parsed = fs14.readJsonSync(configPath);
6412
6599
  if (parsed?.lang === "ko" || parsed?.lang === "en") return parsed.lang;
6413
6600
  } catch {
6414
6601
  }
6415
6602
  }
6416
- const agentsPath = path17.join(docsDir, "agents");
6417
- const featuresPath = path17.join(docsDir, "features");
6418
- if (!fs15.existsSync(agentsPath) || !fs15.existsSync(featuresPath)) continue;
6603
+ const agentsPath = path18.join(docsDir, "agents");
6604
+ const featuresPath = path18.join(docsDir, "features");
6605
+ if (!fs14.existsSync(agentsPath) || !fs14.existsSync(featuresPath)) continue;
6419
6606
  for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
6420
- const file = path17.join(agentsPath, probe);
6421
- if (!fs15.existsSync(file)) continue;
6607
+ const file = path18.join(agentsPath, probe);
6608
+ if (!fs14.existsSync(file)) continue;
6422
6609
  try {
6423
- const content = fs15.readFileSync(file, "utf-8");
6610
+ const content = fs14.readFileSync(file, "utf-8");
6424
6611
  if (/[가-힣]/.test(content)) return "ko";
6425
6612
  } catch {
6426
6613
  }
@@ -6521,7 +6708,7 @@ function ensureSections(body, sections, kind, lang) {
6521
6708
  }
6522
6709
  function ensureDocsExist(docsDir, relativePaths, lang) {
6523
6710
  const missing = relativePaths.filter(
6524
- (relativePath) => !fs15.existsSync(path17.join(docsDir, relativePath))
6711
+ (relativePath) => !fs14.existsSync(path18.join(docsDir, relativePath))
6525
6712
  );
6526
6713
  if (missing.length > 0) {
6527
6714
  throw createCliError(
@@ -6531,13 +6718,13 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
6531
6718
  }
6532
6719
  }
6533
6720
  function buildDefaultBodyFileName(kind, docsDir, component) {
6534
- const key = `${path17.resolve(docsDir)}::${component.trim().toLowerCase()}`;
6721
+ const key = `${path18.resolve(docsDir)}::${component.trim().toLowerCase()}`;
6535
6722
  const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
6536
6723
  return `lee-spec-kit.${digest}.${kind}.md`;
6537
6724
  }
6538
6725
  function toBodyFilePath(raw, kind, docsDir, component) {
6539
- const selected = raw?.trim() || path17.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
6540
- return path17.resolve(selected);
6726
+ const selected = raw?.trim() || path18.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
6727
+ return path18.resolve(selected);
6541
6728
  }
6542
6729
  function toProjectRootDocsPath(relativePathFromDocs) {
6543
6730
  const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -7264,10 +7451,10 @@ function insertFieldInGithubIssueSection(content, key, value) {
7264
7451
  return { content: lines.join("\n"), changed: true };
7265
7452
  }
7266
7453
  function syncTasksPrMetadata(tasksPath, prUrl, nextStatus, lang) {
7267
- if (!fs15.existsSync(tasksPath)) {
7454
+ if (!fs14.existsSync(tasksPath)) {
7268
7455
  throw createCliError("DOCS_NOT_FOUND", tg(lang, "tasksNotFound", { path: tasksPath }));
7269
7456
  }
7270
- const original = fs15.readFileSync(tasksPath, "utf-8");
7457
+ const original = fs14.readFileSync(tasksPath, "utf-8");
7271
7458
  let next = original;
7272
7459
  let changed = false;
7273
7460
  const prReplaced = replaceListField(next, ["PR", "Pull Request"], prUrl);
@@ -7291,7 +7478,7 @@ function syncTasksPrMetadata(tasksPath, prUrl, nextStatus, lang) {
7291
7478
  changed = changed || inserted.changed;
7292
7479
  }
7293
7480
  if (changed) {
7294
- fs15.writeFileSync(tasksPath, next, "utf-8");
7481
+ fs14.writeFileSync(tasksPath, next, "utf-8");
7295
7482
  }
7296
7483
  return { changed, path: tasksPath };
7297
7484
  }
@@ -7319,7 +7506,7 @@ function ensureCleanWorktree(cwd, lang) {
7319
7506
  }
7320
7507
  }
7321
7508
  function commitAndPushPath(cwd, absPath, message, lang) {
7322
- const relativePath = path17.relative(cwd, absPath) || absPath;
7509
+ const relativePath = path18.relative(cwd, absPath) || absPath;
7323
7510
  const status = runProcessOrThrow(
7324
7511
  "git",
7325
7512
  ["status", "--porcelain=v1", "--", relativePath],
@@ -7451,9 +7638,9 @@ function githubCommand(program2) {
7451
7638
  [paths.specPath, paths.planPath, paths.tasksPath],
7452
7639
  config.lang
7453
7640
  );
7454
- const specContent = await fs15.readFile(path17.join(config.docsDir, paths.specPath), "utf-8");
7455
- const planContent = await fs15.readFile(path17.join(config.docsDir, paths.planPath), "utf-8");
7456
- const tasksContent = await fs15.readFile(path17.join(config.docsDir, paths.tasksPath), "utf-8");
7641
+ const specContent = await fs14.readFile(path18.join(config.docsDir, paths.specPath), "utf-8");
7642
+ const planContent = await fs14.readFile(path18.join(config.docsDir, paths.planPath), "utf-8");
7643
+ const tasksContent = await fs14.readFile(path18.join(config.docsDir, paths.tasksPath), "utf-8");
7457
7644
  const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
7458
7645
  const title = options.title?.trim() || tg(config.lang, "issueDefaultTitle", {
7459
7646
  slug: feature.slug,
@@ -7482,8 +7669,8 @@ function githubCommand(program2) {
7482
7669
  );
7483
7670
  const explicitBodyFile = (options.bodyFile || "").trim();
7484
7671
  let body = generatedBody;
7485
- if (options.create && explicitBodyFile && await fs15.pathExists(bodyFile)) {
7486
- body = await fs15.readFile(bodyFile, "utf-8");
7672
+ if (options.create && explicitBodyFile && await fs14.pathExists(bodyFile)) {
7673
+ body = await fs14.readFile(bodyFile, "utf-8");
7487
7674
  ensureSections(
7488
7675
  body,
7489
7676
  getRequiredIssueSections(config.lang),
@@ -7491,8 +7678,8 @@ function githubCommand(program2) {
7491
7678
  config.lang
7492
7679
  );
7493
7680
  } else {
7494
- await fs15.ensureDir(path17.dirname(bodyFile));
7495
- await fs15.writeFile(bodyFile, generatedBody, "utf-8");
7681
+ await fs14.ensureDir(path18.dirname(bodyFile));
7682
+ await fs14.writeFile(bodyFile, generatedBody, "utf-8");
7496
7683
  }
7497
7684
  let issueUrl;
7498
7685
  if (options.create) {
@@ -7589,10 +7776,10 @@ function githubCommand(program2) {
7589
7776
  const labels = parseLabels(options.labels, config.lang);
7590
7777
  const paths = getFeatureDocPaths(feature);
7591
7778
  ensureDocsExist(config.docsDir, [paths.specPath, paths.tasksPath], config.lang);
7592
- const specContent = await fs15.readFile(path17.join(config.docsDir, paths.specPath), "utf-8");
7593
- const planPath = path17.join(config.docsDir, paths.planPath);
7594
- const planContent = await fs15.pathExists(planPath) ? await fs15.readFile(planPath, "utf-8") : "";
7595
- const tasksContent = await fs15.readFile(path17.join(config.docsDir, paths.tasksPath), "utf-8");
7779
+ const specContent = await fs14.readFile(path18.join(config.docsDir, paths.specPath), "utf-8");
7780
+ const planPath = path18.join(config.docsDir, paths.planPath);
7781
+ const planContent = await fs14.pathExists(planPath) ? await fs14.readFile(planPath, "utf-8") : "";
7782
+ const tasksContent = await fs14.readFile(path18.join(config.docsDir, paths.tasksPath), "utf-8");
7596
7783
  const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
7597
7784
  const defaultTitle = feature.issueNumber ? tg(config.lang, "prDefaultTitleWithIssue", {
7598
7785
  issue: feature.issueNumber,
@@ -7626,8 +7813,8 @@ function githubCommand(program2) {
7626
7813
  );
7627
7814
  const explicitBodyFile = (options.bodyFile || "").trim();
7628
7815
  let body = generatedBody;
7629
- if (options.create && explicitBodyFile && await fs15.pathExists(bodyFile)) {
7630
- body = await fs15.readFile(bodyFile, "utf-8");
7816
+ if (options.create && explicitBodyFile && await fs14.pathExists(bodyFile)) {
7817
+ body = await fs14.readFile(bodyFile, "utf-8");
7631
7818
  ensureSections(
7632
7819
  body,
7633
7820
  getRequiredPrSections(config.lang),
@@ -7635,8 +7822,8 @@ function githubCommand(program2) {
7635
7822
  config.lang
7636
7823
  );
7637
7824
  } else {
7638
- await fs15.ensureDir(path17.dirname(bodyFile));
7639
- await fs15.writeFile(bodyFile, generatedBody, "utf-8");
7825
+ await fs14.ensureDir(path18.dirname(bodyFile));
7826
+ await fs14.writeFile(bodyFile, generatedBody, "utf-8");
7640
7827
  }
7641
7828
  const retryCount = toRetryCount(options.retry, config.lang);
7642
7829
  let prUrl = options.pr?.trim() || "";
@@ -7688,7 +7875,7 @@ function githubCommand(program2) {
7688
7875
  }
7689
7876
  if (prUrl && options.syncTasks !== false) {
7690
7877
  const synced = syncTasksPrMetadata(
7691
- path17.join(config.docsDir, paths.tasksPath),
7878
+ path18.join(config.docsDir, paths.tasksPath),
7692
7879
  prUrl,
7693
7880
  "Review",
7694
7881
  config.lang
@@ -7909,7 +8096,7 @@ function docsCommand(program2) {
7909
8096
  );
7910
8097
  return;
7911
8098
  }
7912
- const relativeFromCwd = path17.relative(process.cwd(), loaded.entry.absolutePath);
8099
+ const relativeFromCwd = path18.relative(process.cwd(), loaded.entry.absolutePath);
7913
8100
  console.log();
7914
8101
  console.log(chalk6.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
7915
8102
  console.log(
@@ -7999,13 +8186,13 @@ ${version}
7999
8186
  }
8000
8187
  return `${ascii}${footer}`;
8001
8188
  }
8002
- var CACHE_FILE = path17.join(os.homedir(), ".lee-spec-kit-version-cache.json");
8189
+ var CACHE_FILE = path18.join(os.homedir(), ".lee-spec-kit-version-cache.json");
8003
8190
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
8004
8191
  function getCurrentVersion() {
8005
8192
  try {
8006
- const packageJsonPath = path17.join(__dirname$1, "..", "package.json");
8007
- if (fs15.existsSync(packageJsonPath)) {
8008
- const pkg = fs15.readJsonSync(packageJsonPath);
8193
+ const packageJsonPath = path18.join(__dirname$1, "..", "package.json");
8194
+ if (fs14.existsSync(packageJsonPath)) {
8195
+ const pkg = fs14.readJsonSync(packageJsonPath);
8009
8196
  return pkg.version;
8010
8197
  }
8011
8198
  } catch {
@@ -8014,8 +8201,8 @@ function getCurrentVersion() {
8014
8201
  }
8015
8202
  function readCache() {
8016
8203
  try {
8017
- if (fs15.existsSync(CACHE_FILE)) {
8018
- return fs15.readJsonSync(CACHE_FILE);
8204
+ if (fs14.existsSync(CACHE_FILE)) {
8205
+ return fs14.readJsonSync(CACHE_FILE);
8019
8206
  }
8020
8207
  } catch {
8021
8208
  }
@@ -8103,9 +8290,9 @@ function shouldCheckForUpdates() {
8103
8290
  if (shouldCheckForUpdates()) checkForUpdates();
8104
8291
  function getCliVersion() {
8105
8292
  try {
8106
- const packageJsonPath = path17.join(__dirname$1, "..", "package.json");
8107
- if (fs15.existsSync(packageJsonPath)) {
8108
- const pkg = fs15.readJsonSync(packageJsonPath);
8293
+ const packageJsonPath = path18.join(__dirname$1, "..", "package.json");
8294
+ if (fs14.existsSync(packageJsonPath)) {
8295
+ const pkg = fs14.readJsonSync(packageJsonPath);
8109
8296
  if (pkg?.version) return String(pkg.version);
8110
8297
  }
8111
8298
  } catch {