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