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.
- package/dist/index.js +497 -310
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import path18 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { program } from 'commander';
|
|
5
|
-
import
|
|
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 = () =>
|
|
14
|
+
var getDirname = () => path18.dirname(getFilename());
|
|
15
15
|
var __dirname$1 = /* @__PURE__ */ getDirname();
|
|
16
16
|
async function copyTemplates(src, dest) {
|
|
17
|
-
await
|
|
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
|
|
33
|
+
let content = await fs14.readFile(file, "utf-8");
|
|
34
34
|
content = applyReplacements(content, replacements);
|
|
35
|
-
await
|
|
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
|
|
39
|
+
let content = await fs14.readFile(file, "utf-8");
|
|
40
40
|
content = applyReplacements(content, replacements);
|
|
41
|
-
await
|
|
41
|
+
await fs14.writeFile(file, content, "utf-8");
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
45
|
-
var __dirname2 =
|
|
45
|
+
var __dirname2 = path18.dirname(__filename2);
|
|
46
46
|
function getTemplatesDir() {
|
|
47
|
-
const rootDir =
|
|
48
|
-
return
|
|
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
|
|
1206
|
+
return path18.join(docsDir, ".lee-spec-kit.lock");
|
|
1207
1207
|
}
|
|
1208
1208
|
function getInitLockPath(targetDir) {
|
|
1209
|
-
return
|
|
1210
|
-
|
|
1211
|
-
`.lee-spec-kit.${
|
|
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
|
|
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
|
|
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
|
|
1253
|
+
await fs14.ensureDir(path18.dirname(lockPath));
|
|
1254
1254
|
try {
|
|
1255
|
-
const fd = await
|
|
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
|
|
1261
|
+
await fs14.writeFile(fd, `${payload}
|
|
1262
1262
|
`, { encoding: "utf8" });
|
|
1263
|
-
await
|
|
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
|
|
1277
|
+
while (await fs14.pathExists(lockPath)) {
|
|
1278
1278
|
if (await isStaleLock(lockPath, staleMs)) {
|
|
1279
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
1338
|
-
if (await
|
|
1339
|
-
await
|
|
1340
|
-
removed.push(
|
|
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 =
|
|
1345
|
-
if (await
|
|
1346
|
-
await
|
|
1347
|
-
removed.push(
|
|
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 =
|
|
1351
|
-
if (await
|
|
1352
|
-
await
|
|
1353
|
-
removed.push(
|
|
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 =
|
|
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 =
|
|
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
|
|
1735
|
-
const files = await
|
|
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 =
|
|
1771
|
+
const commonPath = path18.join(templatesDir, lang, "common");
|
|
1772
1772
|
const templateProjectType = toTemplateProjectType(projectType);
|
|
1773
|
-
const typePath =
|
|
1774
|
-
if (await
|
|
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
|
|
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 =
|
|
1785
|
-
await
|
|
1786
|
-
await
|
|
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 =
|
|
1789
|
-
await
|
|
1790
|
-
const readmePath =
|
|
1791
|
-
if (!await
|
|
1792
|
-
await
|
|
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 =
|
|
1842
|
-
await
|
|
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 =
|
|
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 =
|
|
1967
|
+
let current = path18.resolve(startDir);
|
|
1968
1968
|
while (true) {
|
|
1969
1969
|
dirs.push(current);
|
|
1970
|
-
const parent =
|
|
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
|
|
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 ? [
|
|
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 =
|
|
1996
|
+
const resolvedBaseDir = path18.resolve(baseDir);
|
|
1997
1997
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
1998
1998
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
1999
|
-
const possibleDocsDirs = [
|
|
1999
|
+
const possibleDocsDirs = [path18.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2000
2000
|
for (const docsDir of possibleDocsDirs) {
|
|
2001
|
-
const resolvedDocsDir =
|
|
2001
|
+
const resolvedDocsDir = path18.resolve(docsDir);
|
|
2002
2002
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2003
2003
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2004
|
-
const configPath =
|
|
2005
|
-
if (await
|
|
2004
|
+
const configPath = path18.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
2005
|
+
if (await fs14.pathExists(configPath)) {
|
|
2006
2006
|
try {
|
|
2007
|
-
const configFile = await
|
|
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 =
|
|
2031
|
-
const featuresPath =
|
|
2032
|
-
if (await
|
|
2033
|
-
const bePath =
|
|
2034
|
-
const fePath =
|
|
2035
|
-
const projectType = await
|
|
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
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
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
|
|
2045
|
-
const content = await
|
|
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
|
|
2092
|
-
const content = await
|
|
2093
|
-
await
|
|
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(
|
|
2096
|
+
await patchMarkdownIfExists(path18.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
2097
2097
|
await patchMarkdownIfExists(
|
|
2098
|
-
|
|
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 =
|
|
2248
|
+
featuresDir = path18.join(docsDir, "features", component);
|
|
2249
2249
|
} else {
|
|
2250
|
-
featuresDir =
|
|
2250
|
+
featuresDir = path18.join(docsDir, "features");
|
|
2251
2251
|
}
|
|
2252
2252
|
const featureFolderName = `${featureId}-${name}`;
|
|
2253
|
-
const featureDir =
|
|
2254
|
-
if (await
|
|
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 =
|
|
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
|
|
2267
|
+
if (!await fs14.pathExists(featureBasePath)) {
|
|
2268
2268
|
throw createCliError("DOCS_NOT_FOUND", tr(lang, "cli", "feature.baseNotFound"));
|
|
2269
2269
|
}
|
|
2270
|
-
await
|
|
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:
|
|
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 ? [
|
|
2330
|
-
|
|
2331
|
-
|
|
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
|
|
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 =
|
|
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) =>
|
|
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
|
|
2368
|
-
const entries = await
|
|
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(
|
|
3336
|
+
return value.split(path18.sep).join("/");
|
|
3284
3337
|
}
|
|
3285
3338
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
3286
|
-
const relativeDocsDir =
|
|
3339
|
+
const relativeDocsDir = path18.relative(projectGitCwd, docsDir);
|
|
3287
3340
|
if (!relativeDocsDir) return [];
|
|
3288
|
-
if (
|
|
3289
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
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 (!
|
|
3322
|
-
const relative =
|
|
3376
|
+
if (!path18.isAbsolute(candidate)) return candidate;
|
|
3377
|
+
const relative = path18.relative(projectGitCwd, candidate);
|
|
3323
3378
|
if (!relative) return "";
|
|
3324
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
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
|
|
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 =
|
|
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 =
|
|
3398
|
-
const planPath =
|
|
3399
|
-
const tasksPath =
|
|
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
|
|
3465
|
+
const specExists = await fs14.pathExists(specPath);
|
|
3403
3466
|
if (specExists) {
|
|
3404
|
-
const content = await
|
|
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
|
|
3474
|
+
const planExists = await fs14.pathExists(planPath);
|
|
3412
3475
|
if (planExists) {
|
|
3413
|
-
const content = await
|
|
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
|
|
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
|
|
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 =
|
|
3474
|
-
const
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
3641
|
-
|
|
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
|
-
|
|
3674
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
3854
|
-
if (!await
|
|
3855
|
-
const content = await
|
|
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 =
|
|
3935
|
-
const typeAgentsBase =
|
|
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 =
|
|
3942
|
-
const commonAgents = agentsMode === "skills" ?
|
|
3943
|
-
const typeAgents = agentsMode === "skills" ?
|
|
3944
|
-
const targetAgents = agentsMode === "skills" ?
|
|
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
|
|
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
|
|
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
|
|
4023
|
-
const files = await
|
|
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 =
|
|
4027
|
-
const targetPath =
|
|
4028
|
-
const stat = await
|
|
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
|
|
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
|
|
4039
|
-
const targetContent = await
|
|
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
|
|
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 =
|
|
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(
|
|
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 ?
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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:
|
|
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
|
|
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
|
|
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: ${
|
|
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
|
|
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 ||
|
|
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 =
|
|
5554
|
-
if (!await
|
|
5555
|
-
const original = await
|
|
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
|
|
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 =
|
|
5599
|
-
if (!await
|
|
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 =
|
|
5609
|
-
if (!await
|
|
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 ||
|
|
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 =
|
|
5640
|
-
if (!await
|
|
5641
|
-
const content = await
|
|
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 =
|
|
5655
|
-
if (await
|
|
5656
|
-
const content = await
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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: ${
|
|
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: ${
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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 [
|
|
6408
|
-
const configPath =
|
|
6409
|
-
if (
|
|
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 =
|
|
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 =
|
|
6417
|
-
const featuresPath =
|
|
6418
|
-
if (!
|
|
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 =
|
|
6421
|
-
if (!
|
|
6607
|
+
const file = path18.join(agentsPath, probe);
|
|
6608
|
+
if (!fs14.existsSync(file)) continue;
|
|
6422
6609
|
try {
|
|
6423
|
-
const content =
|
|
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) => !
|
|
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 = `${
|
|
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() ||
|
|
6540
|
-
return
|
|
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 (!
|
|
7454
|
+
if (!fs14.existsSync(tasksPath)) {
|
|
7268
7455
|
throw createCliError("DOCS_NOT_FOUND", tg(lang, "tasksNotFound", { path: tasksPath }));
|
|
7269
7456
|
}
|
|
7270
|
-
const original =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
7455
|
-
const planContent = await
|
|
7456
|
-
const tasksContent = await
|
|
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
|
|
7486
|
-
body = await
|
|
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
|
|
7495
|
-
await
|
|
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
|
|
7593
|
-
const planPath =
|
|
7594
|
-
const planContent = await
|
|
7595
|
-
const tasksContent = await
|
|
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
|
|
7630
|
-
body = await
|
|
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
|
|
7639
|
-
await
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8007
|
-
if (
|
|
8008
|
-
const pkg =
|
|
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 (
|
|
8018
|
-
return
|
|
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 =
|
|
8107
|
-
if (
|
|
8108
|
-
const pkg =
|
|
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 {
|