lee-spec-kit 0.6.4 → 0.6.6

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 +560 -321
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,20 +1,20 @@
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';
9
9
  import { spawn, execSync, spawnSync, execFileSync } from 'child_process';
10
- import { createHash } from 'crypto';
11
10
  import os from 'os';
11
+ import { createHash } from 'crypto';
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
@@ -396,8 +396,8 @@ var I18N = {
396
396
  issueCreateAndWrite: "`npx lee-spec-kit docs get create-issue --json`\uC73C\uB85C \uC808\uCC28\uB97C \uD655\uC778\uD55C \uB4A4, `npx lee-spec-kit github issue {featureRef} --json`\uC73C\uB85C \uCD08\uC548\uC744 \uC0DD\uC131\uD558\uC138\uC694. \uBAA9\uD45C/\uC644\uB8CC \uAE30\uC900\uC744 \uAC80\uD1A0\xB7\uBCF4\uC644\uD558\uACE0 \uC0AC\uC6A9\uC790 \uC2B9\uC778(OK) \uD6C4 `--create --confirm OK`\uB85C \uC0DD\uC131\uD55C \uB2E4\uC74C, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694.",
397
397
  docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
398
398
  docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
399
- projectCommitIssueUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat(#{issueNumber}): {folderName} \uAD6C\uD604 \uC5C5\uB370\uC774\uD2B8"',
400
- projectCommitUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat: {folderName} \uAD6C\uD604 \uC5C5\uB370\uC774\uD2B8"',
399
+ projectCommitIssueUpdate: 'cd "{projectGitCwd}" && (git diff --cached --quiet && echo "\uC2A4\uD14C\uC774\uC9D5\uB41C \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774\uBC88 \uD0DC\uC2A4\uD06C\uC5D0\uC11C \uC218\uC815\uD55C \uD30C\uC77C\uB9CC \uC120\uD0DD\uD574 git add [files] \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694." && exit 1 || git commit -m "feat(#{issueNumber}): {commitTopic}")',
400
+ projectCommitUpdate: 'cd "{projectGitCwd}" && (git diff --cached --quiet && echo "\uC2A4\uD14C\uC774\uC9D5\uB41C \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774\uBC88 \uD0DC\uC2A4\uD06C\uC5D0\uC11C \uC218\uC815\uD55C \uD30C\uC77C\uB9CC \uC120\uD0DD\uD574 git add [files] \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694." && exit 1 || git commit -m "feat({folderName}): {commitTopic}")',
401
401
  standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
402
402
  createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
403
403
  tasksAllDoneButNoChecklist: '\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC139\uC158\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC744 \uCD94\uAC00/\uD655\uC778\uD558\uC138\uC694.',
@@ -772,8 +772,8 @@ var I18N = {
772
772
  issueCreateAndWrite: "Review procedure with `npx lee-spec-kit docs get create-issue --json`, then generate a draft via `npx lee-spec-kit github issue {featureRef} --json`. Refine goals/completion criteria, get explicit user OK, run `--create --confirm OK`, then update issue number in spec.md/tasks.md and prepare a docs commit.",
773
773
  docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
774
774
  docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} docs update"',
775
- projectCommitIssueUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat(#{issueNumber}): {folderName} implementation update"',
776
- projectCommitUpdate: 'cd "{projectGitCwd}" && git add -A && git commit -m "feat: {folderName} implementation update"',
775
+ projectCommitIssueUpdate: 'cd "{projectGitCwd}" && (git diff --cached --quiet && echo "No staged files. Stage only files changed in this task with git add [files], then run again." && exit 1 || git commit -m "feat(#{issueNumber}): {commitTopic}")',
776
+ projectCommitUpdate: 'cd "{projectGitCwd}" && (git diff --cached --quiet && echo "No staged files. Stage only files changed in this task with git add [files], then run again." && exit 1 || git commit -m "feat({folderName}): {commitTopic}")',
777
777
  standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
778
778
  createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
779
779
  tasksAllDoneButNoChecklist: 'All tasks are DONE, but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
@@ -1203,17 +1203,38 @@ 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
+ function getTempProjectLockPath(cwd) {
1215
+ const key = createHash("sha1").update(path18.resolve(cwd)).digest("hex");
1216
+ return path18.join(os.tmpdir(), "lee-spec-kit-locks", `${key}.project.lock`);
1217
+ }
1218
+ function getProjectExecutionLockPath(cwd) {
1219
+ try {
1220
+ const out = execFileSync(
1221
+ "git",
1222
+ ["rev-parse", "--git-path", "lee-spec-kit.project.lock"],
1223
+ {
1224
+ cwd,
1225
+ encoding: "utf-8",
1226
+ stdio: ["ignore", "pipe", "ignore"]
1227
+ }
1228
+ ).trim();
1229
+ if (!out) return getTempProjectLockPath(cwd);
1230
+ return path18.isAbsolute(out) ? out : path18.resolve(cwd, out);
1231
+ } catch {
1232
+ return getTempProjectLockPath(cwd);
1233
+ }
1234
+ }
1214
1235
  async function isStaleLock(lockPath, staleMs) {
1215
1236
  try {
1216
- const stat = await fs15.stat(lockPath);
1237
+ const stat = await fs14.stat(lockPath);
1217
1238
  if (Date.now() - stat.mtimeMs <= staleMs) {
1218
1239
  return false;
1219
1240
  }
@@ -1228,7 +1249,7 @@ async function isStaleLock(lockPath, staleMs) {
1228
1249
  }
1229
1250
  async function readLockPayload(lockPath) {
1230
1251
  try {
1231
- const raw = await fs15.readFile(lockPath, "utf8");
1252
+ const raw = await fs14.readFile(lockPath, "utf8");
1232
1253
  const parsed = JSON.parse(raw);
1233
1254
  if (!parsed || typeof parsed !== "object") return null;
1234
1255
  return parsed;
@@ -1250,17 +1271,17 @@ function isProcessAlive(pid) {
1250
1271
  }
1251
1272
  }
1252
1273
  async function tryAcquire(lockPath, owner) {
1253
- await fs15.ensureDir(path17.dirname(lockPath));
1274
+ await fs14.ensureDir(path18.dirname(lockPath));
1254
1275
  try {
1255
- const fd = await fs15.open(lockPath, "wx");
1276
+ const fd = await fs14.open(lockPath, "wx");
1256
1277
  const payload = JSON.stringify(
1257
1278
  { pid: process.pid, owner: owner ?? "unknown", createdAt: (/* @__PURE__ */ new Date()).toISOString() },
1258
1279
  null,
1259
1280
  2
1260
1281
  );
1261
- await fs15.writeFile(fd, `${payload}
1282
+ await fs14.writeFile(fd, `${payload}
1262
1283
  `, { encoding: "utf8" });
1263
- await fs15.close(fd);
1284
+ await fs14.close(fd);
1264
1285
  return true;
1265
1286
  } catch (error) {
1266
1287
  if (error.code === "EEXIST") {
@@ -1274,9 +1295,9 @@ async function waitForLockRelease(lockPath, options = {}) {
1274
1295
  const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
1275
1296
  const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
1276
1297
  const startedAt = Date.now();
1277
- while (await fs15.pathExists(lockPath)) {
1298
+ while (await fs14.pathExists(lockPath)) {
1278
1299
  if (await isStaleLock(lockPath, staleMs)) {
1279
- await fs15.remove(lockPath);
1300
+ await fs14.remove(lockPath);
1280
1301
  break;
1281
1302
  }
1282
1303
  if (Date.now() - startedAt > timeoutMs) {
@@ -1294,7 +1315,7 @@ async function withFileLock(lockPath, task, options = {}) {
1294
1315
  const acquired = await tryAcquire(lockPath, options.owner);
1295
1316
  if (acquired) break;
1296
1317
  if (await isStaleLock(lockPath, staleMs)) {
1297
- await fs15.remove(lockPath);
1318
+ await fs14.remove(lockPath);
1298
1319
  continue;
1299
1320
  }
1300
1321
  if (Date.now() - startedAt > timeoutMs) {
@@ -1308,7 +1329,7 @@ async function withFileLock(lockPath, task, options = {}) {
1308
1329
  try {
1309
1330
  return await task();
1310
1331
  } finally {
1311
- await fs15.remove(lockPath).catch(() => {
1332
+ await fs14.remove(lockPath).catch(() => {
1312
1333
  });
1313
1334
  }
1314
1335
  }
@@ -1327,30 +1348,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
1327
1348
  "pr-template.md"
1328
1349
  ];
1329
1350
  var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
1330
- var ENGINE_MANAGED_FEATURE_PATH = path17.join(
1351
+ var ENGINE_MANAGED_FEATURE_PATH = path18.join(
1331
1352
  "features",
1332
1353
  "feature-base"
1333
1354
  );
1334
1355
  async function pruneEngineManagedDocs(docsDir) {
1335
1356
  const removed = [];
1336
1357
  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));
1358
+ const target = path18.join(docsDir, "agents", file);
1359
+ if (await fs14.pathExists(target)) {
1360
+ await fs14.remove(target);
1361
+ removed.push(path18.relative(docsDir, target));
1341
1362
  }
1342
1363
  }
1343
1364
  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));
1365
+ const target = path18.join(docsDir, "agents", dir);
1366
+ if (await fs14.pathExists(target)) {
1367
+ await fs14.remove(target);
1368
+ removed.push(path18.relative(docsDir, target));
1348
1369
  }
1349
1370
  }
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));
1371
+ const featureBasePath = path18.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
1372
+ if (await fs14.pathExists(featureBasePath)) {
1373
+ await fs14.remove(featureBasePath);
1374
+ removed.push(path18.relative(docsDir, featureBasePath));
1354
1375
  }
1355
1376
  return removed;
1356
1377
  }
@@ -1403,7 +1424,7 @@ ${tr(lang2, "cli", "common.canceled")}`)
1403
1424
  }
1404
1425
  async function runInit(options) {
1405
1426
  const cwd = process.cwd();
1406
- const defaultName = path17.basename(cwd);
1427
+ const defaultName = path18.basename(cwd);
1407
1428
  let projectName = options.name || defaultName;
1408
1429
  let projectType = options.type;
1409
1430
  let components = parseComponentsOption(options.components);
@@ -1413,7 +1434,7 @@ async function runInit(options) {
1413
1434
  let pushDocs = typeof options.pushDocs === "boolean" ? options.pushDocs : void 0;
1414
1435
  let docsRemote = options.docsRemote;
1415
1436
  let projectRoot;
1416
- const targetDir = path17.resolve(cwd, options.dir || "./docs");
1437
+ const targetDir = path18.resolve(cwd, options.dir || "./docs");
1417
1438
  const skipPrompts = !!options.yes || !!options.nonInteractive;
1418
1439
  if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
1419
1440
  throw createCliError(
@@ -1731,8 +1752,8 @@ async function runInit(options) {
1731
1752
  await withFileLock(
1732
1753
  initLockPath,
1733
1754
  async () => {
1734
- if (await fs15.pathExists(targetDir)) {
1735
- const files = await fs15.readdir(targetDir);
1755
+ if (await fs14.pathExists(targetDir)) {
1756
+ const files = await fs14.readdir(targetDir);
1736
1757
  if (files.length > 0) {
1737
1758
  if (options.force) {
1738
1759
  } else if (options.nonInteractive) {
@@ -1768,28 +1789,28 @@ async function runInit(options) {
1768
1789
  );
1769
1790
  console.log();
1770
1791
  const templatesDir = getTemplatesDir();
1771
- const commonPath = path17.join(templatesDir, lang, "common");
1792
+ const commonPath = path18.join(templatesDir, lang, "common");
1772
1793
  const templateProjectType = toTemplateProjectType(projectType);
1773
- const typePath = path17.join(templatesDir, lang, templateProjectType);
1774
- if (await fs15.pathExists(commonPath)) {
1794
+ const typePath = path18.join(templatesDir, lang, templateProjectType);
1795
+ if (await fs14.pathExists(commonPath)) {
1775
1796
  await copyTemplates(commonPath, targetDir);
1776
1797
  }
1777
- if (!await fs15.pathExists(typePath)) {
1798
+ if (!await fs14.pathExists(typePath)) {
1778
1799
  throw new Error(
1779
1800
  tr(lang, "cli", "init.error.templateNotFound", { path: typePath })
1780
1801
  );
1781
1802
  }
1782
1803
  await copyTemplates(typePath, targetDir);
1783
1804
  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"));
1805
+ const featuresRoot = path18.join(targetDir, "features");
1806
+ await fs14.remove(path18.join(featuresRoot, "fe"));
1807
+ await fs14.remove(path18.join(featuresRoot, "be"));
1787
1808
  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(
1809
+ const componentDir = path18.join(featuresRoot, component);
1810
+ await fs14.ensureDir(componentDir);
1811
+ const readmePath = path18.join(componentDir, "README.md");
1812
+ if (!await fs14.pathExists(readmePath)) {
1813
+ await fs14.writeFile(
1793
1814
  readmePath,
1794
1815
  `# ${component.toUpperCase()} Features
1795
1816
 
@@ -1838,8 +1859,8 @@ Store ${component} feature specs here.
1838
1859
  config.projectRoot = projectRoot;
1839
1860
  }
1840
1861
  }
1841
- const configPath = path17.join(targetDir, ".lee-spec-kit.json");
1842
- await fs15.writeJson(configPath, config, { spaces: 2 });
1862
+ const configPath = path18.join(targetDir, ".lee-spec-kit.json");
1863
+ await fs14.writeJson(configPath, config, { spaces: 2 });
1843
1864
  console.log(chalk6.green(tr(lang, "cli", "init.log.docsCreated")));
1844
1865
  console.log();
1845
1866
  await initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote);
@@ -1907,7 +1928,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
1907
1928
  console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
1908
1929
  runGit(["init"], cwd);
1909
1930
  }
1910
- const relativePath = path17.relative(cwd, targetDir);
1931
+ const relativePath = path18.relative(cwd, targetDir);
1911
1932
  const stagedBeforeAdd = getCachedStagedFiles(cwd);
1912
1933
  if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
1913
1934
  console.log(
@@ -1964,17 +1985,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
1964
1985
  }
1965
1986
  function getAncestorDirs(startDir) {
1966
1987
  const dirs = [];
1967
- let current = path17.resolve(startDir);
1988
+ let current = path18.resolve(startDir);
1968
1989
  while (true) {
1969
1990
  dirs.push(current);
1970
- const parent = path17.dirname(current);
1991
+ const parent = path18.dirname(current);
1971
1992
  if (parent === current) break;
1972
1993
  current = parent;
1973
1994
  }
1974
1995
  return dirs;
1975
1996
  }
1976
1997
  function hasWorkspaceBoundary(dir) {
1977
- return fs15.existsSync(path17.join(dir, "package.json")) || fs15.existsSync(path17.join(dir, ".git"));
1998
+ return fs14.existsSync(path18.join(dir, "package.json")) || fs14.existsSync(path18.join(dir, ".git"));
1978
1999
  }
1979
2000
  function getSearchBaseDirs(cwd) {
1980
2001
  const ancestors = getAncestorDirs(cwd);
@@ -1987,24 +2008,24 @@ function getSearchBaseDirs(cwd) {
1987
2008
  async function getConfig(cwd) {
1988
2009
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
1989
2010
  const baseDirs = [
1990
- ...explicitDocsDir ? [path17.resolve(explicitDocsDir)] : [],
2011
+ ...explicitDocsDir ? [path18.resolve(explicitDocsDir)] : [],
1991
2012
  ...getSearchBaseDirs(cwd)
1992
2013
  ];
1993
2014
  const visitedBaseDirs = /* @__PURE__ */ new Set();
1994
2015
  const visitedDocsDirs = /* @__PURE__ */ new Set();
1995
2016
  for (const baseDir of baseDirs) {
1996
- const resolvedBaseDir = path17.resolve(baseDir);
2017
+ const resolvedBaseDir = path18.resolve(baseDir);
1997
2018
  if (visitedBaseDirs.has(resolvedBaseDir)) continue;
1998
2019
  visitedBaseDirs.add(resolvedBaseDir);
1999
- const possibleDocsDirs = [path17.join(resolvedBaseDir, "docs"), resolvedBaseDir];
2020
+ const possibleDocsDirs = [path18.join(resolvedBaseDir, "docs"), resolvedBaseDir];
2000
2021
  for (const docsDir of possibleDocsDirs) {
2001
- const resolvedDocsDir = path17.resolve(docsDir);
2022
+ const resolvedDocsDir = path18.resolve(docsDir);
2002
2023
  if (visitedDocsDirs.has(resolvedDocsDir)) continue;
2003
2024
  visitedDocsDirs.add(resolvedDocsDir);
2004
- const configPath = path17.join(resolvedDocsDir, ".lee-spec-kit.json");
2005
- if (await fs15.pathExists(configPath)) {
2025
+ const configPath = path18.join(resolvedDocsDir, ".lee-spec-kit.json");
2026
+ if (await fs14.pathExists(configPath)) {
2006
2027
  try {
2007
- const configFile = await fs15.readJson(configPath);
2028
+ const configFile = await fs14.readJson(configPath);
2008
2029
  const projectType = normalizeProjectType(configFile.projectType);
2009
2030
  const components = resolveProjectComponents(
2010
2031
  projectType,
@@ -2027,22 +2048,22 @@ async function getConfig(cwd) {
2027
2048
  } catch {
2028
2049
  }
2029
2050
  }
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";
2051
+ const agentsPath = path18.join(resolvedDocsDir, "agents");
2052
+ const featuresPath = path18.join(resolvedDocsDir, "features");
2053
+ if (await fs14.pathExists(agentsPath) && await fs14.pathExists(featuresPath)) {
2054
+ const bePath = path18.join(featuresPath, "be");
2055
+ const fePath = path18.join(featuresPath, "fe");
2056
+ const projectType = await fs14.pathExists(bePath) || await fs14.pathExists(fePath) ? "multi" : "single";
2036
2057
  const components = projectType === "multi" ? resolveProjectComponents("multi", ["fe", "be"]) : void 0;
2037
2058
  const langProbeCandidates = [
2038
- path17.join(agentsPath, "custom.md"),
2039
- path17.join(agentsPath, "constitution.md"),
2040
- path17.join(agentsPath, "agents.md")
2059
+ path18.join(agentsPath, "custom.md"),
2060
+ path18.join(agentsPath, "constitution.md"),
2061
+ path18.join(agentsPath, "agents.md")
2041
2062
  ];
2042
2063
  let lang = "en";
2043
2064
  for (const candidate of langProbeCandidates) {
2044
- if (!await fs15.pathExists(candidate)) continue;
2045
- const content = await fs15.readFile(candidate, "utf-8");
2065
+ if (!await fs14.pathExists(candidate)) continue;
2066
+ const content = await fs14.readFile(candidate, "utf-8");
2046
2067
  if (/[가-힣]/.test(content)) {
2047
2068
  lang = "ko";
2048
2069
  break;
@@ -2088,14 +2109,14 @@ function sanitizeTasksForLocal(content, lang) {
2088
2109
  return normalizeTrailingBlankLines(next);
2089
2110
  }
2090
2111
  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");
2112
+ if (!await fs14.pathExists(filePath)) return;
2113
+ const content = await fs14.readFile(filePath, "utf-8");
2114
+ await fs14.writeFile(filePath, transform(content), "utf-8");
2094
2115
  }
2095
2116
  async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
2096
- await patchMarkdownIfExists(path17.join(featureDir, "spec.md"), sanitizeSpecForLocal);
2117
+ await patchMarkdownIfExists(path18.join(featureDir, "spec.md"), sanitizeSpecForLocal);
2097
2118
  await patchMarkdownIfExists(
2098
- path17.join(featureDir, "tasks.md"),
2119
+ path18.join(featureDir, "tasks.md"),
2099
2120
  (content) => sanitizeTasksForLocal(content, lang)
2100
2121
  );
2101
2122
  }
@@ -2245,29 +2266,29 @@ async function runFeature(name, options) {
2245
2266
  }
2246
2267
  let featuresDir;
2247
2268
  if (projectType === "multi") {
2248
- featuresDir = path17.join(docsDir, "features", component);
2269
+ featuresDir = path18.join(docsDir, "features", component);
2249
2270
  } else {
2250
- featuresDir = path17.join(docsDir, "features");
2271
+ featuresDir = path18.join(docsDir, "features");
2251
2272
  }
2252
2273
  const featureFolderName = `${featureId}-${name}`;
2253
- const featureDir = path17.join(featuresDir, featureFolderName);
2254
- if (await fs15.pathExists(featureDir)) {
2274
+ const featureDir = path18.join(featuresDir, featureFolderName);
2275
+ if (await fs14.pathExists(featureDir)) {
2255
2276
  throw createCliError(
2256
2277
  "INVALID_ARGUMENT",
2257
2278
  tr(lang, "cli", "feature.folderExists", { path: featureDir })
2258
2279
  );
2259
2280
  }
2260
- const featureBasePath = path17.join(
2281
+ const featureBasePath = path18.join(
2261
2282
  getTemplatesDir(),
2262
2283
  lang,
2263
2284
  toTemplateProjectType(projectType),
2264
2285
  "features",
2265
2286
  "feature-base"
2266
2287
  );
2267
- if (!await fs15.pathExists(featureBasePath)) {
2288
+ if (!await fs14.pathExists(featureBasePath)) {
2268
2289
  throw createCliError("DOCS_NOT_FOUND", tr(lang, "cli", "feature.baseNotFound"));
2269
2290
  }
2270
- await fs15.copy(featureBasePath, featureDir);
2291
+ await fs14.copy(featureBasePath, featureDir);
2271
2292
  const idNumber = featureId.replace("F", "");
2272
2293
  const repoName = projectType === "multi" ? `{{projectName}}-${component}` : "{{projectName}}";
2273
2294
  const replacements = {
@@ -2314,7 +2335,7 @@ async function runFeature(name, options) {
2314
2335
  featureName: name,
2315
2336
  component: projectType === "multi" ? component : void 0,
2316
2337
  featurePath: featureDir,
2317
- featurePathFromDocs: path17.relative(docsDir, featureDir)
2338
+ featurePathFromDocs: path18.relative(docsDir, featureDir)
2318
2339
  };
2319
2340
  },
2320
2341
  { owner: "feature" }
@@ -2326,9 +2347,9 @@ function sleep2(ms) {
2326
2347
  async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2327
2348
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
2328
2349
  const candidates = [
2329
- ...explicitDocsDir ? [path17.resolve(explicitDocsDir)] : [],
2330
- path17.resolve(cwd, "docs"),
2331
- path17.resolve(cwd)
2350
+ ...explicitDocsDir ? [path18.resolve(explicitDocsDir)] : [],
2351
+ path18.resolve(cwd, "docs"),
2352
+ path18.resolve(cwd)
2332
2353
  ];
2333
2354
  const endAt = Date.now() + timeoutMs;
2334
2355
  while (Date.now() < endAt) {
@@ -2339,7 +2360,7 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2339
2360
  const initLockPath = getInitLockPath(dir);
2340
2361
  const docsLockPath = getDocsLockPath(dir);
2341
2362
  for (const lockPath of [initLockPath, docsLockPath]) {
2342
- if (await fs15.pathExists(lockPath)) {
2363
+ if (await fs14.pathExists(lockPath)) {
2343
2364
  sawLock = true;
2344
2365
  await waitForLockRelease(lockPath, {
2345
2366
  timeoutMs: Math.max(200, endAt - Date.now()),
@@ -2355,17 +2376,17 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
2355
2376
  return getConfig(cwd);
2356
2377
  }
2357
2378
  async function getNextFeatureId(docsDir, projectType, components) {
2358
- const featuresDir = path17.join(docsDir, "features");
2379
+ const featuresDir = path18.join(docsDir, "features");
2359
2380
  let max = 0;
2360
2381
  const scanDirs = [];
2361
2382
  if (projectType === "multi") {
2362
- scanDirs.push(...components.map((component) => path17.join(featuresDir, component)));
2383
+ scanDirs.push(...components.map((component) => path18.join(featuresDir, component)));
2363
2384
  } else {
2364
2385
  scanDirs.push(featuresDir);
2365
2386
  }
2366
2387
  for (const dir of scanDirs) {
2367
- if (!await fs15.pathExists(dir)) continue;
2368
- const entries = await fs15.readdir(dir, { withFileTypes: true });
2388
+ if (!await fs14.pathExists(dir)) continue;
2389
+ const entries = await fs14.readdir(dir, { withFileTypes: true });
2369
2390
  for (const entry of entries) {
2370
2391
  if (!entry.isDirectory()) continue;
2371
2392
  const match = entry.name.match(/^F(\d+)-/);
@@ -2473,6 +2494,21 @@ function formatSkillList(skills) {
2473
2494
  function getFindingsPolicyText(lang, blockOnFindings) {
2474
2495
  return blockOnFindings ? tr(lang, "messages", "prePrReviewFindingsBlock") : tr(lang, "messages", "prePrReviewFindingsWarn");
2475
2496
  }
2497
+ function normalizeCommitTopicText(value) {
2498
+ return value.replace(/\s+/g, " ").trim();
2499
+ }
2500
+ function toShellSafeCommitTopic(value) {
2501
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
2502
+ }
2503
+ function resolveProjectCommitTopic(feature) {
2504
+ const raw = feature.activeTask?.title || feature.lastDoneTask?.title || feature.nextTodoTask?.title || feature.folderName;
2505
+ const withoutTaskId = normalizeCommitTopicText(raw).replace(
2506
+ /^T-[A-Za-z0-9-]+\s+/,
2507
+ ""
2508
+ );
2509
+ const topic = withoutTaskId || normalizeCommitTopicText(feature.folderName);
2510
+ return toShellSafeCommitTopic(topic);
2511
+ }
2476
2512
  function getStepDefinitions(lang, workflow) {
2477
2513
  const workflowPolicy = resolveWorkflowPolicy(workflow);
2478
2514
  const prePrReviewPolicy = resolvePrePrReviewPolicy(workflow);
@@ -2796,10 +2832,12 @@ function getStepDefinitions(lang, workflow) {
2796
2832
  cmd: f.issueNumber ? tr(lang, "messages", "projectCommitIssueUpdate", {
2797
2833
  projectGitCwd: f.git.projectGitCwd,
2798
2834
  issueNumber: f.issueNumber,
2799
- folderName: f.folderName
2835
+ folderName: f.folderName,
2836
+ commitTopic: resolveProjectCommitTopic(f)
2800
2837
  }) : tr(lang, "messages", "projectCommitUpdate", {
2801
2838
  projectGitCwd: f.git.projectGitCwd,
2802
- folderName: f.folderName
2839
+ folderName: f.folderName,
2840
+ commitTopic: resolveProjectCommitTopic(f)
2803
2841
  })
2804
2842
  }
2805
2843
  ];
@@ -2880,10 +2918,12 @@ function getStepDefinitions(lang, workflow) {
2880
2918
  cmd: f.issueNumber ? tr(lang, "messages", "projectCommitIssueUpdate", {
2881
2919
  projectGitCwd: f.git.projectGitCwd,
2882
2920
  issueNumber: f.issueNumber,
2883
- folderName: f.folderName
2921
+ folderName: f.folderName,
2922
+ commitTopic: resolveProjectCommitTopic(f)
2884
2923
  }) : tr(lang, "messages", "projectCommitUpdate", {
2885
2924
  projectGitCwd: f.git.projectGitCwd,
2886
- folderName: f.folderName
2925
+ folderName: f.folderName,
2926
+ commitTopic: resolveProjectCommitTopic(f)
2887
2927
  })
2888
2928
  }
2889
2929
  ];
@@ -3133,6 +3173,59 @@ function getGitStatusPorcelain(cwd, relativePaths) {
3133
3173
  return void 0;
3134
3174
  }
3135
3175
  }
3176
+ function normalizeInputPath(value) {
3177
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
3178
+ }
3179
+ function toUniqueNormalizedPaths(relativePaths) {
3180
+ const seen = /* @__PURE__ */ new Set();
3181
+ const out = [];
3182
+ for (const value of relativePaths) {
3183
+ const normalized = normalizeInputPath(value);
3184
+ if (!normalized) continue;
3185
+ if (seen.has(normalized)) continue;
3186
+ seen.add(normalized);
3187
+ out.push(normalized);
3188
+ }
3189
+ return out;
3190
+ }
3191
+ function getTrackedGitPaths(cwd, relativePaths) {
3192
+ const inputs = toUniqueNormalizedPaths(relativePaths);
3193
+ if (inputs.length === 0) return /* @__PURE__ */ new Set();
3194
+ try {
3195
+ const out = execFileSync("git", ["ls-files", "--", ...inputs], {
3196
+ cwd,
3197
+ encoding: "utf-8",
3198
+ stdio: ["ignore", "pipe", "pipe"]
3199
+ });
3200
+ return new Set(
3201
+ out.split("\n").map((line) => normalizeInputPath(line)).filter(Boolean)
3202
+ );
3203
+ } catch {
3204
+ return void 0;
3205
+ }
3206
+ }
3207
+ function getIgnoredGitPaths(cwd, relativePaths) {
3208
+ const inputs = toUniqueNormalizedPaths(relativePaths);
3209
+ if (inputs.length === 0) return /* @__PURE__ */ new Set();
3210
+ try {
3211
+ const out = execFileSync("git", ["check-ignore", "--stdin"], {
3212
+ cwd,
3213
+ encoding: "utf-8",
3214
+ input: `${inputs.join("\n")}
3215
+ `,
3216
+ stdio: ["pipe", "pipe", "pipe"]
3217
+ });
3218
+ return new Set(
3219
+ out.split("\n").map((line) => normalizeInputPath(line)).filter(Boolean)
3220
+ );
3221
+ } catch (error) {
3222
+ if (error && typeof error === "object" && "status" in error) {
3223
+ const status = error.status;
3224
+ if (status === 1) return /* @__PURE__ */ new Set();
3225
+ }
3226
+ return void 0;
3227
+ }
3228
+ }
3136
3229
  function getLastCommitForPath(cwd, relativePath) {
3137
3230
  try {
3138
3231
  const out = execSync(`git rev-list -n 1 HEAD -- "${relativePath}"`, {
@@ -3280,13 +3373,13 @@ function parsePrLink(value) {
3280
3373
  return trimmed;
3281
3374
  }
3282
3375
  function normalizeGitPath(value) {
3283
- return value.split(path17.sep).join("/");
3376
+ return value.split(path18.sep).join("/");
3284
3377
  }
3285
3378
  function resolveProjectStatusPaths(projectGitCwd, docsDir) {
3286
- const relativeDocsDir = path17.relative(projectGitCwd, docsDir);
3379
+ const relativeDocsDir = path18.relative(projectGitCwd, docsDir);
3287
3380
  if (!relativeDocsDir) return [];
3288
- if (path17.isAbsolute(relativeDocsDir)) return [];
3289
- if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path17.sep}`)) {
3381
+ if (path18.isAbsolute(relativeDocsDir)) return [];
3382
+ if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path18.sep}`)) {
3290
3383
  return [];
3291
3384
  }
3292
3385
  const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(/\/+$/, "");
@@ -3305,6 +3398,8 @@ function uniqueNormalizedPaths(values) {
3305
3398
  }
3306
3399
  return out;
3307
3400
  }
3401
+ var PROJECT_DIRTY_STATUS_CACHE = /* @__PURE__ */ new Map();
3402
+ var COMPONENT_STATUS_PATH_CACHE = /* @__PURE__ */ new Map();
3308
3403
  async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
3309
3404
  const configured = workflow?.componentPaths?.[component];
3310
3405
  const configuredCandidates = Array.isArray(configured) ? configured.map((value) => String(value).trim()).filter(Boolean) : [];
@@ -3318,24 +3413,33 @@ async function resolveComponentStatusPaths(projectGitCwd, component, workflow) {
3318
3413
  const normalizedCandidates = uniqueNormalizedPaths(
3319
3414
  candidates.map((candidate) => {
3320
3415
  if (!candidate) return "";
3321
- if (!path17.isAbsolute(candidate)) return candidate;
3322
- const relative = path17.relative(projectGitCwd, candidate);
3416
+ if (!path18.isAbsolute(candidate)) return candidate;
3417
+ const relative = path18.relative(projectGitCwd, candidate);
3323
3418
  if (!relative) return "";
3324
- if (relative === ".." || relative.startsWith(`..${path17.sep}`)) return "";
3419
+ if (relative === ".." || relative.startsWith(`..${path18.sep}`)) return "";
3325
3420
  return relative;
3326
3421
  }).filter(Boolean)
3327
3422
  );
3423
+ const cacheKey = JSON.stringify({
3424
+ projectGitCwd,
3425
+ component,
3426
+ normalizedCandidates
3427
+ });
3428
+ const cached = COMPONENT_STATUS_PATH_CACHE.get(cacheKey);
3429
+ if (cached) return [...cached];
3328
3430
  const existing = [];
3329
3431
  for (const candidate of normalizedCandidates) {
3330
- if (await fs15.pathExists(path17.join(projectGitCwd, candidate))) {
3432
+ if (await fs14.pathExists(path18.join(projectGitCwd, candidate))) {
3331
3433
  existing.push(candidate);
3332
3434
  }
3333
3435
  }
3436
+ COMPONENT_STATUS_PATH_CACHE.set(cacheKey, [...existing]);
3334
3437
  return existing;
3335
3438
  }
3336
3439
  function parseTasks(content) {
3337
3440
  const summary = { total: 0, todo: 0, doing: 0, done: 0 };
3338
3441
  let activeTask;
3442
+ let lastDoneTask;
3339
3443
  let nextTodoTask;
3340
3444
  const lines = content.split("\n");
3341
3445
  let inCodeBlock = false;
@@ -3356,11 +3460,14 @@ function parseTasks(content) {
3356
3460
  if (!activeTask && (status === "DOING" || status === "REVIEW")) {
3357
3461
  activeTask = { status, title };
3358
3462
  }
3463
+ if (status === "DONE") {
3464
+ lastDoneTask = { status: "DONE", title };
3465
+ }
3359
3466
  if (!nextTodoTask && status === "TODO") {
3360
3467
  nextTodoTask = { status: "TODO", title };
3361
3468
  }
3362
3469
  }
3363
- return { summary, activeTask, nextTodoTask };
3470
+ return { summary, activeTask, lastDoneTask, nextTodoTask };
3364
3471
  }
3365
3472
  function parseCompletionChecklist(content) {
3366
3473
  const lines = content.split("\n");
@@ -3390,33 +3497,34 @@ async function parseFeature(featurePath, type, context, options) {
3390
3497
  const lang = options.lang;
3391
3498
  const workflowPolicy = resolveWorkflowPolicy(options.workflow);
3392
3499
  const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
3393
- const folderName = path17.basename(featurePath);
3500
+ const folderName = path18.basename(featurePath);
3394
3501
  const match = folderName.match(/^(F\d+)-(.+)$/);
3395
3502
  const id = match?.[1];
3396
3503
  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");
3504
+ const specPath = path18.join(featurePath, "spec.md");
3505
+ const planPath = path18.join(featurePath, "plan.md");
3506
+ const tasksPath = path18.join(featurePath, "tasks.md");
3400
3507
  let specStatus;
3401
3508
  let issueNumber;
3402
- const specExists = await fs15.pathExists(specPath);
3509
+ const specExists = await fs14.pathExists(specPath);
3403
3510
  if (specExists) {
3404
- const content = await fs15.readFile(specPath, "utf-8");
3511
+ const content = await fs14.readFile(specPath, "utf-8");
3405
3512
  const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
3406
3513
  specStatus = parseDocStatus(statusValue);
3407
3514
  const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
3408
3515
  issueNumber = parseIssueNumber(issueValue);
3409
3516
  }
3410
3517
  let planStatus;
3411
- const planExists = await fs15.pathExists(planPath);
3518
+ const planExists = await fs14.pathExists(planPath);
3412
3519
  if (planExists) {
3413
- const content = await fs15.readFile(planPath, "utf-8");
3520
+ const content = await fs14.readFile(planPath, "utf-8");
3414
3521
  const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
3415
3522
  planStatus = parseDocStatus(statusValue);
3416
3523
  }
3417
- const tasksExists = await fs15.pathExists(tasksPath);
3524
+ const tasksExists = await fs14.pathExists(tasksPath);
3418
3525
  const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
3419
3526
  let activeTask;
3527
+ let lastDoneTask;
3420
3528
  let nextTodoTask;
3421
3529
  let tasksDocStatus;
3422
3530
  let tasksDocStatusFieldExists = false;
@@ -3428,13 +3536,19 @@ async function parseFeature(featurePath, type, context, options) {
3428
3536
  let prStatusFieldExists = false;
3429
3537
  let prePrReviewFieldExists = false;
3430
3538
  if (tasksExists) {
3431
- const content = await fs15.readFile(tasksPath, "utf-8");
3432
- const { summary, activeTask: active, nextTodoTask: nextTodo } = parseTasks(content);
3539
+ const content = await fs14.readFile(tasksPath, "utf-8");
3540
+ const {
3541
+ summary,
3542
+ activeTask: active,
3543
+ lastDoneTask: lastDone,
3544
+ nextTodoTask: nextTodo
3545
+ } = parseTasks(content);
3433
3546
  tasksSummary.total = summary.total;
3434
3547
  tasksSummary.todo = summary.todo;
3435
3548
  tasksSummary.doing = summary.doing;
3436
3549
  tasksSummary.done = summary.done;
3437
3550
  activeTask = active;
3551
+ lastDoneTask = lastDone;
3438
3552
  nextTodoTask = nextTodo;
3439
3553
  completionChecklist = parseCompletionChecklist(content);
3440
3554
  if (!issueNumber) {
@@ -3470,44 +3584,77 @@ async function parseFeature(featurePath, type, context, options) {
3470
3584
  slug,
3471
3585
  folderName
3472
3586
  );
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);
3587
+ const relativeFeaturePathFromDocs = path18.relative(context.docsDir, featurePath);
3588
+ const normalizedFeaturePathFromDocs = normalizeGitPath(relativeFeaturePathFromDocs);
3589
+ const docsPathIgnored = typeof context.docsPathIgnored === "boolean" ? context.docsPathIgnored : isGitPathIgnored(context.docsGitCwd, normalizedFeaturePathFromDocs);
3590
+ let docsHasUncommittedChanges = typeof context.docsHasUncommittedChanges === "boolean" ? context.docsHasUncommittedChanges : false;
3591
+ let docsEverCommitted = typeof context.docsEverCommitted === "boolean" ? context.docsEverCommitted : false;
3592
+ let docsGitUnavailable = !!context.docsGitUnavailable;
3593
+ if (typeof context.docsHasUncommittedChanges !== "boolean") {
3594
+ const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [normalizedFeaturePathFromDocs]);
3595
+ if (docsStatus === void 0) {
3596
+ docsGitUnavailable = true;
3597
+ docsHasUncommittedChanges = true;
3490
3598
  } else {
3491
- projectStatusPaths = resolveProjectStatusPaths(
3599
+ docsHasUncommittedChanges = docsStatus.trim().length > 0;
3600
+ }
3601
+ }
3602
+ if (typeof context.docsEverCommitted !== "boolean") {
3603
+ const docsLastCommit = getLastCommitForPath(
3604
+ context.docsGitCwd,
3605
+ normalizedFeaturePathFromDocs
3606
+ );
3607
+ docsEverCommitted = !!docsLastCommit;
3608
+ }
3609
+ let projectHasUncommittedChanges = typeof context.projectHasUncommittedChanges === "boolean" ? context.projectHasUncommittedChanges : false;
3610
+ let projectStatusUnavailable = !!context.projectStatusUnavailable;
3611
+ if (typeof context.projectHasUncommittedChanges !== "boolean" && context.projectGitCwd) {
3612
+ const dirtyScopePolicy = resolveCodeDirtyScopePolicy(options.workflow, options.projectType);
3613
+ const projectCacheKey = JSON.stringify({
3614
+ projectGitCwd: context.projectGitCwd,
3615
+ docsDir: context.docsDir,
3616
+ type,
3617
+ dirtyScopePolicy,
3618
+ componentPaths: options.workflow?.componentPaths?.[type] || []
3619
+ });
3620
+ const cachedStatus = PROJECT_DIRTY_STATUS_CACHE.get(projectCacheKey);
3621
+ if (cachedStatus) {
3622
+ projectHasUncommittedChanges = cachedStatus.hasUncommittedChanges;
3623
+ projectStatusUnavailable = cachedStatus.statusUnavailable;
3624
+ } else {
3625
+ let projectStatusPaths = [];
3626
+ if (dirtyScopePolicy === "component" && type !== "single") {
3627
+ const componentStatusPaths = await resolveComponentStatusPaths(
3628
+ context.projectGitCwd,
3629
+ type,
3630
+ options.workflow
3631
+ );
3632
+ projectStatusPaths = componentStatusPaths.length > 0 ? componentStatusPaths : resolveProjectStatusPaths(context.projectGitCwd, context.docsDir);
3633
+ } else {
3634
+ projectStatusPaths = resolveProjectStatusPaths(
3635
+ context.projectGitCwd,
3636
+ context.docsDir
3637
+ );
3638
+ }
3639
+ const projectStatus = getGitStatusPorcelain(
3492
3640
  context.projectGitCwd,
3493
- context.docsDir
3641
+ projectStatusPaths
3494
3642
  );
3643
+ projectStatusUnavailable = projectStatus === void 0;
3644
+ projectHasUncommittedChanges = projectStatus === void 0 ? false : projectStatus.trim().length > 0;
3645
+ PROJECT_DIRTY_STATUS_CACHE.set(projectCacheKey, {
3646
+ hasUncommittedChanges: projectHasUncommittedChanges,
3647
+ statusUnavailable: projectStatusUnavailable
3648
+ });
3495
3649
  }
3496
3650
  }
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) {
3651
+ if (docsGitUnavailable) {
3505
3652
  warnings.push(tr(lang, "warnings", "docsGitUnavailable"));
3506
3653
  }
3507
3654
  if (docsPathIgnored === true) {
3508
3655
  warnings.push(
3509
3656
  tr(lang, "warnings", "docsPathIgnored", {
3510
- path: relativeFeaturePathFromDocs
3657
+ path: normalizedFeaturePathFromDocs
3511
3658
  })
3512
3659
  );
3513
3660
  }
@@ -3575,6 +3722,7 @@ async function parseFeature(featurePath, type, context, options) {
3575
3722
  tasksDocStatus,
3576
3723
  tasks: tasksSummary,
3577
3724
  activeTask,
3725
+ lastDoneTask,
3578
3726
  nextTodoTask,
3579
3727
  completionChecklist,
3580
3728
  prePrReview: {
@@ -3612,90 +3760,181 @@ async function parseFeature(featurePath, type, context, options) {
3612
3760
  );
3613
3761
  return { ...featureState, currentStep, actions, nextAction, warnings };
3614
3762
  }
3763
+ function normalizeRelPath(value) {
3764
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
3765
+ }
3766
+ function parsePorcelainChangedPaths(porcelain) {
3767
+ const changed = [];
3768
+ for (const rawLine of porcelain.split("\n")) {
3769
+ if (!rawLine.trim()) continue;
3770
+ const payload = rawLine.slice(3).trim();
3771
+ if (!payload) continue;
3772
+ const pathCandidate = payload.includes(" -> ") ? payload.split(" -> ").at(-1) || "" : payload;
3773
+ const normalized = normalizeRelPath(pathCandidate.replace(/^"+|"+$/g, ""));
3774
+ if (!normalized) continue;
3775
+ changed.push(normalized);
3776
+ }
3777
+ return changed;
3778
+ }
3779
+ function findFeaturePathPrefix(normalizedPath, relativeFeaturePaths) {
3780
+ for (const featurePath of relativeFeaturePaths) {
3781
+ if (normalizedPath === featurePath) return featurePath;
3782
+ if (normalizedPath.startsWith(`${featurePath}/`)) return featurePath;
3783
+ const nestedPrefix = `/${featurePath}`;
3784
+ if (normalizedPath.endsWith(nestedPrefix)) return featurePath;
3785
+ if (normalizedPath.includes(`${nestedPrefix}/`)) return featurePath;
3786
+ }
3787
+ return void 0;
3788
+ }
3789
+ function buildDefaultDocsFeatureGitMeta(relativeFeaturePaths) {
3790
+ const map = /* @__PURE__ */ new Map();
3791
+ for (const featurePath of relativeFeaturePaths) {
3792
+ map.set(featurePath, {
3793
+ docsPathIgnored: false,
3794
+ docsHasUncommittedChanges: false,
3795
+ docsEverCommitted: false,
3796
+ docsGitUnavailable: false
3797
+ });
3798
+ }
3799
+ return map;
3800
+ }
3801
+ function buildDocsFeatureGitMeta(docsGitCwd, relativeFeaturePaths) {
3802
+ const normalizedFeaturePaths = relativeFeaturePaths.map(
3803
+ (value) => normalizeRelPath(value)
3804
+ );
3805
+ const map = buildDefaultDocsFeatureGitMeta(normalizedFeaturePaths);
3806
+ if (normalizedFeaturePaths.length === 0) return map;
3807
+ const docsStatus = getGitStatusPorcelain(docsGitCwd, normalizedFeaturePaths);
3808
+ if (docsStatus === void 0) {
3809
+ for (const featurePath of normalizedFeaturePaths) {
3810
+ const current = map.get(featurePath);
3811
+ if (!current) continue;
3812
+ current.docsGitUnavailable = true;
3813
+ current.docsHasUncommittedChanges = true;
3814
+ }
3815
+ } else {
3816
+ const changedPaths = parsePorcelainChangedPaths(docsStatus);
3817
+ for (const changedPath of changedPaths) {
3818
+ const featurePath = findFeaturePathPrefix(changedPath, normalizedFeaturePaths);
3819
+ if (!featurePath) continue;
3820
+ const current = map.get(featurePath);
3821
+ if (!current) continue;
3822
+ current.docsHasUncommittedChanges = true;
3823
+ }
3824
+ }
3825
+ const trackedPaths = getTrackedGitPaths(docsGitCwd, normalizedFeaturePaths);
3826
+ if (trackedPaths) {
3827
+ for (const trackedPath of trackedPaths) {
3828
+ const featurePath = findFeaturePathPrefix(
3829
+ normalizeRelPath(trackedPath),
3830
+ normalizedFeaturePaths
3831
+ );
3832
+ if (!featurePath) continue;
3833
+ const current = map.get(featurePath);
3834
+ if (!current) continue;
3835
+ current.docsEverCommitted = true;
3836
+ }
3837
+ }
3838
+ const ignoredPaths = getIgnoredGitPaths(docsGitCwd, normalizedFeaturePaths);
3839
+ if (ignoredPaths) {
3840
+ for (const ignoredPath of ignoredPaths) {
3841
+ const featurePath = findFeaturePathPrefix(
3842
+ normalizeRelPath(ignoredPath),
3843
+ normalizedFeaturePaths
3844
+ );
3845
+ if (!featurePath) continue;
3846
+ const current = map.get(featurePath);
3847
+ if (!current) continue;
3848
+ current.docsPathIgnored = true;
3849
+ }
3850
+ }
3851
+ return map;
3852
+ }
3615
3853
  async function scanFeatures(config) {
3616
3854
  const features = [];
3617
3855
  const warnings = [];
3618
3856
  const stepDefinitions = getStepDefinitions(config.lang, config.workflow);
3619
3857
  const docsBranch = getCurrentBranch(config.docsDir);
3620
3858
  const projectBranches = {};
3859
+ const projectGitCwds = {};
3621
3860
  let singleProject;
3622
3861
  if (config.projectType === "single") {
3623
3862
  singleProject = resolveProjectGitCwd(config, "single", config.lang);
3624
3863
  if (singleProject.warning) warnings.push(singleProject.warning);
3625
3864
  projectBranches.single = singleProject.cwd ? getCurrentBranch(singleProject.cwd) : "";
3865
+ projectGitCwds.single = singleProject.cwd ?? void 0;
3626
3866
  } else {
3627
3867
  const components = resolveProjectComponents(config.projectType, config.components);
3628
3868
  for (const component of components) {
3629
3869
  const project = resolveProjectGitCwd(config, component, config.lang);
3630
3870
  if (project.warning) warnings.push(project.warning);
3631
3871
  projectBranches[component] = project.cwd ? getCurrentBranch(project.cwd) : "";
3872
+ projectGitCwds[component] = project.cwd ?? void 0;
3632
3873
  }
3633
3874
  }
3875
+ const allFeatureDirs = [];
3876
+ const componentFeatureDirs = /* @__PURE__ */ new Map();
3634
3877
  if (config.projectType === "single") {
3635
3878
  const featureDirs = await glob("features/*/", {
3636
3879
  cwd: config.docsDir,
3637
3880
  absolute: true,
3638
3881
  ignore: ["**/feature-base/**"]
3639
3882
  });
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
- }
3883
+ componentFeatureDirs.set("single", featureDirs);
3884
+ allFeatureDirs.push(...featureDirs);
3665
3885
  } else {
3666
3886
  const components = resolveProjectComponents(config.projectType, config.components);
3667
3887
  for (const component of components) {
3668
- const project = resolveProjectGitCwd(config, component, config.lang);
3669
3888
  const componentDirs = await glob(`features/${component}/*/`, {
3670
3889
  cwd: config.docsDir,
3671
3890
  absolute: true
3672
3891
  });
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
- }
3892
+ componentFeatureDirs.set(component, componentDirs);
3893
+ allFeatureDirs.push(...componentDirs);
3697
3894
  }
3698
3895
  }
3896
+ const relativeFeaturePaths = allFeatureDirs.map(
3897
+ (dir) => normalizeRelPath(path18.relative(config.docsDir, dir))
3898
+ );
3899
+ const docsGitMeta = buildDocsFeatureGitMeta(config.docsDir, relativeFeaturePaths);
3900
+ const parseTargets = config.projectType === "single" ? [{ type: "single", dirs: componentFeatureDirs.get("single") || [] }] : resolveProjectComponents(config.projectType, config.components).map((component) => ({
3901
+ type: component,
3902
+ dirs: componentFeatureDirs.get(component) || []
3903
+ }));
3904
+ for (const target of parseTargets) {
3905
+ const parsed = await Promise.all(
3906
+ target.dirs.map(async (dir) => {
3907
+ const relativeFeaturePathFromDocs = normalizeRelPath(
3908
+ path18.relative(config.docsDir, dir)
3909
+ );
3910
+ const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
3911
+ return parseFeature(
3912
+ dir,
3913
+ target.type,
3914
+ {
3915
+ projectBranch: projectBranches[target.type] || "",
3916
+ docsBranch,
3917
+ docsGitCwd: config.docsDir,
3918
+ projectGitCwd: projectGitCwds[target.type],
3919
+ docsDir: config.docsDir,
3920
+ projectBranchAvailable: Boolean(projectGitCwds[target.type]),
3921
+ docsPathIgnored: docsMeta?.docsPathIgnored,
3922
+ docsHasUncommittedChanges: docsMeta?.docsHasUncommittedChanges,
3923
+ docsEverCommitted: docsMeta?.docsEverCommitted,
3924
+ docsGitUnavailable: docsMeta?.docsGitUnavailable
3925
+ },
3926
+ {
3927
+ lang: config.lang,
3928
+ stepDefinitions,
3929
+ approval: config.approval,
3930
+ workflow: config.workflow,
3931
+ projectType: config.projectType
3932
+ }
3933
+ );
3934
+ })
3935
+ );
3936
+ features.push(...parsed);
3937
+ }
3699
3938
  return {
3700
3939
  features,
3701
3940
  branches: {
@@ -3735,13 +3974,13 @@ async function runStatus(options) {
3735
3974
  );
3736
3975
  }
3737
3976
  const { docsDir, projectType, projectName, lang } = config;
3738
- const featuresDir = path17.join(docsDir, "features");
3977
+ const featuresDir = path18.join(docsDir, "features");
3739
3978
  const scan = await scanFeatures(config);
3740
3979
  const features = [];
3741
3980
  const idMap = /* @__PURE__ */ new Map();
3742
3981
  for (const f of scan.features) {
3743
3982
  const id = f.id || "UNKNOWN";
3744
- const relPath = path17.relative(docsDir, f.path);
3983
+ const relPath = path18.relative(docsDir, f.path);
3745
3984
  if (!idMap.has(id)) idMap.set(id, []);
3746
3985
  idMap.get(id).push(relPath);
3747
3986
  if (!f.docs.specExists || !f.docs.tasksExists) continue;
@@ -3822,7 +4061,7 @@ async function runStatus(options) {
3822
4061
  }
3823
4062
  console.log();
3824
4063
  if (options.write) {
3825
- const outputPath = path17.join(featuresDir, "status.md");
4064
+ const outputPath = path18.join(featuresDir, "status.md");
3826
4065
  const date = getLocalDateString();
3827
4066
  const content = [
3828
4067
  "# Feature Status",
@@ -3837,7 +4076,7 @@ async function runStatus(options) {
3837
4076
  ),
3838
4077
  ""
3839
4078
  ].join("\n");
3840
- await fs15.writeFile(outputPath, content, "utf-8");
4079
+ await fs14.writeFile(outputPath, content, "utf-8");
3841
4080
  console.log(
3842
4081
  chalk6.green(
3843
4082
  tr(lang, "cli", "status.wrote", { path: outputPath })
@@ -3850,9 +4089,9 @@ function escapeRegExp2(value) {
3850
4089
  }
3851
4090
  async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderName) {
3852
4091
  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");
4092
+ const specPath = path18.join(featureDir, "spec.md");
4093
+ if (!await fs14.pathExists(specPath)) return fallbackSlug;
4094
+ const content = await fs14.readFile(specPath, "utf-8");
3856
4095
  const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
3857
4096
  for (const key of keys) {
3858
4097
  const regex = new RegExp(
@@ -3931,17 +4170,17 @@ async function runUpdate(options) {
3931
4170
  console.log(chalk6.blue(tr(lang, "cli", "update.updatingAgents")));
3932
4171
  }
3933
4172
  if (agentsMode === "all") {
3934
- const commonAgentsBase = path17.join(templatesDir, lang, "common", "agents");
3935
- const typeAgentsBase = path17.join(
4173
+ const commonAgentsBase = path18.join(templatesDir, lang, "common", "agents");
4174
+ const typeAgentsBase = path18.join(
3936
4175
  templatesDir,
3937
4176
  lang,
3938
4177
  toTemplateProjectType(projectType),
3939
4178
  "agents"
3940
4179
  );
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;
4180
+ const targetAgentsBase = path18.join(docsDir, "agents");
4181
+ const commonAgents = agentsMode === "skills" ? path18.join(commonAgentsBase, "skills") : commonAgentsBase;
4182
+ const typeAgents = agentsMode === "skills" ? path18.join(typeAgentsBase, "skills") : typeAgentsBase;
4183
+ const targetAgents = agentsMode === "skills" ? path18.join(targetAgentsBase, "skills") : targetAgentsBase;
3945
4184
  const featurePath = projectType === "multi" ? isDefaultFullstackComponents(config.components || []) ? "docs/features/{be|fe}" : "docs/features/{component}" : "docs/features";
3946
4185
  const projectName = config.projectName ?? "{{projectName}}";
3947
4186
  const commonReplacements = {
@@ -3951,7 +4190,7 @@ async function runUpdate(options) {
3951
4190
  const typeReplacements = {
3952
4191
  "{{projectName}}": projectName
3953
4192
  };
3954
- if (await fs15.pathExists(commonAgents)) {
4193
+ if (await fs14.pathExists(commonAgents)) {
3955
4194
  const count = await updateFolder(
3956
4195
  commonAgents,
3957
4196
  targetAgents,
@@ -3969,7 +4208,7 @@ async function runUpdate(options) {
3969
4208
  );
3970
4209
  updatedCount += count;
3971
4210
  }
3972
- if (await fs15.pathExists(typeAgents)) {
4211
+ if (await fs14.pathExists(typeAgents)) {
3973
4212
  const count = await updateFolder(
3974
4213
  typeAgents,
3975
4214
  targetAgents,
@@ -4019,24 +4258,24 @@ async function runUpdate(options) {
4019
4258
  async function updateFolder(sourceDir, targetDir, force, replacements, lang = DEFAULT_LANG, options = {}) {
4020
4259
  const protectedFiles = options.protectedFiles ?? /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
4021
4260
  const skipDirectories = options.skipDirectories ?? /* @__PURE__ */ new Set();
4022
- await fs15.ensureDir(targetDir);
4023
- const files = await fs15.readdir(sourceDir);
4261
+ await fs14.ensureDir(targetDir);
4262
+ const files = await fs14.readdir(sourceDir);
4024
4263
  let updatedCount = 0;
4025
4264
  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);
4265
+ const sourcePath = path18.join(sourceDir, file);
4266
+ const targetPath = path18.join(targetDir, file);
4267
+ const stat = await fs14.stat(sourcePath);
4029
4268
  if (stat.isFile()) {
4030
4269
  if (protectedFiles.has(file)) {
4031
4270
  continue;
4032
4271
  }
4033
- let sourceContent = await fs15.readFile(sourcePath, "utf-8");
4272
+ let sourceContent = await fs14.readFile(sourcePath, "utf-8");
4034
4273
  if (replacements) {
4035
4274
  sourceContent = applyReplacements(sourceContent, replacements);
4036
4275
  }
4037
4276
  let shouldUpdate = true;
4038
- if (await fs15.pathExists(targetPath)) {
4039
- const targetContent = await fs15.readFile(targetPath, "utf-8");
4277
+ if (await fs14.pathExists(targetPath)) {
4278
+ const targetContent = await fs14.readFile(targetPath, "utf-8");
4040
4279
  if (sourceContent === targetContent) {
4041
4280
  continue;
4042
4281
  }
@@ -4050,7 +4289,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
4050
4289
  }
4051
4290
  }
4052
4291
  if (shouldUpdate) {
4053
- await fs15.writeFile(targetPath, sourceContent);
4292
+ await fs14.writeFile(targetPath, sourceContent);
4054
4293
  console.log(
4055
4294
  chalk6.gray(` \u{1F4C4} ${tr(lang, "cli", "update.fileUpdated", { file })}`)
4056
4295
  );
@@ -4107,7 +4346,7 @@ function extractPorcelainPaths(line) {
4107
4346
  function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
4108
4347
  const top = getGitTopLevel2(docsDir);
4109
4348
  if (!top) return null;
4110
- const rel = path17.relative(top, docsDir) || ".";
4349
+ const rel = path18.relative(top, docsDir) || ".";
4111
4350
  try {
4112
4351
  const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
4113
4352
  cwd: top,
@@ -4119,7 +4358,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
4119
4358
  }
4120
4359
  const ignoredRelPaths = new Set(
4121
4360
  ignoredAbsPaths.map(
4122
- (absPath) => normalizeGitPath2(path17.relative(top, absPath) || ".")
4361
+ (absPath) => normalizeGitPath2(path18.relative(top, absPath) || ".")
4123
4362
  )
4124
4363
  );
4125
4364
  const filtered = output.split("\n").filter((line) => {
@@ -4176,7 +4415,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
4176
4415
  }
4177
4416
  async function runConfig(options) {
4178
4417
  const cwd = process.cwd();
4179
- const targetCwd = options.dir ? path17.resolve(cwd, options.dir) : cwd;
4418
+ const targetCwd = options.dir ? path18.resolve(cwd, options.dir) : cwd;
4180
4419
  const config = await getConfig(targetCwd);
4181
4420
  if (!config) {
4182
4421
  throw createCliError(
@@ -4184,7 +4423,7 @@ async function runConfig(options) {
4184
4423
  tr(DEFAULT_LANG, "cli", "common.configNotFound")
4185
4424
  );
4186
4425
  }
4187
- const configPath = path17.join(config.docsDir, ".lee-spec-kit.json");
4426
+ const configPath = path18.join(config.docsDir, ".lee-spec-kit.json");
4188
4427
  if (!options.projectRoot) {
4189
4428
  console.log();
4190
4429
  console.log(chalk6.blue(tr(config.lang, "cli", "config.currentTitle")));
@@ -4195,7 +4434,7 @@ async function runConfig(options) {
4195
4434
  )
4196
4435
  );
4197
4436
  console.log();
4198
- const configFile = await fs15.readJson(configPath);
4437
+ const configFile = await fs14.readJson(configPath);
4199
4438
  console.log(JSON.stringify(configFile, null, 2));
4200
4439
  console.log();
4201
4440
  return;
@@ -4203,7 +4442,7 @@ async function runConfig(options) {
4203
4442
  await withFileLock(
4204
4443
  getDocsLockPath(config.docsDir),
4205
4444
  async () => {
4206
- const configFile = await fs15.readJson(configPath);
4445
+ const configFile = await fs14.readJson(configPath);
4207
4446
  if (configFile.docsRepo !== "standalone") {
4208
4447
  console.log(
4209
4448
  chalk6.yellow(tr(config.lang, "cli", "config.projectRootStandaloneOnly"))
@@ -4282,7 +4521,7 @@ async function runConfig(options) {
4282
4521
  )
4283
4522
  );
4284
4523
  }
4285
- await fs15.writeJson(configPath, configFile, { spaces: 2 });
4524
+ await fs14.writeJson(configPath, configFile, { spaces: 2 });
4286
4525
  console.log();
4287
4526
  },
4288
4527
  { owner: "config" }
@@ -4536,42 +4775,42 @@ var BUILTIN_DOC_DEFINITIONS = [
4536
4775
  {
4537
4776
  id: "agents",
4538
4777
  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")
4778
+ relativePath: (projectType, lang) => path18.join(lang, toTemplateProjectType(projectType), "agents", "agents.md")
4540
4779
  },
4541
4780
  {
4542
4781
  id: "git-workflow",
4543
4782
  title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
4544
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "git-workflow.md")
4783
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "git-workflow.md")
4545
4784
  },
4546
4785
  {
4547
4786
  id: "issue-template",
4548
4787
  title: { ko: "Issue \uD15C\uD50C\uB9BF", en: "Issue Template" },
4549
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "issue-template.md")
4788
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "issue-template.md")
4550
4789
  },
4551
4790
  {
4552
4791
  id: "pr-template",
4553
4792
  title: { ko: "PR \uD15C\uD50C\uB9BF", en: "PR Template" },
4554
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "pr-template.md")
4793
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "pr-template.md")
4555
4794
  },
4556
4795
  {
4557
4796
  id: "create-feature",
4558
4797
  title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
4559
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "skills", "create-feature.md")
4798
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "skills", "create-feature.md")
4560
4799
  },
4561
4800
  {
4562
4801
  id: "execute-task",
4563
4802
  title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
4564
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "skills", "execute-task.md")
4803
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "skills", "execute-task.md")
4565
4804
  },
4566
4805
  {
4567
4806
  id: "create-issue",
4568
4807
  title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
4569
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "skills", "create-issue.md")
4808
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "skills", "create-issue.md")
4570
4809
  },
4571
4810
  {
4572
4811
  id: "create-pr",
4573
4812
  title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
4574
- relativePath: (_, lang) => path17.join(lang, "common", "agents", "skills", "create-pr.md")
4813
+ relativePath: (_, lang) => path18.join(lang, "common", "agents", "skills", "create-pr.md")
4575
4814
  }
4576
4815
  ];
4577
4816
  var DOC_FOLLOWUPS = {
@@ -4655,7 +4894,7 @@ function listBuiltinDocs(projectType, lang) {
4655
4894
  id: doc.id,
4656
4895
  title: doc.title[lang],
4657
4896
  relativePath,
4658
- absolutePath: path17.join(templatesDir, relativePath)
4897
+ absolutePath: path18.join(templatesDir, relativePath)
4659
4898
  };
4660
4899
  });
4661
4900
  }
@@ -4664,7 +4903,7 @@ async function getBuiltinDoc(docId, projectType, lang) {
4664
4903
  if (!entry) {
4665
4904
  throw new Error(`Unknown builtin doc: ${docId}`);
4666
4905
  }
4667
- const content = await fs15.readFile(entry.absolutePath, "utf-8");
4906
+ const content = await fs14.readFile(entry.absolutePath, "utf-8");
4668
4907
  const hash = createHash("sha256").update(content).digest("hex").slice(0, 12);
4669
4908
  return {
4670
4909
  entry,
@@ -4749,7 +4988,7 @@ function getCommandExecutionLockPath(action, config) {
4749
4988
  if (action.scope === "docs") {
4750
4989
  return getDocsLockPath(config.docsDir);
4751
4990
  }
4752
- return path17.join(action.cwd, ".lee-spec-kit.project.lock");
4991
+ return getProjectExecutionLockPath(action.cwd);
4753
4992
  }
4754
4993
  function contextCommand(program2) {
4755
4994
  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 +5382,7 @@ async function runContext(featureName, options) {
5143
5382
  if (f.issueNumber) {
5144
5383
  console.log(` \u2022 Issue: #${f.issueNumber}`);
5145
5384
  }
5146
- console.log(` \u2022 Path: ${path17.relative(cwd, f.path)}`);
5385
+ console.log(` \u2022 Path: ${path.relative(cwd, f.path)}`);
5147
5386
  if (f.git.projectBranch) {
5148
5387
  console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
5149
5388
  }
@@ -5397,7 +5636,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
5397
5636
  ]);
5398
5637
  function formatPath(cwd, p) {
5399
5638
  if (!p) return "";
5400
- return path17.isAbsolute(p) ? path17.relative(cwd, p) : p;
5639
+ return path18.isAbsolute(p) ? path18.relative(cwd, p) : p;
5401
5640
  }
5402
5641
  function detectPlaceholders(content) {
5403
5642
  const patterns = [
@@ -5540,7 +5779,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
5540
5779
  const placeholderContext = {
5541
5780
  projectName: config.projectName,
5542
5781
  featureName: f.slug,
5543
- featurePath: f.docs.featurePathFromDocs || path17.relative(config.docsDir, f.path),
5782
+ featurePath: f.docs.featurePathFromDocs || path18.relative(config.docsDir, f.path),
5544
5783
  repoType: f.type,
5545
5784
  featureNumber
5546
5785
  };
@@ -5550,9 +5789,9 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
5550
5789
  "tasks.md"
5551
5790
  ];
5552
5791
  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");
5792
+ const fullPath = path18.join(f.path, file);
5793
+ if (!await fs14.pathExists(fullPath)) continue;
5794
+ const original = await fs14.readFile(fullPath, "utf-8");
5556
5795
  let next = original;
5557
5796
  const changes = [];
5558
5797
  const placeholderFix = applyPlaceholderFixes(next, placeholderContext, config.lang);
@@ -5576,7 +5815,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
5576
5815
  }
5577
5816
  if (next === original) continue;
5578
5817
  if (!dryRun) {
5579
- await fs15.writeFile(fullPath, next, "utf-8");
5818
+ await fs14.writeFile(fullPath, next, "utf-8");
5580
5819
  }
5581
5820
  entries.push({
5582
5821
  path: formatPath(cwd, fullPath),
@@ -5595,8 +5834,8 @@ async function checkDocsStructure(config, cwd) {
5595
5834
  const issues = [];
5596
5835
  const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
5597
5836
  for (const dir of requiredDirs) {
5598
- const p = path17.join(config.docsDir, dir);
5599
- if (!await fs15.pathExists(p)) {
5837
+ const p = path18.join(config.docsDir, dir);
5838
+ if (!await fs14.pathExists(p)) {
5600
5839
  issues.push({
5601
5840
  level: "error",
5602
5841
  code: "missing_dir",
@@ -5605,8 +5844,8 @@ async function checkDocsStructure(config, cwd) {
5605
5844
  });
5606
5845
  }
5607
5846
  }
5608
- const configPath = path17.join(config.docsDir, ".lee-spec-kit.json");
5609
- if (!await fs15.pathExists(configPath)) {
5847
+ const configPath = path18.join(config.docsDir, ".lee-spec-kit.json");
5848
+ if (!await fs14.pathExists(configPath)) {
5610
5849
  issues.push({
5611
5850
  level: "warn",
5612
5851
  code: "missing_config",
@@ -5628,7 +5867,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5628
5867
  }
5629
5868
  const idMap = /* @__PURE__ */ new Map();
5630
5869
  for (const f of features) {
5631
- const rel = f.docs.featurePathFromDocs || path17.relative(config.docsDir, f.path);
5870
+ const rel = f.docs.featurePathFromDocs || path18.relative(config.docsDir, f.path);
5632
5871
  const id = f.id || "UNKNOWN";
5633
5872
  if (!idMap.has(id)) idMap.set(id, []);
5634
5873
  idMap.get(id).push(rel);
@@ -5636,9 +5875,9 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5636
5875
  if (!isInitialTemplateState) {
5637
5876
  const featureDocs = ["spec.md", "plan.md", "tasks.md"];
5638
5877
  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");
5878
+ const p = path18.join(f.path, file);
5879
+ if (!await fs14.pathExists(p)) continue;
5880
+ const content = await fs14.readFile(p, "utf-8");
5642
5881
  const placeholders = detectPlaceholders(content);
5643
5882
  if (placeholders.length === 0) continue;
5644
5883
  issues.push({
@@ -5651,9 +5890,9 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5651
5890
  });
5652
5891
  }
5653
5892
  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");
5893
+ const decisionsPath = path18.join(f.path, "decisions.md");
5894
+ if (await fs14.pathExists(decisionsPath)) {
5895
+ const content = await fs14.readFile(decisionsPath, "utf-8");
5657
5896
  const placeholders = detectPlaceholders(content);
5658
5897
  if (placeholders.length > 0) {
5659
5898
  issues.push({
@@ -5680,7 +5919,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5680
5919
  level: "warn",
5681
5920
  code: "spec_status_unset",
5682
5921
  message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
5683
- path: formatPath(cwd, path17.join(f.path, "spec.md"))
5922
+ path: formatPath(cwd, path18.join(f.path, "spec.md"))
5684
5923
  });
5685
5924
  }
5686
5925
  if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
@@ -5688,7 +5927,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5688
5927
  level: "warn",
5689
5928
  code: "plan_status_unset",
5690
5929
  message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
5691
- path: formatPath(cwd, path17.join(f.path, "plan.md"))
5930
+ path: formatPath(cwd, path18.join(f.path, "plan.md"))
5692
5931
  });
5693
5932
  }
5694
5933
  if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
@@ -5696,7 +5935,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5696
5935
  level: "warn",
5697
5936
  code: "tasks_empty",
5698
5937
  message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
5699
- path: formatPath(cwd, path17.join(f.path, "tasks.md"))
5938
+ path: formatPath(cwd, path18.join(f.path, "tasks.md"))
5700
5939
  });
5701
5940
  }
5702
5941
  if (f.docs.tasksExists && !f.docs.tasksDocStatusFieldExists && !isInitialTemplateState) {
@@ -5704,7 +5943,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5704
5943
  level: "warn",
5705
5944
  code: "tasks_doc_status_missing",
5706
5945
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
5707
- path: formatPath(cwd, path17.join(f.path, "tasks.md"))
5946
+ path: formatPath(cwd, path18.join(f.path, "tasks.md"))
5708
5947
  });
5709
5948
  }
5710
5949
  if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
@@ -5712,7 +5951,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5712
5951
  level: "warn",
5713
5952
  code: "tasks_doc_status_unset",
5714
5953
  message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
5715
- path: formatPath(cwd, path17.join(f.path, "tasks.md"))
5954
+ path: formatPath(cwd, path18.join(f.path, "tasks.md"))
5716
5955
  });
5717
5956
  }
5718
5957
  }
@@ -5736,7 +5975,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
5736
5975
  level: "warn",
5737
5976
  code: "missing_feature_id",
5738
5977
  message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
5739
- path: formatPath(cwd, path17.join(config.docsDir, p))
5978
+ path: formatPath(cwd, path18.join(config.docsDir, p))
5740
5979
  });
5741
5980
  }
5742
5981
  return issues;
@@ -5858,7 +6097,7 @@ function doctorCommand(program2) {
5858
6097
  }
5859
6098
  console.log();
5860
6099
  console.log(chalk6.bold(tr(lang, "cli", "doctor.title")));
5861
- console.log(chalk6.gray(`- Docs: ${path17.relative(cwd, docsDir)}`));
6100
+ console.log(chalk6.gray(`- Docs: ${path18.relative(cwd, docsDir)}`));
5862
6101
  console.log(chalk6.gray(`- Type: ${projectType}`));
5863
6102
  console.log(chalk6.gray(`- Lang: ${lang}`));
5864
6103
  console.log();
@@ -6037,7 +6276,7 @@ async function runView(featureName, options) {
6037
6276
  }
6038
6277
  console.log();
6039
6278
  console.log(chalk6.bold("\u{1F4CA} Workflow View"));
6040
- console.log(chalk6.gray(`- Docs: ${path17.relative(cwd, config.docsDir)}`));
6279
+ console.log(chalk6.gray(`- Docs: ${path18.relative(cwd, config.docsDir)}`));
6041
6280
  console.log(
6042
6281
  chalk6.gray(
6043
6282
  `- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
@@ -6387,40 +6626,40 @@ function tg(lang, key, vars = {}) {
6387
6626
  }
6388
6627
  function detectGithubCliLangSync(cwd) {
6389
6628
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
6390
- const startDirs = [explicitDocsDir ? path17.resolve(explicitDocsDir) : "", path17.resolve(cwd)].filter(Boolean);
6629
+ const startDirs = [explicitDocsDir ? path18.resolve(explicitDocsDir) : "", path18.resolve(cwd)].filter(Boolean);
6391
6630
  const scanOrder = [];
6392
6631
  const seen = /* @__PURE__ */ new Set();
6393
6632
  for (const start of startDirs) {
6394
6633
  let current = start;
6395
6634
  while (true) {
6396
- const abs = path17.resolve(current);
6635
+ const abs = path18.resolve(current);
6397
6636
  if (!seen.has(abs)) {
6398
6637
  scanOrder.push(abs);
6399
6638
  seen.add(abs);
6400
6639
  }
6401
- const parent = path17.dirname(abs);
6640
+ const parent = path18.dirname(abs);
6402
6641
  if (parent === abs) break;
6403
6642
  current = parent;
6404
6643
  }
6405
6644
  }
6406
6645
  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)) {
6646
+ for (const docsDir of [path18.join(base, "docs"), base]) {
6647
+ const configPath = path18.join(docsDir, ".lee-spec-kit.json");
6648
+ if (fs14.existsSync(configPath)) {
6410
6649
  try {
6411
- const parsed = fs15.readJsonSync(configPath);
6650
+ const parsed = fs14.readJsonSync(configPath);
6412
6651
  if (parsed?.lang === "ko" || parsed?.lang === "en") return parsed.lang;
6413
6652
  } catch {
6414
6653
  }
6415
6654
  }
6416
- const agentsPath = path17.join(docsDir, "agents");
6417
- const featuresPath = path17.join(docsDir, "features");
6418
- if (!fs15.existsSync(agentsPath) || !fs15.existsSync(featuresPath)) continue;
6655
+ const agentsPath = path18.join(docsDir, "agents");
6656
+ const featuresPath = path18.join(docsDir, "features");
6657
+ if (!fs14.existsSync(agentsPath) || !fs14.existsSync(featuresPath)) continue;
6419
6658
  for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
6420
- const file = path17.join(agentsPath, probe);
6421
- if (!fs15.existsSync(file)) continue;
6659
+ const file = path18.join(agentsPath, probe);
6660
+ if (!fs14.existsSync(file)) continue;
6422
6661
  try {
6423
- const content = fs15.readFileSync(file, "utf-8");
6662
+ const content = fs14.readFileSync(file, "utf-8");
6424
6663
  if (/[가-힣]/.test(content)) return "ko";
6425
6664
  } catch {
6426
6665
  }
@@ -6521,7 +6760,7 @@ function ensureSections(body, sections, kind, lang) {
6521
6760
  }
6522
6761
  function ensureDocsExist(docsDir, relativePaths, lang) {
6523
6762
  const missing = relativePaths.filter(
6524
- (relativePath) => !fs15.existsSync(path17.join(docsDir, relativePath))
6763
+ (relativePath) => !fs14.existsSync(path18.join(docsDir, relativePath))
6525
6764
  );
6526
6765
  if (missing.length > 0) {
6527
6766
  throw createCliError(
@@ -6531,13 +6770,13 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
6531
6770
  }
6532
6771
  }
6533
6772
  function buildDefaultBodyFileName(kind, docsDir, component) {
6534
- const key = `${path17.resolve(docsDir)}::${component.trim().toLowerCase()}`;
6773
+ const key = `${path18.resolve(docsDir)}::${component.trim().toLowerCase()}`;
6535
6774
  const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
6536
6775
  return `lee-spec-kit.${digest}.${kind}.md`;
6537
6776
  }
6538
6777
  function toBodyFilePath(raw, kind, docsDir, component) {
6539
- const selected = raw?.trim() || path17.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
6540
- return path17.resolve(selected);
6778
+ const selected = raw?.trim() || path18.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
6779
+ return path18.resolve(selected);
6541
6780
  }
6542
6781
  function toProjectRootDocsPath(relativePathFromDocs) {
6543
6782
  const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
@@ -7264,10 +7503,10 @@ function insertFieldInGithubIssueSection(content, key, value) {
7264
7503
  return { content: lines.join("\n"), changed: true };
7265
7504
  }
7266
7505
  function syncTasksPrMetadata(tasksPath, prUrl, nextStatus, lang) {
7267
- if (!fs15.existsSync(tasksPath)) {
7506
+ if (!fs14.existsSync(tasksPath)) {
7268
7507
  throw createCliError("DOCS_NOT_FOUND", tg(lang, "tasksNotFound", { path: tasksPath }));
7269
7508
  }
7270
- const original = fs15.readFileSync(tasksPath, "utf-8");
7509
+ const original = fs14.readFileSync(tasksPath, "utf-8");
7271
7510
  let next = original;
7272
7511
  let changed = false;
7273
7512
  const prReplaced = replaceListField(next, ["PR", "Pull Request"], prUrl);
@@ -7291,7 +7530,7 @@ function syncTasksPrMetadata(tasksPath, prUrl, nextStatus, lang) {
7291
7530
  changed = changed || inserted.changed;
7292
7531
  }
7293
7532
  if (changed) {
7294
- fs15.writeFileSync(tasksPath, next, "utf-8");
7533
+ fs14.writeFileSync(tasksPath, next, "utf-8");
7295
7534
  }
7296
7535
  return { changed, path: tasksPath };
7297
7536
  }
@@ -7319,7 +7558,7 @@ function ensureCleanWorktree(cwd, lang) {
7319
7558
  }
7320
7559
  }
7321
7560
  function commitAndPushPath(cwd, absPath, message, lang) {
7322
- const relativePath = path17.relative(cwd, absPath) || absPath;
7561
+ const relativePath = path18.relative(cwd, absPath) || absPath;
7323
7562
  const status = runProcessOrThrow(
7324
7563
  "git",
7325
7564
  ["status", "--porcelain=v1", "--", relativePath],
@@ -7451,9 +7690,9 @@ function githubCommand(program2) {
7451
7690
  [paths.specPath, paths.planPath, paths.tasksPath],
7452
7691
  config.lang
7453
7692
  );
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");
7693
+ const specContent = await fs14.readFile(path18.join(config.docsDir, paths.specPath), "utf-8");
7694
+ const planContent = await fs14.readFile(path18.join(config.docsDir, paths.planPath), "utf-8");
7695
+ const tasksContent = await fs14.readFile(path18.join(config.docsDir, paths.tasksPath), "utf-8");
7457
7696
  const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
7458
7697
  const title = options.title?.trim() || tg(config.lang, "issueDefaultTitle", {
7459
7698
  slug: feature.slug,
@@ -7482,8 +7721,8 @@ function githubCommand(program2) {
7482
7721
  );
7483
7722
  const explicitBodyFile = (options.bodyFile || "").trim();
7484
7723
  let body = generatedBody;
7485
- if (options.create && explicitBodyFile && await fs15.pathExists(bodyFile)) {
7486
- body = await fs15.readFile(bodyFile, "utf-8");
7724
+ if (options.create && explicitBodyFile && await fs14.pathExists(bodyFile)) {
7725
+ body = await fs14.readFile(bodyFile, "utf-8");
7487
7726
  ensureSections(
7488
7727
  body,
7489
7728
  getRequiredIssueSections(config.lang),
@@ -7491,8 +7730,8 @@ function githubCommand(program2) {
7491
7730
  config.lang
7492
7731
  );
7493
7732
  } else {
7494
- await fs15.ensureDir(path17.dirname(bodyFile));
7495
- await fs15.writeFile(bodyFile, generatedBody, "utf-8");
7733
+ await fs14.ensureDir(path18.dirname(bodyFile));
7734
+ await fs14.writeFile(bodyFile, generatedBody, "utf-8");
7496
7735
  }
7497
7736
  let issueUrl;
7498
7737
  if (options.create) {
@@ -7589,10 +7828,10 @@ function githubCommand(program2) {
7589
7828
  const labels = parseLabels(options.labels, config.lang);
7590
7829
  const paths = getFeatureDocPaths(feature);
7591
7830
  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");
7831
+ const specContent = await fs14.readFile(path18.join(config.docsDir, paths.specPath), "utf-8");
7832
+ const planPath = path18.join(config.docsDir, paths.planPath);
7833
+ const planContent = await fs14.pathExists(planPath) ? await fs14.readFile(planPath, "utf-8") : "";
7834
+ const tasksContent = await fs14.readFile(path18.join(config.docsDir, paths.tasksPath), "utf-8");
7596
7835
  const overview = resolveOverviewFromSpec(specContent, feature, config.lang);
7597
7836
  const defaultTitle = feature.issueNumber ? tg(config.lang, "prDefaultTitleWithIssue", {
7598
7837
  issue: feature.issueNumber,
@@ -7626,8 +7865,8 @@ function githubCommand(program2) {
7626
7865
  );
7627
7866
  const explicitBodyFile = (options.bodyFile || "").trim();
7628
7867
  let body = generatedBody;
7629
- if (options.create && explicitBodyFile && await fs15.pathExists(bodyFile)) {
7630
- body = await fs15.readFile(bodyFile, "utf-8");
7868
+ if (options.create && explicitBodyFile && await fs14.pathExists(bodyFile)) {
7869
+ body = await fs14.readFile(bodyFile, "utf-8");
7631
7870
  ensureSections(
7632
7871
  body,
7633
7872
  getRequiredPrSections(config.lang),
@@ -7635,8 +7874,8 @@ function githubCommand(program2) {
7635
7874
  config.lang
7636
7875
  );
7637
7876
  } else {
7638
- await fs15.ensureDir(path17.dirname(bodyFile));
7639
- await fs15.writeFile(bodyFile, generatedBody, "utf-8");
7877
+ await fs14.ensureDir(path18.dirname(bodyFile));
7878
+ await fs14.writeFile(bodyFile, generatedBody, "utf-8");
7640
7879
  }
7641
7880
  const retryCount = toRetryCount(options.retry, config.lang);
7642
7881
  let prUrl = options.pr?.trim() || "";
@@ -7688,7 +7927,7 @@ function githubCommand(program2) {
7688
7927
  }
7689
7928
  if (prUrl && options.syncTasks !== false) {
7690
7929
  const synced = syncTasksPrMetadata(
7691
- path17.join(config.docsDir, paths.tasksPath),
7930
+ path18.join(config.docsDir, paths.tasksPath),
7692
7931
  prUrl,
7693
7932
  "Review",
7694
7933
  config.lang
@@ -7909,7 +8148,7 @@ function docsCommand(program2) {
7909
8148
  );
7910
8149
  return;
7911
8150
  }
7912
- const relativeFromCwd = path17.relative(process.cwd(), loaded.entry.absolutePath);
8151
+ const relativeFromCwd = path18.relative(process.cwd(), loaded.entry.absolutePath);
7913
8152
  console.log();
7914
8153
  console.log(chalk6.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
7915
8154
  console.log(
@@ -7999,13 +8238,13 @@ ${version}
7999
8238
  }
8000
8239
  return `${ascii}${footer}`;
8001
8240
  }
8002
- var CACHE_FILE = path17.join(os.homedir(), ".lee-spec-kit-version-cache.json");
8241
+ var CACHE_FILE = path18.join(os.homedir(), ".lee-spec-kit-version-cache.json");
8003
8242
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
8004
8243
  function getCurrentVersion() {
8005
8244
  try {
8006
- const packageJsonPath = path17.join(__dirname$1, "..", "package.json");
8007
- if (fs15.existsSync(packageJsonPath)) {
8008
- const pkg = fs15.readJsonSync(packageJsonPath);
8245
+ const packageJsonPath = path18.join(__dirname$1, "..", "package.json");
8246
+ if (fs14.existsSync(packageJsonPath)) {
8247
+ const pkg = fs14.readJsonSync(packageJsonPath);
8009
8248
  return pkg.version;
8010
8249
  }
8011
8250
  } catch {
@@ -8014,8 +8253,8 @@ function getCurrentVersion() {
8014
8253
  }
8015
8254
  function readCache() {
8016
8255
  try {
8017
- if (fs15.existsSync(CACHE_FILE)) {
8018
- return fs15.readJsonSync(CACHE_FILE);
8256
+ if (fs14.existsSync(CACHE_FILE)) {
8257
+ return fs14.readJsonSync(CACHE_FILE);
8019
8258
  }
8020
8259
  } catch {
8021
8260
  }
@@ -8103,9 +8342,9 @@ function shouldCheckForUpdates() {
8103
8342
  if (shouldCheckForUpdates()) checkForUpdates();
8104
8343
  function getCliVersion() {
8105
8344
  try {
8106
- const packageJsonPath = path17.join(__dirname$1, "..", "package.json");
8107
- if (fs15.existsSync(packageJsonPath)) {
8108
- const pkg = fs15.readJsonSync(packageJsonPath);
8345
+ const packageJsonPath = path18.join(__dirname$1, "..", "package.json");
8346
+ if (fs14.existsSync(packageJsonPath)) {
8347
+ const pkg = fs14.readJsonSync(packageJsonPath);
8109
8348
  if (pkg?.version) return String(pkg.version);
8110
8349
  }
8111
8350
  } catch {