lee-spec-kit 0.7.2 → 0.7.3
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 +450 -332
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +7 -0
- package/templates/en/common/agents/agents.md +5 -3
- package/templates/en/common/agents/issue-template.md +1 -5
- package/templates/en/common/agents/pr-template.md +2 -11
- package/templates/en/common/agents/skills/create-feature.md +3 -0
- package/templates/en/common/agents/skills/create-issue.md +4 -7
- package/templates/en/common/agents/skills/create-pr.md +4 -8
- package/templates/en/common/agents/skills/execute-task.md +3 -0
- package/templates/en/common/features/README.md +19 -0
- package/templates/en/common/features/feature-base/decisions.md +1 -0
- package/templates/en/common/features/feature-base/plan.md +1 -0
- package/templates/en/common/features/feature-base/tasks.md +1 -0
- package/templates/ko/common/README.md +7 -0
- package/templates/ko/common/agents/agents.md +5 -3
- package/templates/ko/common/agents/issue-template.md +1 -5
- package/templates/ko/common/agents/pr-template.md +2 -11
- package/templates/ko/common/agents/skills/create-feature.md +3 -0
- package/templates/ko/common/agents/skills/create-issue.md +4 -7
- package/templates/ko/common/agents/skills/create-pr.md +4 -8
- package/templates/ko/common/agents/skills/execute-task.md +4 -0
- package/templates/ko/common/features/README.md +19 -0
- package/templates/ko/common/features/feature-base/decisions.md +1 -0
- package/templates/ko/common/features/feature-base/plan.md +1 -0
- package/templates/ko/common/features/feature-base/tasks.md +1 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import path14 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { program } from 'commander';
|
|
5
5
|
import fs from 'fs-extra';
|
|
@@ -8,11 +8,11 @@ import chalk9 from 'chalk';
|
|
|
8
8
|
import { spawn, spawnSync, execFileSync, execSync } from 'child_process';
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import { createHash, randomUUID } from 'crypto';
|
|
11
|
-
import
|
|
11
|
+
import fs11 from 'fs';
|
|
12
12
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
13
13
|
|
|
14
14
|
var getFilename = () => fileURLToPath(import.meta.url);
|
|
15
|
-
var getDirname = () =>
|
|
15
|
+
var getDirname = () => path14.dirname(getFilename());
|
|
16
16
|
var __dirname$1 = /* @__PURE__ */ getDirname();
|
|
17
17
|
async function walkFiles(fsAdapter, rootDir, options = {}) {
|
|
18
18
|
const out = [];
|
|
@@ -25,7 +25,7 @@ async function walkFiles(fsAdapter, rootDir, options = {}) {
|
|
|
25
25
|
async function visit(current) {
|
|
26
26
|
const entries = await fsAdapter.readdir(current);
|
|
27
27
|
for (const entryName of entries) {
|
|
28
|
-
const absolute =
|
|
28
|
+
const absolute = path14.join(current, entryName);
|
|
29
29
|
const stat = await fsAdapter.stat(absolute);
|
|
30
30
|
if (stat.isDirectory()) {
|
|
31
31
|
if (ignored.has(entryName.trim().toLowerCase())) continue;
|
|
@@ -34,7 +34,7 @@ async function walkFiles(fsAdapter, rootDir, options = {}) {
|
|
|
34
34
|
}
|
|
35
35
|
if (!stat.isFile()) continue;
|
|
36
36
|
if (normalizedExtensions.size > 0) {
|
|
37
|
-
const ext =
|
|
37
|
+
const ext = path14.extname(entryName).toLowerCase();
|
|
38
38
|
if (!normalizedExtensions.has(ext)) continue;
|
|
39
39
|
}
|
|
40
40
|
out.push(absolute);
|
|
@@ -50,7 +50,7 @@ async function listSubdirectories(fsAdapter, rootDir) {
|
|
|
50
50
|
const entries = await fsAdapter.readdir(rootDir);
|
|
51
51
|
const dirs = [];
|
|
52
52
|
for (const entryName of entries) {
|
|
53
|
-
const absolute =
|
|
53
|
+
const absolute = path14.join(rootDir, entryName);
|
|
54
54
|
const stat = await fsAdapter.stat(absolute);
|
|
55
55
|
if (stat.isDirectory()) {
|
|
56
56
|
dirs.push(absolute);
|
|
@@ -151,10 +151,10 @@ var DefaultFileSystemAdapter = class {
|
|
|
151
151
|
}
|
|
152
152
|
};
|
|
153
153
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
154
|
-
var __dirname2 =
|
|
154
|
+
var __dirname2 = path14.dirname(__filename2);
|
|
155
155
|
function getTemplatesDir() {
|
|
156
|
-
const rootDir =
|
|
157
|
-
return
|
|
156
|
+
const rootDir = path14.resolve(__dirname2, "..");
|
|
157
|
+
return path14.join(rootDir, "templates");
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// src/utils/locales/ko/cli.ts
|
|
@@ -1741,10 +1741,10 @@ var DEFAULT_STALE_MS = 2 * 6e4;
|
|
|
1741
1741
|
var RUNTIME_GIT_DIRNAME = "lee-spec-kit.runtime";
|
|
1742
1742
|
var RUNTIME_TEMP_DIRNAME = "lee-spec-kit-runtime";
|
|
1743
1743
|
function toScopeKey(value) {
|
|
1744
|
-
return createHash("sha1").update(
|
|
1744
|
+
return createHash("sha1").update(path14.resolve(value)).digest("hex").slice(0, 16);
|
|
1745
1745
|
}
|
|
1746
1746
|
function getTempRuntimeDir(scopePath) {
|
|
1747
|
-
return
|
|
1747
|
+
return path14.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
|
|
1748
1748
|
}
|
|
1749
1749
|
function resolveGitRuntimeDir(cwd) {
|
|
1750
1750
|
try {
|
|
@@ -1758,38 +1758,38 @@ function resolveGitRuntimeDir(cwd) {
|
|
|
1758
1758
|
}
|
|
1759
1759
|
).trim();
|
|
1760
1760
|
if (!out) return null;
|
|
1761
|
-
return
|
|
1761
|
+
return path14.isAbsolute(out) ? out : path14.resolve(cwd, out);
|
|
1762
1762
|
} catch {
|
|
1763
1763
|
return null;
|
|
1764
1764
|
}
|
|
1765
1765
|
}
|
|
1766
1766
|
function getRuntimeStateDir(cwd) {
|
|
1767
|
-
const resolved =
|
|
1767
|
+
const resolved = path14.resolve(cwd);
|
|
1768
1768
|
return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
|
|
1769
1769
|
}
|
|
1770
1770
|
function getDocsLockPath(docsDir) {
|
|
1771
|
-
return
|
|
1771
|
+
return path14.join(
|
|
1772
1772
|
getRuntimeStateDir(docsDir),
|
|
1773
1773
|
"locks",
|
|
1774
1774
|
`docs-${toScopeKey(docsDir)}.lock`
|
|
1775
1775
|
);
|
|
1776
1776
|
}
|
|
1777
1777
|
function getInitLockPath(targetDir) {
|
|
1778
|
-
return
|
|
1779
|
-
getRuntimeStateDir(
|
|
1778
|
+
return path14.join(
|
|
1779
|
+
getRuntimeStateDir(path14.dirname(path14.resolve(targetDir))),
|
|
1780
1780
|
"locks",
|
|
1781
1781
|
`init-${toScopeKey(targetDir)}.lock`
|
|
1782
1782
|
);
|
|
1783
1783
|
}
|
|
1784
1784
|
function getApprovalTicketStorePath(docsDir) {
|
|
1785
|
-
return
|
|
1785
|
+
return path14.join(
|
|
1786
1786
|
getRuntimeStateDir(docsDir),
|
|
1787
1787
|
"tickets",
|
|
1788
1788
|
`approval-${toScopeKey(docsDir)}.json`
|
|
1789
1789
|
);
|
|
1790
1790
|
}
|
|
1791
1791
|
function getProjectExecutionLockPath(cwd) {
|
|
1792
|
-
return
|
|
1792
|
+
return path14.join(getRuntimeStateDir(cwd), "locks", "project.lock");
|
|
1793
1793
|
}
|
|
1794
1794
|
async function isStaleLock(lockPath, staleMs) {
|
|
1795
1795
|
try {
|
|
@@ -1830,7 +1830,7 @@ function isProcessAlive(pid) {
|
|
|
1830
1830
|
}
|
|
1831
1831
|
}
|
|
1832
1832
|
async function tryAcquire(lockPath, owner) {
|
|
1833
|
-
await fs.ensureDir(
|
|
1833
|
+
await fs.ensureDir(path14.dirname(lockPath));
|
|
1834
1834
|
try {
|
|
1835
1835
|
const fd = await fs.open(lockPath, "wx");
|
|
1836
1836
|
const payload = JSON.stringify(
|
|
@@ -1907,30 +1907,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
|
|
|
1907
1907
|
"pr-template.md"
|
|
1908
1908
|
];
|
|
1909
1909
|
var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
|
|
1910
|
-
var ENGINE_MANAGED_FEATURE_PATH =
|
|
1910
|
+
var ENGINE_MANAGED_FEATURE_PATH = path14.join(
|
|
1911
1911
|
"features",
|
|
1912
1912
|
"feature-base"
|
|
1913
1913
|
);
|
|
1914
1914
|
async function pruneEngineManagedDocs(docsDir) {
|
|
1915
1915
|
const removed = [];
|
|
1916
1916
|
for (const file of ENGINE_MANAGED_AGENT_FILES) {
|
|
1917
|
-
const target =
|
|
1917
|
+
const target = path14.join(docsDir, "agents", file);
|
|
1918
1918
|
if (await fs.pathExists(target)) {
|
|
1919
1919
|
await fs.remove(target);
|
|
1920
|
-
removed.push(
|
|
1920
|
+
removed.push(path14.relative(docsDir, target));
|
|
1921
1921
|
}
|
|
1922
1922
|
}
|
|
1923
1923
|
for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
|
|
1924
|
-
const target =
|
|
1924
|
+
const target = path14.join(docsDir, "agents", dir);
|
|
1925
1925
|
if (await fs.pathExists(target)) {
|
|
1926
1926
|
await fs.remove(target);
|
|
1927
|
-
removed.push(
|
|
1927
|
+
removed.push(path14.relative(docsDir, target));
|
|
1928
1928
|
}
|
|
1929
1929
|
}
|
|
1930
|
-
const featureBasePath =
|
|
1930
|
+
const featureBasePath = path14.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
|
|
1931
1931
|
if (await fs.pathExists(featureBasePath)) {
|
|
1932
1932
|
await fs.remove(featureBasePath);
|
|
1933
|
-
removed.push(
|
|
1933
|
+
removed.push(path14.relative(docsDir, featureBasePath));
|
|
1934
1934
|
}
|
|
1935
1935
|
return removed;
|
|
1936
1936
|
}
|
|
@@ -2011,7 +2011,7 @@ Auto-run continuity (main/sub-agent orchestration):
|
|
|
2011
2011
|
3. Else run \`npx lee-spec-kit context --json-compact\` (fallback: \`--json\`) and continue from current \`actionOptions\`/\`autoRun\`.
|
|
2012
2012
|
- Pause and report to user only when:
|
|
2013
2013
|
- \`approvalRequest.required === true\`, or
|
|
2014
|
-
- \`autoRun.reasonCode\` is \`AUTO_GATE_REACHED\` or \`AUTO_MANUAL_REQUIRED\`, or
|
|
2014
|
+
- \`autoRun.reasonCode\` is \`AUTO_GATE_REACHED\`, \`AUTO_DELEGATED_HANDOFF\`, or \`AUTO_MANUAL_REQUIRED\`, or
|
|
2015
2015
|
- command execution fails (non-zero/error), or
|
|
2016
2016
|
- user explicitly asks to pause.
|
|
2017
2017
|
|
|
@@ -2229,7 +2229,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2229
2229
|
}
|
|
2230
2230
|
async function runInit(options) {
|
|
2231
2231
|
const cwd = process.cwd();
|
|
2232
|
-
const defaultName =
|
|
2232
|
+
const defaultName = path14.basename(cwd);
|
|
2233
2233
|
let projectName = options.name || defaultName;
|
|
2234
2234
|
let projectType = options.type;
|
|
2235
2235
|
let components = parseComponentsOption(options.components);
|
|
@@ -2240,7 +2240,7 @@ async function runInit(options) {
|
|
|
2240
2240
|
let docsRemote = options.docsRemote;
|
|
2241
2241
|
let projectRoot;
|
|
2242
2242
|
const componentProjectRoots = options.componentProjectRoots ? parseComponentProjectRootsOption(options.componentProjectRoots) : {};
|
|
2243
|
-
const targetDir =
|
|
2243
|
+
const targetDir = path14.resolve(cwd, options.dir || "./docs");
|
|
2244
2244
|
const skipPrompts = !!options.yes || !!options.nonInteractive;
|
|
2245
2245
|
if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
|
|
2246
2246
|
throw createCliError(
|
|
@@ -2637,7 +2637,7 @@ async function runInit(options) {
|
|
|
2637
2637
|
);
|
|
2638
2638
|
console.log();
|
|
2639
2639
|
const templatesDir = getTemplatesDir();
|
|
2640
|
-
const commonPath =
|
|
2640
|
+
const commonPath = path14.join(templatesDir, lang, "common");
|
|
2641
2641
|
if (!await fs.pathExists(commonPath)) {
|
|
2642
2642
|
throw new Error(
|
|
2643
2643
|
tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
|
|
@@ -2646,11 +2646,11 @@ async function runInit(options) {
|
|
|
2646
2646
|
const fsAdapter = new DefaultFileSystemAdapter();
|
|
2647
2647
|
await copyTemplates(fsAdapter, commonPath, targetDir);
|
|
2648
2648
|
if (projectType === "multi") {
|
|
2649
|
-
const featuresRoot =
|
|
2649
|
+
const featuresRoot = path14.join(targetDir, "features");
|
|
2650
2650
|
for (const component of components) {
|
|
2651
|
-
const componentDir =
|
|
2651
|
+
const componentDir = path14.join(featuresRoot, component);
|
|
2652
2652
|
await fs.ensureDir(componentDir);
|
|
2653
|
-
const readmePath =
|
|
2653
|
+
const readmePath = path14.join(componentDir, "README.md");
|
|
2654
2654
|
if (!await fs.pathExists(readmePath)) {
|
|
2655
2655
|
await fs.writeFile(
|
|
2656
2656
|
readmePath,
|
|
@@ -2711,20 +2711,20 @@ async function runInit(options) {
|
|
|
2711
2711
|
config.projectRoot = projectRoot;
|
|
2712
2712
|
}
|
|
2713
2713
|
}
|
|
2714
|
-
const configPath =
|
|
2714
|
+
const configPath = path14.join(targetDir, ".lee-spec-kit.json");
|
|
2715
2715
|
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
2716
2716
|
const extraCommitPathsAbs = [];
|
|
2717
2717
|
try {
|
|
2718
2718
|
if (docsRepo === "embedded") {
|
|
2719
2719
|
const repoRoot = getGitTopLevelOrNull(cwd) || cwd;
|
|
2720
|
-
const agentsMdPath =
|
|
2720
|
+
const agentsMdPath = path14.join(repoRoot, "AGENTS.md");
|
|
2721
2721
|
const result = await upsertLeeSpecKitAgentsMd(agentsMdPath, {
|
|
2722
2722
|
lang,
|
|
2723
2723
|
docsRepo
|
|
2724
2724
|
});
|
|
2725
2725
|
if (result.changed) extraCommitPathsAbs.push(agentsMdPath);
|
|
2726
2726
|
} else {
|
|
2727
|
-
await upsertLeeSpecKitAgentsMd(
|
|
2727
|
+
await upsertLeeSpecKitAgentsMd(path14.join(targetDir, "AGENTS.md"), {
|
|
2728
2728
|
lang,
|
|
2729
2729
|
docsRepo
|
|
2730
2730
|
});
|
|
@@ -2734,16 +2734,16 @@ async function runInit(options) {
|
|
|
2734
2734
|
} else if (projectRoot && typeof projectRoot === "object") {
|
|
2735
2735
|
roots.push(...Object.values(projectRoot));
|
|
2736
2736
|
}
|
|
2737
|
-
const resolvedCwd =
|
|
2737
|
+
const resolvedCwd = path14.resolve(cwd);
|
|
2738
2738
|
for (const raw of roots) {
|
|
2739
2739
|
const value = String(raw || "").trim();
|
|
2740
2740
|
if (!value) continue;
|
|
2741
|
-
const abs =
|
|
2742
|
-
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${
|
|
2741
|
+
const abs = path14.resolve(cwd, value);
|
|
2742
|
+
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path14.sep}`)) {
|
|
2743
2743
|
if (await fs.pathExists(abs)) {
|
|
2744
2744
|
const stat = await fs.stat(abs);
|
|
2745
2745
|
if (stat.isDirectory()) {
|
|
2746
|
-
await upsertLeeSpecKitAgentsMd(
|
|
2746
|
+
await upsertLeeSpecKitAgentsMd(path14.join(abs, "AGENTS.md"), {
|
|
2747
2747
|
lang,
|
|
2748
2748
|
docsRepo
|
|
2749
2749
|
});
|
|
@@ -2833,7 +2833,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2833
2833
|
console.log(chalk9.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
2834
2834
|
runGitOrThrow(["init"], gitWorkdir);
|
|
2835
2835
|
}
|
|
2836
|
-
const relativePath = docsRepo === "standalone" ? "." :
|
|
2836
|
+
const relativePath = docsRepo === "standalone" ? "." : path14.relative(gitWorkdir, targetDir);
|
|
2837
2837
|
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
2838
2838
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
2839
2839
|
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.stagedChangesSkip")));
|
|
@@ -2860,7 +2860,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2860
2860
|
console.log();
|
|
2861
2861
|
return;
|
|
2862
2862
|
}
|
|
2863
|
-
const extraRelativePaths = extraCommitPathsAbs.map((absPath) =>
|
|
2863
|
+
const extraRelativePaths = extraCommitPathsAbs.map((absPath) => path14.relative(gitWorkdir, absPath)).map((p) => p.replace(/\\/g, "/").trim()).filter((p) => !!p && p !== "." && !p.startsWith("../"));
|
|
2864
2864
|
const pathsToStage = [relativePath, ...extraRelativePaths];
|
|
2865
2865
|
for (const p of pathsToStage) {
|
|
2866
2866
|
runGitOrThrow(["add", p], gitWorkdir);
|
|
@@ -2896,17 +2896,17 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
2896
2896
|
}
|
|
2897
2897
|
function getAncestorDirs(startDir) {
|
|
2898
2898
|
const dirs = [];
|
|
2899
|
-
let current =
|
|
2899
|
+
let current = path14.resolve(startDir);
|
|
2900
2900
|
while (true) {
|
|
2901
2901
|
dirs.push(current);
|
|
2902
|
-
const parent =
|
|
2902
|
+
const parent = path14.dirname(current);
|
|
2903
2903
|
if (parent === current) break;
|
|
2904
2904
|
current = parent;
|
|
2905
2905
|
}
|
|
2906
2906
|
return dirs;
|
|
2907
2907
|
}
|
|
2908
2908
|
function hasWorkspaceBoundary(dir) {
|
|
2909
|
-
return fs.existsSync(
|
|
2909
|
+
return fs.existsSync(path14.join(dir, "package.json")) || fs.existsSync(path14.join(dir, ".git"));
|
|
2910
2910
|
}
|
|
2911
2911
|
function getSearchBaseDirs(cwd) {
|
|
2912
2912
|
const ancestors = getAncestorDirs(cwd);
|
|
@@ -2922,7 +2922,7 @@ function normalizeComponentKeys(value) {
|
|
|
2922
2922
|
return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
|
|
2923
2923
|
}
|
|
2924
2924
|
async function inferComponentsFromFeaturesDir(docsDir) {
|
|
2925
|
-
const featuresPath =
|
|
2925
|
+
const featuresPath = path14.join(docsDir, "features");
|
|
2926
2926
|
if (!await fs.pathExists(featuresPath)) return [];
|
|
2927
2927
|
const entries = await fs.readdir(featuresPath, { withFileTypes: true });
|
|
2928
2928
|
const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
|
|
@@ -2933,21 +2933,21 @@ async function inferComponentsFromFeaturesDir(docsDir) {
|
|
|
2933
2933
|
async function getConfig(cwd) {
|
|
2934
2934
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2935
2935
|
const baseDirs = [
|
|
2936
|
-
...explicitDocsDir ? [
|
|
2936
|
+
...explicitDocsDir ? [path14.resolve(explicitDocsDir)] : [],
|
|
2937
2937
|
...getSearchBaseDirs(cwd)
|
|
2938
2938
|
];
|
|
2939
2939
|
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
2940
2940
|
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
2941
2941
|
for (const baseDir of baseDirs) {
|
|
2942
|
-
const resolvedBaseDir =
|
|
2942
|
+
const resolvedBaseDir = path14.resolve(baseDir);
|
|
2943
2943
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
2944
2944
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
2945
|
-
const possibleDocsDirs = [
|
|
2945
|
+
const possibleDocsDirs = [path14.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2946
2946
|
for (const docsDir of possibleDocsDirs) {
|
|
2947
|
-
const resolvedDocsDir =
|
|
2947
|
+
const resolvedDocsDir = path14.resolve(docsDir);
|
|
2948
2948
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2949
2949
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2950
|
-
const configPath =
|
|
2950
|
+
const configPath = path14.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
2951
2951
|
if (await fs.pathExists(configPath)) {
|
|
2952
2952
|
try {
|
|
2953
2953
|
const configFile = await fs.readJson(configPath);
|
|
@@ -2977,16 +2977,16 @@ async function getConfig(cwd) {
|
|
|
2977
2977
|
} catch {
|
|
2978
2978
|
}
|
|
2979
2979
|
}
|
|
2980
|
-
const agentsPath =
|
|
2981
|
-
const featuresPath =
|
|
2980
|
+
const agentsPath = path14.join(resolvedDocsDir, "agents");
|
|
2981
|
+
const featuresPath = path14.join(resolvedDocsDir, "features");
|
|
2982
2982
|
if (await fs.pathExists(agentsPath) && await fs.pathExists(featuresPath)) {
|
|
2983
2983
|
const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
|
|
2984
2984
|
const projectType = inferredComponents.length > 0 ? "multi" : "single";
|
|
2985
2985
|
const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
|
|
2986
2986
|
const langProbeCandidates = [
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2987
|
+
path14.join(agentsPath, "custom.md"),
|
|
2988
|
+
path14.join(agentsPath, "constitution.md"),
|
|
2989
|
+
path14.join(agentsPath, "agents.md")
|
|
2990
2990
|
];
|
|
2991
2991
|
let lang = "en";
|
|
2992
2992
|
for (const candidate of langProbeCandidates) {
|
|
@@ -3057,13 +3057,85 @@ async function patchMarkdownIfExists(filePath, transform) {
|
|
|
3057
3057
|
await fs.writeFile(filePath, transform(content), "utf-8");
|
|
3058
3058
|
}
|
|
3059
3059
|
async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
|
|
3060
|
-
await patchMarkdownIfExists(
|
|
3060
|
+
await patchMarkdownIfExists(path14.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
3061
3061
|
await patchMarkdownIfExists(
|
|
3062
|
-
|
|
3062
|
+
path14.join(featureDir, "tasks.md"),
|
|
3063
3063
|
(content) => sanitizeTasksForLocal(content, lang)
|
|
3064
3064
|
);
|
|
3065
|
-
await fs.remove(
|
|
3066
|
-
await fs.remove(
|
|
3065
|
+
await fs.remove(path14.join(featureDir, "issue.md"));
|
|
3066
|
+
await fs.remove(path14.join(featureDir, "pr.md"));
|
|
3067
|
+
}
|
|
3068
|
+
var IDEA_REF_PATTERN = /\b(I\d{3,}(?:-[A-Za-z0-9._-]+)?)\b/;
|
|
3069
|
+
var IDEA_PATH_PATTERN = /\b(?:\.\/)?docs\/ideas\/[^\s]+\.md\b/;
|
|
3070
|
+
function extractExplicitIdeaRef(requestText) {
|
|
3071
|
+
const pathMatch = requestText.match(IDEA_PATH_PATTERN);
|
|
3072
|
+
if (pathMatch) return pathMatch[0];
|
|
3073
|
+
const refMatch = requestText.match(IDEA_REF_PATTERN);
|
|
3074
|
+
if (refMatch) return refMatch[1];
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
async function resolveIdeaReference(docsDir, ref, lang) {
|
|
3078
|
+
const ideasDir = path14.join(docsDir, "ideas");
|
|
3079
|
+
const trimmedRef = ref.trim();
|
|
3080
|
+
if (!trimmedRef) {
|
|
3081
|
+
throw createCliError(
|
|
3082
|
+
"INVALID_ARGUMENT",
|
|
3083
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref })
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
|
|
3087
|
+
const candidate = path14.resolve(process.cwd(), trimmedRef);
|
|
3088
|
+
if (await fs.pathExists(candidate)) {
|
|
3089
|
+
return { path: candidate };
|
|
3090
|
+
}
|
|
3091
|
+
throw createCliError(
|
|
3092
|
+
"INVALID_ARGUMENT",
|
|
3093
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
if (!await fs.pathExists(ideasDir)) {
|
|
3097
|
+
throw createCliError(
|
|
3098
|
+
"INVALID_ARGUMENT",
|
|
3099
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3100
|
+
);
|
|
3101
|
+
}
|
|
3102
|
+
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
3103
|
+
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
|
|
3104
|
+
const exactName = `${trimmedRef}.md`;
|
|
3105
|
+
if (files.includes(exactName)) {
|
|
3106
|
+
return { path: path14.join(ideasDir, exactName) };
|
|
3107
|
+
}
|
|
3108
|
+
const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
|
|
3109
|
+
if (byId.length === 1) {
|
|
3110
|
+
return { path: path14.join(ideasDir, byId[0]) };
|
|
3111
|
+
}
|
|
3112
|
+
if (byId.length > 1) {
|
|
3113
|
+
throw createCliError(
|
|
3114
|
+
"INVALID_ARGUMENT",
|
|
3115
|
+
tr(lang, "cli", "feature.ideaAmbiguous", { ref: trimmedRef })
|
|
3116
|
+
);
|
|
3117
|
+
}
|
|
3118
|
+
throw createCliError(
|
|
3119
|
+
"INVALID_ARGUMENT",
|
|
3120
|
+
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3121
|
+
);
|
|
3122
|
+
}
|
|
3123
|
+
async function readIdeaMetadataValue(ideaPath, label) {
|
|
3124
|
+
const content = await fs.readFile(ideaPath, "utf-8");
|
|
3125
|
+
const pattern = new RegExp(`^- \\*\\*${escapeRegExp(label)}\\*\\*:\\s*(.+)$`, "m");
|
|
3126
|
+
const match = content.match(pattern);
|
|
3127
|
+
if (!match) return null;
|
|
3128
|
+
const value = match[1].trim();
|
|
3129
|
+
return value.length > 0 ? value : null;
|
|
3130
|
+
}
|
|
3131
|
+
async function deriveFeatureNameFromIdea(ideaPath) {
|
|
3132
|
+
const ideaName = await readIdeaMetadataValue(ideaPath, "Idea Name");
|
|
3133
|
+
if (ideaName && ideaName !== "-") return ideaName;
|
|
3134
|
+
const basename = path14.basename(ideaPath, ".md");
|
|
3135
|
+
return basename.replace(/^I\d{3,}-/, "");
|
|
3136
|
+
}
|
|
3137
|
+
function escapeRegExp(value) {
|
|
3138
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3067
3139
|
}
|
|
3068
3140
|
|
|
3069
3141
|
// src/commands/feature.ts
|
|
@@ -3215,19 +3287,19 @@ async function runFeature(name, options) {
|
|
|
3215
3287
|
}
|
|
3216
3288
|
let featuresDir;
|
|
3217
3289
|
if (projectType === "multi") {
|
|
3218
|
-
featuresDir =
|
|
3290
|
+
featuresDir = path14.join(docsDir, "features", component);
|
|
3219
3291
|
} else {
|
|
3220
|
-
featuresDir =
|
|
3292
|
+
featuresDir = path14.join(docsDir, "features");
|
|
3221
3293
|
}
|
|
3222
3294
|
const featureFolderName = `${featureId}-${name}`;
|
|
3223
|
-
const featureDir =
|
|
3295
|
+
const featureDir = path14.join(featuresDir, featureFolderName);
|
|
3224
3296
|
if (await fs.pathExists(featureDir)) {
|
|
3225
3297
|
throw createCliError(
|
|
3226
3298
|
"INVALID_ARGUMENT",
|
|
3227
3299
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
3228
3300
|
);
|
|
3229
3301
|
}
|
|
3230
|
-
const featureBasePath =
|
|
3302
|
+
const featureBasePath = path14.join(
|
|
3231
3303
|
getTemplatesDir(),
|
|
3232
3304
|
lang,
|
|
3233
3305
|
"common",
|
|
@@ -3274,8 +3346,8 @@ async function runFeature(name, options) {
|
|
|
3274
3346
|
await replaceInFiles(fsAdapter, featureDir, replacements);
|
|
3275
3347
|
if (linkedIdea) {
|
|
3276
3348
|
await stampIdeaReferenceInSpec(
|
|
3277
|
-
|
|
3278
|
-
|
|
3349
|
+
path14.join(featureDir, "spec.md"),
|
|
3350
|
+
path14.relative(featureDir, linkedIdea.path)
|
|
3279
3351
|
);
|
|
3280
3352
|
await markIdeaAsFeatureized(linkedIdea.path, featureFolderName);
|
|
3281
3353
|
}
|
|
@@ -3303,58 +3375,12 @@ async function runFeature(name, options) {
|
|
|
3303
3375
|
featureName: name,
|
|
3304
3376
|
component: projectType === "multi" ? component : void 0,
|
|
3305
3377
|
featurePath: featureDir,
|
|
3306
|
-
featurePathFromDocs:
|
|
3378
|
+
featurePathFromDocs: path14.relative(docsDir, featureDir)
|
|
3307
3379
|
};
|
|
3308
3380
|
},
|
|
3309
3381
|
{ owner: "feature" }
|
|
3310
3382
|
);
|
|
3311
3383
|
}
|
|
3312
|
-
async function resolveIdeaReference(docsDir, ref, lang) {
|
|
3313
|
-
const ideasDir = path13.join(docsDir, "ideas");
|
|
3314
|
-
const trimmedRef = ref.trim();
|
|
3315
|
-
if (!trimmedRef) {
|
|
3316
|
-
throw createCliError(
|
|
3317
|
-
"INVALID_ARGUMENT",
|
|
3318
|
-
tr(lang, "cli", "feature.ideaNotFound", { ref })
|
|
3319
|
-
);
|
|
3320
|
-
}
|
|
3321
|
-
if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
|
|
3322
|
-
const candidate = path13.resolve(process.cwd(), trimmedRef);
|
|
3323
|
-
if (await fs.pathExists(candidate)) {
|
|
3324
|
-
return { path: candidate };
|
|
3325
|
-
}
|
|
3326
|
-
throw createCliError(
|
|
3327
|
-
"INVALID_ARGUMENT",
|
|
3328
|
-
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3329
|
-
);
|
|
3330
|
-
}
|
|
3331
|
-
if (!await fs.pathExists(ideasDir)) {
|
|
3332
|
-
throw createCliError(
|
|
3333
|
-
"INVALID_ARGUMENT",
|
|
3334
|
-
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3335
|
-
);
|
|
3336
|
-
}
|
|
3337
|
-
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
3338
|
-
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
|
|
3339
|
-
const exactName = `${trimmedRef}.md`;
|
|
3340
|
-
if (files.includes(exactName)) {
|
|
3341
|
-
return { path: path13.join(ideasDir, exactName) };
|
|
3342
|
-
}
|
|
3343
|
-
const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
|
|
3344
|
-
if (byId.length === 1) {
|
|
3345
|
-
return { path: path13.join(ideasDir, byId[0]) };
|
|
3346
|
-
}
|
|
3347
|
-
if (byId.length > 1) {
|
|
3348
|
-
throw createCliError(
|
|
3349
|
-
"INVALID_ARGUMENT",
|
|
3350
|
-
tr(lang, "cli", "feature.ideaAmbiguous", { ref: trimmedRef })
|
|
3351
|
-
);
|
|
3352
|
-
}
|
|
3353
|
-
throw createCliError(
|
|
3354
|
-
"INVALID_ARGUMENT",
|
|
3355
|
-
tr(lang, "cli", "feature.ideaNotFound", { ref: trimmedRef })
|
|
3356
|
-
);
|
|
3357
|
-
}
|
|
3358
3384
|
async function stampIdeaReferenceInSpec(specPath, relativeIdeaPath) {
|
|
3359
3385
|
const normalizedPath = relativeIdeaPath.replace(/\\/g, "/");
|
|
3360
3386
|
const ideaLine = `- Idea: \`${normalizedPath}\``;
|
|
@@ -3385,7 +3411,7 @@ async function markIdeaAsFeatureized(ideaPath, featureFolderName) {
|
|
|
3385
3411
|
await fs.writeFile(ideaPath, content, "utf-8");
|
|
3386
3412
|
}
|
|
3387
3413
|
function replaceOrAppendIdeaMetadata(content, label, value) {
|
|
3388
|
-
const pattern = new RegExp(`^- \\*\\*${
|
|
3414
|
+
const pattern = new RegExp(`^- \\*\\*${escapeRegExp2(label)}\\*\\*:.*$`, "m");
|
|
3389
3415
|
const line = `- **${label}**: ${value}`;
|
|
3390
3416
|
if (pattern.test(content)) {
|
|
3391
3417
|
return content.replace(pattern, line);
|
|
@@ -3403,15 +3429,15 @@ ${heading}
|
|
|
3403
3429
|
${line}
|
|
3404
3430
|
`;
|
|
3405
3431
|
}
|
|
3406
|
-
function
|
|
3432
|
+
function escapeRegExp2(value) {
|
|
3407
3433
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3408
3434
|
}
|
|
3409
3435
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
3410
3436
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
3411
3437
|
const candidates = [
|
|
3412
|
-
...explicitDocsDir ? [
|
|
3413
|
-
|
|
3414
|
-
|
|
3438
|
+
...explicitDocsDir ? [path14.resolve(explicitDocsDir)] : [],
|
|
3439
|
+
path14.resolve(cwd, "docs"),
|
|
3440
|
+
path14.resolve(cwd)
|
|
3415
3441
|
];
|
|
3416
3442
|
const endAt = Date.now() + timeoutMs;
|
|
3417
3443
|
while (Date.now() < endAt) {
|
|
@@ -3438,12 +3464,12 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
3438
3464
|
return getConfig(cwd);
|
|
3439
3465
|
}
|
|
3440
3466
|
async function getNextFeatureId(docsDir, projectType, components) {
|
|
3441
|
-
const featuresDir =
|
|
3467
|
+
const featuresDir = path14.join(docsDir, "features");
|
|
3442
3468
|
let max = 0;
|
|
3443
3469
|
const scanDirs = [];
|
|
3444
3470
|
if (projectType === "multi") {
|
|
3445
3471
|
scanDirs.push(
|
|
3446
|
-
...components.map((component) =>
|
|
3472
|
+
...components.map((component) => path14.join(featuresDir, component))
|
|
3447
3473
|
);
|
|
3448
3474
|
} else {
|
|
3449
3475
|
scanDirs.push(featuresDir);
|
|
@@ -3544,16 +3570,16 @@ async function runIdea(name, options) {
|
|
|
3544
3570
|
getDocsLockPath(docsDir),
|
|
3545
3571
|
async () => {
|
|
3546
3572
|
const ideaId = options.id ? validateProvidedIdeaId(options.id, lang) : await getNextIdeaId(docsDir);
|
|
3547
|
-
const ideasDir =
|
|
3573
|
+
const ideasDir = path14.join(docsDir, "ideas");
|
|
3548
3574
|
const ideaFileName = `${ideaId}-${name}.md`;
|
|
3549
|
-
const ideaPath =
|
|
3575
|
+
const ideaPath = path14.join(ideasDir, ideaFileName);
|
|
3550
3576
|
if (await fs.pathExists(ideaPath)) {
|
|
3551
3577
|
throw createCliError(
|
|
3552
3578
|
"INVALID_ARGUMENT",
|
|
3553
3579
|
tr(lang, "cli", "idea.fileExists", { path: ideaPath })
|
|
3554
3580
|
);
|
|
3555
3581
|
}
|
|
3556
|
-
const templatePath =
|
|
3582
|
+
const templatePath = path14.join(
|
|
3557
3583
|
getTemplatesDir(),
|
|
3558
3584
|
lang,
|
|
3559
3585
|
"common",
|
|
@@ -3591,7 +3617,7 @@ async function runIdea(name, options) {
|
|
|
3591
3617
|
ideaName: name,
|
|
3592
3618
|
component: component || void 0,
|
|
3593
3619
|
ideaPath,
|
|
3594
|
-
ideaPathFromDocs:
|
|
3620
|
+
ideaPathFromDocs: path14.relative(docsDir, ideaPath)
|
|
3595
3621
|
};
|
|
3596
3622
|
},
|
|
3597
3623
|
{ owner: "idea" }
|
|
@@ -3609,7 +3635,7 @@ function applyIdeaTemplate(template, values) {
|
|
|
3609
3635
|
return template.replaceAll("{idea-id}", values.ideaId).replaceAll("{idea-name}", values.name).replaceAll("{YYYY-MM-DD}", values.created).replaceAll("{{description}}", values.description).replaceAll("{component}", values.component);
|
|
3610
3636
|
}
|
|
3611
3637
|
async function getNextIdeaId(docsDir) {
|
|
3612
|
-
const ideasDir =
|
|
3638
|
+
const ideasDir = path14.join(docsDir, "ideas");
|
|
3613
3639
|
let max = 0;
|
|
3614
3640
|
if (await fs.pathExists(ideasDir)) {
|
|
3615
3641
|
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
@@ -4048,7 +4074,7 @@ function isNonNegativeIntegerValue(value) {
|
|
|
4048
4074
|
}
|
|
4049
4075
|
function isLikelyCurrentPrePrReviewEvidence(evidencePath, feature) {
|
|
4050
4076
|
try {
|
|
4051
|
-
const raw =
|
|
4077
|
+
const raw = fs11.readFileSync(evidencePath, "utf-8");
|
|
4052
4078
|
const parsed = JSON.parse(raw);
|
|
4053
4079
|
const evidenceFeature = (parsed.feature || "").toString().trim();
|
|
4054
4080
|
if (evidenceFeature && evidenceFeature !== feature.folderName) {
|
|
@@ -4064,31 +4090,31 @@ function resolvePrePrReviewEvidencePath(feature) {
|
|
|
4064
4090
|
const candidates = [];
|
|
4065
4091
|
const explicit = (feature.prePrReview.evidence || "").trim();
|
|
4066
4092
|
if (explicit && explicit !== "-") {
|
|
4067
|
-
if (
|
|
4093
|
+
if (path14.isAbsolute(explicit)) {
|
|
4068
4094
|
candidates.push(explicit);
|
|
4069
4095
|
} else {
|
|
4070
|
-
candidates.push(
|
|
4071
|
-
candidates.push(
|
|
4096
|
+
candidates.push(path14.resolve(feature.path, explicit));
|
|
4097
|
+
candidates.push(path14.resolve(docsRoot, explicit));
|
|
4072
4098
|
const normalizedExplicit = explicit.replace(/\\/g, "/");
|
|
4073
4099
|
if (normalizedExplicit.startsWith("docs/")) {
|
|
4074
4100
|
const withoutDocsPrefix = normalizedExplicit.slice("docs/".length);
|
|
4075
4101
|
if (withoutDocsPrefix) {
|
|
4076
|
-
candidates.push(
|
|
4102
|
+
candidates.push(path14.resolve(docsRoot, withoutDocsPrefix));
|
|
4077
4103
|
}
|
|
4078
4104
|
}
|
|
4079
4105
|
}
|
|
4080
4106
|
}
|
|
4081
|
-
candidates.push(
|
|
4082
|
-
candidates.push(
|
|
4107
|
+
candidates.push(path14.join(feature.path, "review-trace.json"));
|
|
4108
|
+
candidates.push(path14.join(docsRoot, "review-trace.json"));
|
|
4083
4109
|
const seen = /* @__PURE__ */ new Set();
|
|
4084
4110
|
for (const candidate of candidates) {
|
|
4085
|
-
const abs =
|
|
4111
|
+
const abs = path14.resolve(candidate);
|
|
4086
4112
|
if (seen.has(abs)) continue;
|
|
4087
4113
|
seen.add(abs);
|
|
4088
|
-
if (!
|
|
4114
|
+
if (!fs11.existsSync(abs)) continue;
|
|
4089
4115
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
4090
4116
|
if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
|
|
4091
|
-
const rel =
|
|
4117
|
+
const rel = path14.relative(docsRoot, abs).replace(/\\/g, "/");
|
|
4092
4118
|
if (rel && !rel.startsWith("../")) {
|
|
4093
4119
|
return rel;
|
|
4094
4120
|
}
|
|
@@ -4129,8 +4155,8 @@ function getReviewFixCommitGuidance(feature, lang, options) {
|
|
|
4129
4155
|
}
|
|
4130
4156
|
function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
|
|
4131
4157
|
if (!projectGitCwd) return null;
|
|
4132
|
-
const normalized =
|
|
4133
|
-
const marker = `${
|
|
4158
|
+
const normalized = path14.resolve(projectGitCwd);
|
|
4159
|
+
const marker = `${path14.sep}.worktrees${path14.sep}`;
|
|
4134
4160
|
const markerIndex = normalized.lastIndexOf(marker);
|
|
4135
4161
|
if (markerIndex <= 0) return null;
|
|
4136
4162
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -4173,7 +4199,7 @@ function toTaskKey(rawTitle) {
|
|
|
4173
4199
|
function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
|
|
4174
4200
|
const docsGitCwd = feature.git.docsGitCwd;
|
|
4175
4201
|
const tasksRelativePath = normalizeGitRelativePath(
|
|
4176
|
-
|
|
4202
|
+
path14.join(feature.docs.featurePathFromDocs, "tasks.md")
|
|
4177
4203
|
);
|
|
4178
4204
|
const diff = readGitText(ctx, docsGitCwd, [
|
|
4179
4205
|
"diff",
|
|
@@ -4248,7 +4274,7 @@ function checkTaskCommitGate(ctx, feature) {
|
|
|
4248
4274
|
return { pass: true };
|
|
4249
4275
|
}
|
|
4250
4276
|
const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
|
|
4251
|
-
const relativeDocsDir =
|
|
4277
|
+
const relativeDocsDir = path14.relative(projectGitCwd, feature.git.docsGitCwd);
|
|
4252
4278
|
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
4253
4279
|
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
4254
4280
|
args.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
@@ -5864,17 +5890,17 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
|
|
|
5864
5890
|
}
|
|
5865
5891
|
}
|
|
5866
5892
|
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
5867
|
-
var WORKTREE_MARKER = `${
|
|
5893
|
+
var WORKTREE_MARKER = `${path14.sep}.worktrees${path14.sep}`;
|
|
5868
5894
|
function resetContextGitCaches() {
|
|
5869
5895
|
GIT_WORKTREE_CACHE.clear();
|
|
5870
5896
|
}
|
|
5871
5897
|
function isManagedWorktreePath(cwd) {
|
|
5872
5898
|
if (!cwd) return false;
|
|
5873
|
-
const normalized =
|
|
5899
|
+
const normalized = path14.resolve(cwd);
|
|
5874
5900
|
return normalized.includes(WORKTREE_MARKER);
|
|
5875
5901
|
}
|
|
5876
5902
|
function resolveProjectRootFromGitCwd(cwd) {
|
|
5877
|
-
const normalized =
|
|
5903
|
+
const normalized = path14.resolve(cwd);
|
|
5878
5904
|
const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
|
|
5879
5905
|
if (markerIndex <= 0) return normalized;
|
|
5880
5906
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -5893,7 +5919,7 @@ function getGitTopLevel(ctx, cwd) {
|
|
|
5893
5919
|
}
|
|
5894
5920
|
function listGitWorktrees(ctx, cwd) {
|
|
5895
5921
|
const topLevel = getGitTopLevel(ctx, cwd) || cwd;
|
|
5896
|
-
const cacheKey =
|
|
5922
|
+
const cacheKey = path14.resolve(topLevel);
|
|
5897
5923
|
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
5898
5924
|
if (cached) return cached;
|
|
5899
5925
|
try {
|
|
@@ -5994,12 +6020,12 @@ function countDocumentLines(content) {
|
|
|
5994
6020
|
if (lines[lines.length - 1] === "") return lines.length - 1;
|
|
5995
6021
|
return lines.length;
|
|
5996
6022
|
}
|
|
5997
|
-
function
|
|
6023
|
+
function escapeRegExp3(value) {
|
|
5998
6024
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5999
6025
|
}
|
|
6000
6026
|
function extractSpecValue(content, key) {
|
|
6001
6027
|
const regex = new RegExp(
|
|
6002
|
-
`^\\s*-\\s*\\*\\*${
|
|
6028
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
6003
6029
|
"m"
|
|
6004
6030
|
);
|
|
6005
6031
|
const match = content.match(regex);
|
|
@@ -6007,7 +6033,7 @@ function extractSpecValue(content, key) {
|
|
|
6007
6033
|
}
|
|
6008
6034
|
function hasSpecKey(content, key) {
|
|
6009
6035
|
const regex = new RegExp(
|
|
6010
|
-
`^\\s*-\\s*\\*\\*${
|
|
6036
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp3(key)}\\*\\*\\s*:`,
|
|
6011
6037
|
"m"
|
|
6012
6038
|
);
|
|
6013
6039
|
return regex.test(content);
|
|
@@ -6199,7 +6225,7 @@ function splitReviewLogSections(content, headerRegex) {
|
|
|
6199
6225
|
}
|
|
6200
6226
|
function collectStructuredReviewEntries(section, keys) {
|
|
6201
6227
|
const lines = section.split("\n");
|
|
6202
|
-
const escaped = keys.map((key) =>
|
|
6228
|
+
const escaped = keys.map((key) => escapeRegExp3(key));
|
|
6203
6229
|
const fieldRegex = new RegExp(
|
|
6204
6230
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*(.*)$`,
|
|
6205
6231
|
"i"
|
|
@@ -6381,17 +6407,17 @@ function resolveLocalEvidencePathCandidates(rawValue, context) {
|
|
|
6381
6407
|
if (!evidencePath) return [];
|
|
6382
6408
|
if (/^https?:\/\//i.test(evidencePath)) return [];
|
|
6383
6409
|
const candidates = /* @__PURE__ */ new Set();
|
|
6384
|
-
if (
|
|
6385
|
-
candidates.add(
|
|
6410
|
+
if (path14.isAbsolute(evidencePath)) {
|
|
6411
|
+
candidates.add(path14.resolve(evidencePath));
|
|
6386
6412
|
} else {
|
|
6387
|
-
candidates.add(
|
|
6388
|
-
candidates.add(
|
|
6389
|
-
candidates.add(
|
|
6413
|
+
candidates.add(path14.resolve(context.featurePath, evidencePath));
|
|
6414
|
+
candidates.add(path14.resolve(context.docsDir, evidencePath));
|
|
6415
|
+
candidates.add(path14.resolve(path14.dirname(context.docsDir), evidencePath));
|
|
6390
6416
|
const normalizedEvidencePath = evidencePath.replace(/\\/g, "/");
|
|
6391
6417
|
if (normalizedEvidencePath.startsWith("docs/")) {
|
|
6392
6418
|
const withoutDocsPrefix = normalizedEvidencePath.slice("docs/".length);
|
|
6393
6419
|
if (withoutDocsPrefix) {
|
|
6394
|
-
candidates.add(
|
|
6420
|
+
candidates.add(path14.resolve(context.docsDir, withoutDocsPrefix));
|
|
6395
6421
|
}
|
|
6396
6422
|
}
|
|
6397
6423
|
}
|
|
@@ -6453,13 +6479,13 @@ function parsePrLink(value) {
|
|
|
6453
6479
|
return trimmed;
|
|
6454
6480
|
}
|
|
6455
6481
|
function normalizeGitPath(value) {
|
|
6456
|
-
return value.split(
|
|
6482
|
+
return value.split(path14.sep).join("/");
|
|
6457
6483
|
}
|
|
6458
6484
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
6459
|
-
const relativeDocsDir =
|
|
6485
|
+
const relativeDocsDir = path14.relative(projectGitCwd, docsDir);
|
|
6460
6486
|
if (!relativeDocsDir) return [];
|
|
6461
|
-
if (
|
|
6462
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
6487
|
+
if (path14.isAbsolute(relativeDocsDir)) return [];
|
|
6488
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path14.sep}`)) {
|
|
6463
6489
|
return [];
|
|
6464
6490
|
}
|
|
6465
6491
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(
|
|
@@ -6497,7 +6523,7 @@ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderN
|
|
|
6497
6523
|
const seen = /* @__PURE__ */ new Set();
|
|
6498
6524
|
const out = [];
|
|
6499
6525
|
for (const name of names) {
|
|
6500
|
-
const candidate =
|
|
6526
|
+
const candidate = path14.resolve(projectRoot, ".worktrees", name);
|
|
6501
6527
|
if (seen.has(candidate)) continue;
|
|
6502
6528
|
seen.add(candidate);
|
|
6503
6529
|
out.push(candidate);
|
|
@@ -6511,7 +6537,7 @@ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, f
|
|
|
6511
6537
|
slug,
|
|
6512
6538
|
folderName
|
|
6513
6539
|
)) {
|
|
6514
|
-
if (!
|
|
6540
|
+
if (!fs11.existsSync(candidate)) continue;
|
|
6515
6541
|
return candidate;
|
|
6516
6542
|
}
|
|
6517
6543
|
return void 0;
|
|
@@ -6541,7 +6567,7 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
|
|
|
6541
6567
|
slug,
|
|
6542
6568
|
folderName
|
|
6543
6569
|
)) {
|
|
6544
|
-
if (!
|
|
6570
|
+
if (!fs11.existsSync(candidate)) continue;
|
|
6545
6571
|
const branchName = getCurrentBranch(ctx, candidate);
|
|
6546
6572
|
if (!expectedBranchesSet.has(branchName)) continue;
|
|
6547
6573
|
return {
|
|
@@ -6682,10 +6708,10 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6682
6708
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
6683
6709
|
candidates.map((candidate) => {
|
|
6684
6710
|
if (!candidate) return "";
|
|
6685
|
-
if (!
|
|
6686
|
-
const relative =
|
|
6711
|
+
if (!path14.isAbsolute(candidate)) return candidate;
|
|
6712
|
+
const relative = path14.relative(projectGitCwd, candidate);
|
|
6687
6713
|
if (!relative) return "";
|
|
6688
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
6714
|
+
if (relative === ".." || relative.startsWith(`..${path14.sep}`))
|
|
6689
6715
|
return "";
|
|
6690
6716
|
return relative;
|
|
6691
6717
|
}).filter(Boolean)
|
|
@@ -6699,7 +6725,7 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6699
6725
|
if (cached) return [...cached];
|
|
6700
6726
|
const existing = [];
|
|
6701
6727
|
for (const candidate of normalizedCandidates) {
|
|
6702
|
-
if (await ctx.fs.pathExists(
|
|
6728
|
+
if (await ctx.fs.pathExists(path14.join(projectGitCwd, candidate))) {
|
|
6703
6729
|
existing.push(candidate);
|
|
6704
6730
|
}
|
|
6705
6731
|
}
|
|
@@ -6787,16 +6813,16 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
6787
6813
|
const lang = options.lang;
|
|
6788
6814
|
const workflowPolicy = resolveWorkflowPolicy(options.workflow);
|
|
6789
6815
|
const prePrReviewPolicy = resolvePrePrReviewPolicy(options.workflow);
|
|
6790
|
-
const folderName =
|
|
6816
|
+
const folderName = path14.basename(featurePath);
|
|
6791
6817
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
6792
6818
|
const id = match?.[1];
|
|
6793
6819
|
const slug = match?.[2] || folderName;
|
|
6794
|
-
const specPath =
|
|
6795
|
-
const planPath =
|
|
6796
|
-
const tasksPath =
|
|
6797
|
-
const decisionsPath =
|
|
6798
|
-
const issueDocPath =
|
|
6799
|
-
const prDocPath =
|
|
6820
|
+
const specPath = path14.join(featurePath, "spec.md");
|
|
6821
|
+
const planPath = path14.join(featurePath, "plan.md");
|
|
6822
|
+
const tasksPath = path14.join(featurePath, "tasks.md");
|
|
6823
|
+
const decisionsPath = path14.join(featurePath, "decisions.md");
|
|
6824
|
+
const issueDocPath = path14.join(featurePath, "issue.md");
|
|
6825
|
+
const prDocPath = path14.join(featurePath, "pr.md");
|
|
6800
6826
|
let specStatus;
|
|
6801
6827
|
let issueNumber;
|
|
6802
6828
|
const specExists = await ctx.fs.pathExists(specPath);
|
|
@@ -7058,7 +7084,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7058
7084
|
} else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
|
|
7059
7085
|
warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
|
|
7060
7086
|
}
|
|
7061
|
-
const relativeFeaturePathFromDocs =
|
|
7087
|
+
const relativeFeaturePathFromDocs = path14.relative(
|
|
7062
7088
|
context.docsDir,
|
|
7063
7089
|
featurePath
|
|
7064
7090
|
);
|
|
@@ -7421,7 +7447,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7421
7447
|
async function listFeatureDirs(ctx, rootDir) {
|
|
7422
7448
|
const dirs = await listSubdirectories(ctx.fs, rootDir);
|
|
7423
7449
|
return dirs.filter(
|
|
7424
|
-
(value) =>
|
|
7450
|
+
(value) => path14.basename(value).trim().toLowerCase() !== "feature-base"
|
|
7425
7451
|
);
|
|
7426
7452
|
}
|
|
7427
7453
|
function normalizeRelPath(value) {
|
|
@@ -7562,7 +7588,7 @@ async function scanFeatures(ctx) {
|
|
|
7562
7588
|
if (config.projectType === "single") {
|
|
7563
7589
|
const featureDirs = await listFeatureDirs(
|
|
7564
7590
|
ctx,
|
|
7565
|
-
|
|
7591
|
+
path14.join(config.docsDir, "features")
|
|
7566
7592
|
);
|
|
7567
7593
|
componentFeatureDirs.set("single", featureDirs);
|
|
7568
7594
|
allFeatureDirs.push(...featureDirs);
|
|
@@ -7574,14 +7600,14 @@ async function scanFeatures(ctx) {
|
|
|
7574
7600
|
for (const component of components) {
|
|
7575
7601
|
const componentDirs = await listFeatureDirs(
|
|
7576
7602
|
ctx,
|
|
7577
|
-
|
|
7603
|
+
path14.join(config.docsDir, "features", component)
|
|
7578
7604
|
);
|
|
7579
7605
|
componentFeatureDirs.set(component, componentDirs);
|
|
7580
7606
|
allFeatureDirs.push(...componentDirs);
|
|
7581
7607
|
}
|
|
7582
7608
|
}
|
|
7583
7609
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
7584
|
-
(dir) => normalizeRelPath(
|
|
7610
|
+
(dir) => normalizeRelPath(path14.relative(config.docsDir, dir))
|
|
7585
7611
|
);
|
|
7586
7612
|
const docsGitMeta = buildDocsFeatureGitMeta(
|
|
7587
7613
|
ctx,
|
|
@@ -7598,7 +7624,7 @@ async function scanFeatures(ctx) {
|
|
|
7598
7624
|
const parsed = await Promise.all(
|
|
7599
7625
|
target.dirs.map(async (dir) => {
|
|
7600
7626
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
7601
|
-
|
|
7627
|
+
path14.relative(config.docsDir, dir)
|
|
7602
7628
|
);
|
|
7603
7629
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
7604
7630
|
return parseFeature(
|
|
@@ -7668,13 +7694,13 @@ async function runStatus(options) {
|
|
|
7668
7694
|
);
|
|
7669
7695
|
}
|
|
7670
7696
|
const { docsDir, projectType, projectName, lang } = ctx.config;
|
|
7671
|
-
const featuresDir =
|
|
7697
|
+
const featuresDir = path14.join(docsDir, "features");
|
|
7672
7698
|
const scan = await scanFeatures(ctx);
|
|
7673
7699
|
const features = [];
|
|
7674
7700
|
const idMap = /* @__PURE__ */ new Map();
|
|
7675
7701
|
for (const f of scan.features) {
|
|
7676
7702
|
const id = f.id || "UNKNOWN";
|
|
7677
|
-
const relPath =
|
|
7703
|
+
const relPath = path14.relative(docsDir, f.path);
|
|
7678
7704
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
7679
7705
|
idMap.get(id).push(relPath);
|
|
7680
7706
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -7765,7 +7791,7 @@ async function runStatus(options) {
|
|
|
7765
7791
|
}
|
|
7766
7792
|
console.log();
|
|
7767
7793
|
if (options.write) {
|
|
7768
|
-
const outputPath =
|
|
7794
|
+
const outputPath = path14.join(featuresDir, "status.md");
|
|
7769
7795
|
const date = getLocalDateString();
|
|
7770
7796
|
const content = [
|
|
7771
7797
|
"# Feature Status",
|
|
@@ -7786,18 +7812,18 @@ async function runStatus(options) {
|
|
|
7786
7812
|
);
|
|
7787
7813
|
}
|
|
7788
7814
|
}
|
|
7789
|
-
function
|
|
7815
|
+
function escapeRegExp4(value) {
|
|
7790
7816
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7791
7817
|
}
|
|
7792
7818
|
async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallbackFolderName) {
|
|
7793
7819
|
try {
|
|
7794
|
-
const specPath =
|
|
7820
|
+
const specPath = path14.join(featureDir, "spec.md");
|
|
7795
7821
|
if (!await fsAdapter.pathExists(specPath)) return fallbackSlug;
|
|
7796
7822
|
const content = await fsAdapter.readFile(specPath, "utf-8");
|
|
7797
7823
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
7798
7824
|
for (const key of keys) {
|
|
7799
7825
|
const regex = new RegExp(
|
|
7800
|
-
`^\\s*-\\s*\\*\\*${
|
|
7826
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp4(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
7801
7827
|
"m"
|
|
7802
7828
|
);
|
|
7803
7829
|
const match = content.match(regex);
|
|
@@ -7875,8 +7901,8 @@ async function runUpdate(options) {
|
|
|
7875
7901
|
console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
7876
7902
|
}
|
|
7877
7903
|
if (agentsMode === "all") {
|
|
7878
|
-
const commonAgentsBase =
|
|
7879
|
-
const targetAgentsBase =
|
|
7904
|
+
const commonAgentsBase = path14.join(templatesDir, lang, "common", "agents");
|
|
7905
|
+
const targetAgentsBase = path14.join(docsDir, "agents");
|
|
7880
7906
|
const commonAgents = commonAgentsBase;
|
|
7881
7907
|
const targetAgents = targetAgentsBase;
|
|
7882
7908
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
@@ -7974,21 +8000,21 @@ async function collectAgentsMdTargets(cwd, config) {
|
|
|
7974
8000
|
const targets = /* @__PURE__ */ new Set();
|
|
7975
8001
|
const docsRepo = config.docsRepo ?? "embedded";
|
|
7976
8002
|
if (docsRepo === "embedded") {
|
|
7977
|
-
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) ||
|
|
7978
|
-
targets.add(
|
|
8003
|
+
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path14.resolve(config.docsDir, "..");
|
|
8004
|
+
targets.add(path14.join(repoRoot, "AGENTS.md"));
|
|
7979
8005
|
return [...targets];
|
|
7980
8006
|
}
|
|
7981
|
-
targets.add(
|
|
8007
|
+
targets.add(path14.join(config.docsDir, "AGENTS.md"));
|
|
7982
8008
|
const baseDir = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || process.cwd();
|
|
7983
8009
|
const rawRoots = typeof config.projectRoot === "string" ? [config.projectRoot] : config.projectRoot && typeof config.projectRoot === "object" ? Object.values(config.projectRoot) : [];
|
|
7984
8010
|
for (const rawRoot of rawRoots) {
|
|
7985
8011
|
const value = String(rawRoot || "").trim();
|
|
7986
8012
|
if (!value) continue;
|
|
7987
|
-
const resolved =
|
|
8013
|
+
const resolved = path14.resolve(baseDir, value);
|
|
7988
8014
|
if (!await fs.pathExists(resolved)) continue;
|
|
7989
8015
|
const stat = await fs.stat(resolved);
|
|
7990
8016
|
if (!stat.isDirectory()) continue;
|
|
7991
|
-
targets.add(
|
|
8017
|
+
targets.add(path14.join(resolved, "AGENTS.md"));
|
|
7992
8018
|
}
|
|
7993
8019
|
return [...targets];
|
|
7994
8020
|
}
|
|
@@ -8028,7 +8054,7 @@ function normalizeDecisionEnumList2(raw) {
|
|
|
8028
8054
|
return [...deduped];
|
|
8029
8055
|
}
|
|
8030
8056
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
8031
|
-
const configPath =
|
|
8057
|
+
const configPath = path14.join(docsDir, ".lee-spec-kit.json");
|
|
8032
8058
|
if (!await fs.pathExists(configPath)) {
|
|
8033
8059
|
return { changed: false, changedPaths: [] };
|
|
8034
8060
|
}
|
|
@@ -8151,8 +8177,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
8151
8177
|
const files = await fs.readdir(sourceDir);
|
|
8152
8178
|
let updatedCount = 0;
|
|
8153
8179
|
for (const file of files) {
|
|
8154
|
-
const sourcePath =
|
|
8155
|
-
const targetPath =
|
|
8180
|
+
const sourcePath = path14.join(sourceDir, file);
|
|
8181
|
+
const targetPath = path14.join(targetDir, file);
|
|
8156
8182
|
const stat = await fs.stat(sourcePath);
|
|
8157
8183
|
if (stat.isFile()) {
|
|
8158
8184
|
if (protectedFiles.has(file)) {
|
|
@@ -8235,7 +8261,7 @@ function extractPorcelainPaths(line) {
|
|
|
8235
8261
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
8236
8262
|
const top = getGitTopLevel2(docsDir);
|
|
8237
8263
|
if (!top) return null;
|
|
8238
|
-
const rel =
|
|
8264
|
+
const rel = path14.relative(top, docsDir) || ".";
|
|
8239
8265
|
try {
|
|
8240
8266
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
8241
8267
|
cwd: top,
|
|
@@ -8247,7 +8273,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
8247
8273
|
}
|
|
8248
8274
|
const ignoredRelPaths = new Set(
|
|
8249
8275
|
ignoredAbsPaths.map(
|
|
8250
|
-
(absPath) => normalizeGitPath2(
|
|
8276
|
+
(absPath) => normalizeGitPath2(path14.relative(top, absPath) || ".")
|
|
8251
8277
|
)
|
|
8252
8278
|
);
|
|
8253
8279
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -8305,7 +8331,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
8305
8331
|
}
|
|
8306
8332
|
async function runConfig(options) {
|
|
8307
8333
|
const cwd = process.cwd();
|
|
8308
|
-
const targetCwd = options.dir ?
|
|
8334
|
+
const targetCwd = options.dir ? path14.resolve(cwd, options.dir) : cwd;
|
|
8309
8335
|
const config = await getConfig(targetCwd);
|
|
8310
8336
|
if (!config) {
|
|
8311
8337
|
throw createCliError(
|
|
@@ -8313,7 +8339,7 @@ async function runConfig(options) {
|
|
|
8313
8339
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
8314
8340
|
);
|
|
8315
8341
|
}
|
|
8316
|
-
const configPath =
|
|
8342
|
+
const configPath = path14.join(config.docsDir, ".lee-spec-kit.json");
|
|
8317
8343
|
if (!options.projectRoot) {
|
|
8318
8344
|
console.log();
|
|
8319
8345
|
console.log(chalk9.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
@@ -8419,47 +8445,47 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
8419
8445
|
{
|
|
8420
8446
|
id: "agents",
|
|
8421
8447
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
8422
|
-
relativePath: (_, lang) =>
|
|
8448
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "agents.md")
|
|
8423
8449
|
},
|
|
8424
8450
|
{
|
|
8425
8451
|
id: "git-workflow",
|
|
8426
8452
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
8427
|
-
relativePath: (_, lang) =>
|
|
8453
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "git-workflow.md")
|
|
8428
8454
|
},
|
|
8429
8455
|
{
|
|
8430
8456
|
id: "issue-doc",
|
|
8431
8457
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
8432
|
-
relativePath: (_, lang) =>
|
|
8458
|
+
relativePath: (_, lang) => path14.join(lang, "common", "features", "feature-base", "issue.md")
|
|
8433
8459
|
},
|
|
8434
8460
|
{
|
|
8435
8461
|
id: "pr-doc",
|
|
8436
8462
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
8437
|
-
relativePath: (_, lang) =>
|
|
8463
|
+
relativePath: (_, lang) => path14.join(lang, "common", "features", "feature-base", "pr.md")
|
|
8438
8464
|
},
|
|
8439
8465
|
{
|
|
8440
8466
|
id: "create-feature",
|
|
8441
8467
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
8442
|
-
relativePath: (_, lang) =>
|
|
8468
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
8443
8469
|
},
|
|
8444
8470
|
{
|
|
8445
8471
|
id: "execute-task",
|
|
8446
8472
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
8447
|
-
relativePath: (_, lang) =>
|
|
8473
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
8448
8474
|
},
|
|
8449
8475
|
{
|
|
8450
8476
|
id: "create-issue",
|
|
8451
8477
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
8452
|
-
relativePath: (_, lang) =>
|
|
8478
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
8453
8479
|
},
|
|
8454
8480
|
{
|
|
8455
8481
|
id: "create-pr",
|
|
8456
8482
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
8457
|
-
relativePath: (_, lang) =>
|
|
8483
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
8458
8484
|
},
|
|
8459
8485
|
{
|
|
8460
8486
|
id: "split-feature",
|
|
8461
8487
|
title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
|
|
8462
|
-
relativePath: (_, lang) =>
|
|
8488
|
+
relativePath: (_, lang) => path14.join(lang, "common", "agents", "skills", "split-feature.md")
|
|
8463
8489
|
}
|
|
8464
8490
|
];
|
|
8465
8491
|
var DOC_FOLLOWUPS = {
|
|
@@ -8556,7 +8582,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
8556
8582
|
id: doc.id,
|
|
8557
8583
|
title: doc.title[lang],
|
|
8558
8584
|
relativePath,
|
|
8559
|
-
absolutePath:
|
|
8585
|
+
absolutePath: path14.join(templatesDir, relativePath)
|
|
8560
8586
|
};
|
|
8561
8587
|
});
|
|
8562
8588
|
}
|
|
@@ -8966,6 +8992,13 @@ async function resolveContextSelection(ctx, featureName, options) {
|
|
|
8966
8992
|
|
|
8967
8993
|
// src/services/ContextPresenter.ts
|
|
8968
8994
|
function getActionExecutionMetadata(action) {
|
|
8995
|
+
if (action.category === "task_execute" && action.taskExecutePhase === "start") {
|
|
8996
|
+
return {
|
|
8997
|
+
handoffOnly: true,
|
|
8998
|
+
advancesWorkflow: false,
|
|
8999
|
+
nextMainState: "task_complete"
|
|
9000
|
+
};
|
|
9001
|
+
}
|
|
8969
9002
|
if (action.category === "code_review_run") {
|
|
8970
9003
|
return {
|
|
8971
9004
|
handoffOnly: true,
|
|
@@ -9050,6 +9083,7 @@ function buildAgentOrchestrationPolicy(actionOptions, autoRunAvailable, autoRunC
|
|
|
9050
9083
|
pauseAndReportWhen: [
|
|
9051
9084
|
"approvalRequest.required=true",
|
|
9052
9085
|
"AUTO_GATE_REACHED",
|
|
9086
|
+
"AUTO_DELEGATED_HANDOFF",
|
|
9053
9087
|
"AUTO_MANUAL_REQUIRED",
|
|
9054
9088
|
"command execution error"
|
|
9055
9089
|
],
|
|
@@ -9593,7 +9627,7 @@ function getApprovalSessionId() {
|
|
|
9593
9627
|
function getApprovalTicketPaths(config) {
|
|
9594
9628
|
return {
|
|
9595
9629
|
runtimePath: getApprovalTicketStorePath(config.docsDir),
|
|
9596
|
-
legacyPath:
|
|
9630
|
+
legacyPath: path14.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
|
|
9597
9631
|
};
|
|
9598
9632
|
}
|
|
9599
9633
|
async function loadApprovalTicketStore(storePath) {
|
|
@@ -9607,7 +9641,7 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
9607
9641
|
}
|
|
9608
9642
|
}
|
|
9609
9643
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
9610
|
-
await fs.ensureDir(
|
|
9644
|
+
await fs.ensureDir(path14.dirname(storePath));
|
|
9611
9645
|
await fs.writeJson(storePath, payload, { spaces: 2 });
|
|
9612
9646
|
}
|
|
9613
9647
|
function pruneApprovalTickets(tickets, nowMs) {
|
|
@@ -9793,6 +9827,14 @@ function getCommandExecutionLockPath(action, config) {
|
|
|
9793
9827
|
return getProjectExecutionLockPath(action.cwd);
|
|
9794
9828
|
}
|
|
9795
9829
|
function buildApprovedHandoffMetadata(action, featureRef) {
|
|
9830
|
+
if (action.category === "task_execute" && action.taskExecutePhase === "start") {
|
|
9831
|
+
const taskId = action.cmd.match(/\b--task\s+([^\s]+)/)?.[1];
|
|
9832
|
+
return {
|
|
9833
|
+
delegatedWorkRequired: true,
|
|
9834
|
+
doNotReapproveSameLabel: true,
|
|
9835
|
+
reuseKey: taskId ? `task:${featureRef}:${taskId}` : `task:${featureRef}`
|
|
9836
|
+
};
|
|
9837
|
+
}
|
|
9796
9838
|
if (action.category === "pre_pr_review_run") {
|
|
9797
9839
|
return {
|
|
9798
9840
|
delegatedWorkRequired: true,
|
|
@@ -10460,7 +10502,7 @@ async function runContext(featureName, options) {
|
|
|
10460
10502
|
untilCategories: autoRunPlan.untilCategories,
|
|
10461
10503
|
unknownCategories: autoRunPlan.unknownCategories,
|
|
10462
10504
|
manualBoundary: autoRunPlan.manualBoundary,
|
|
10463
|
-
guidance: 'Use auto-run only when `autoRun.available=true`. If `autoRun.policyEligible=true` but `autoRun.executableNow=false`, resolve `autoRun.manualBoundary` first. Do not treat `autoRun.available` alone as a delegation trigger; use `agentOrchestration.subAgentHandoff.required` + `mode="auto_run"` for actual delegation. Stop and request approval when `approvalRequest.required=true
|
|
10505
|
+
guidance: 'Use auto-run only when `autoRun.available=true`. If `autoRun.policyEligible=true` but `autoRun.executableNow=false`, resolve `autoRun.manualBoundary` first. Do not treat `autoRun.available` alone as a delegation trigger; use `agentOrchestration.subAgentHandoff.required` + `mode="auto_run"` for actual delegation. Stop and request approval when `approvalRequest.required=true`, when auto mode reaches configured gate categories, or when a delegated handoff pause must be resumed.'
|
|
10464
10506
|
},
|
|
10465
10507
|
approvalRequest: {
|
|
10466
10508
|
guidance: approvalGuidance.replace(
|
|
@@ -10684,7 +10726,7 @@ async function runContext(featureName, options) {
|
|
|
10684
10726
|
if (f.issueNumber) {
|
|
10685
10727
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
10686
10728
|
}
|
|
10687
|
-
console.log(` \u2022 Path: ${
|
|
10729
|
+
console.log(` \u2022 Path: ${path14.relative(cwd, f.path)}`);
|
|
10688
10730
|
if (f.git.projectBranch) {
|
|
10689
10731
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
10690
10732
|
}
|
|
@@ -10836,7 +10878,7 @@ function extractTitleAfterId(line, id) {
|
|
|
10836
10878
|
return cleaned ? cleaned : void 0;
|
|
10837
10879
|
}
|
|
10838
10880
|
async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
10839
|
-
const prdDir =
|
|
10881
|
+
const prdDir = path14.join(docsDir, "prd");
|
|
10840
10882
|
const files = await walkFiles(fsAdapter, prdDir, {
|
|
10841
10883
|
extensions: [".md"],
|
|
10842
10884
|
ignoreDirs: [".git", "node_modules", "dist", "tmp"]
|
|
@@ -10844,7 +10886,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10844
10886
|
const definitions = /* @__PURE__ */ new Map();
|
|
10845
10887
|
const duplicates = [];
|
|
10846
10888
|
for (const filePath of files) {
|
|
10847
|
-
if (
|
|
10889
|
+
if (path14.basename(filePath).toLowerCase() === "readme.md") {
|
|
10848
10890
|
continue;
|
|
10849
10891
|
}
|
|
10850
10892
|
let content = "";
|
|
@@ -10853,7 +10895,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
10853
10895
|
} catch {
|
|
10854
10896
|
continue;
|
|
10855
10897
|
}
|
|
10856
|
-
const relFile = normalizeRelPath2(
|
|
10898
|
+
const relFile = normalizeRelPath2(path14.relative(docsDir, filePath));
|
|
10857
10899
|
const lines = content.split(/\r?\n/);
|
|
10858
10900
|
let inCodeBlock = false;
|
|
10859
10901
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -10928,7 +10970,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
10928
10970
|
]);
|
|
10929
10971
|
function formatPath(cwd, p) {
|
|
10930
10972
|
if (!p) return "";
|
|
10931
|
-
return
|
|
10973
|
+
return path14.isAbsolute(p) ? path14.relative(cwd, p) : p;
|
|
10932
10974
|
}
|
|
10933
10975
|
function detectPlaceholders(content) {
|
|
10934
10976
|
const patterns = [
|
|
@@ -11087,7 +11129,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
11087
11129
|
const placeholderContext = {
|
|
11088
11130
|
projectName: config.projectName,
|
|
11089
11131
|
featureName: f.slug,
|
|
11090
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
11132
|
+
featurePath: f.docs.featurePathFromDocs || path14.relative(config.docsDir, f.path),
|
|
11091
11133
|
repoType: f.type,
|
|
11092
11134
|
featureNumber
|
|
11093
11135
|
};
|
|
@@ -11097,7 +11139,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
11097
11139
|
"tasks.md"
|
|
11098
11140
|
];
|
|
11099
11141
|
for (const file of files) {
|
|
11100
|
-
const fullPath =
|
|
11142
|
+
const fullPath = path14.join(f.path, file);
|
|
11101
11143
|
if (!await fs.pathExists(fullPath)) continue;
|
|
11102
11144
|
const original = await fs.readFile(fullPath, "utf-8");
|
|
11103
11145
|
let next = original;
|
|
@@ -11150,7 +11192,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
11150
11192
|
const issues = [];
|
|
11151
11193
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
11152
11194
|
for (const dir of requiredDirs) {
|
|
11153
|
-
const p =
|
|
11195
|
+
const p = path14.join(config.docsDir, dir);
|
|
11154
11196
|
if (!await fs.pathExists(p)) {
|
|
11155
11197
|
issues.push({
|
|
11156
11198
|
level: "error",
|
|
@@ -11162,7 +11204,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
11162
11204
|
});
|
|
11163
11205
|
}
|
|
11164
11206
|
}
|
|
11165
|
-
const configPath =
|
|
11207
|
+
const configPath = path14.join(config.docsDir, ".lee-spec-kit.json");
|
|
11166
11208
|
if (!await fs.pathExists(configPath)) {
|
|
11167
11209
|
issues.push({
|
|
11168
11210
|
level: "warn",
|
|
@@ -11190,7 +11232,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11190
11232
|
}
|
|
11191
11233
|
const idMap = /* @__PURE__ */ new Map();
|
|
11192
11234
|
for (const f of features) {
|
|
11193
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
11235
|
+
const rel = f.docs.featurePathFromDocs || path14.relative(config.docsDir, f.path);
|
|
11194
11236
|
const id = f.id || "UNKNOWN";
|
|
11195
11237
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
11196
11238
|
idMap.get(id).push(rel);
|
|
@@ -11198,7 +11240,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11198
11240
|
if (!isInitialTemplateState) {
|
|
11199
11241
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
11200
11242
|
for (const file of featureDocs) {
|
|
11201
|
-
const p =
|
|
11243
|
+
const p = path14.join(f.path, file);
|
|
11202
11244
|
if (!await fs.pathExists(p)) continue;
|
|
11203
11245
|
const content = await fs.readFile(p, "utf-8");
|
|
11204
11246
|
const placeholders = detectPlaceholders(content);
|
|
@@ -11213,7 +11255,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11213
11255
|
});
|
|
11214
11256
|
}
|
|
11215
11257
|
if (decisionsPlaceholderMode !== "off") {
|
|
11216
|
-
const decisionsPath =
|
|
11258
|
+
const decisionsPath = path14.join(f.path, "decisions.md");
|
|
11217
11259
|
if (await fs.pathExists(decisionsPath)) {
|
|
11218
11260
|
const content = await fs.readFile(decisionsPath, "utf-8");
|
|
11219
11261
|
const placeholders = detectPlaceholders(content);
|
|
@@ -11242,7 +11284,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11242
11284
|
level: "warn",
|
|
11243
11285
|
code: "spec_status_unset",
|
|
11244
11286
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
11245
|
-
path: formatPath(cwd,
|
|
11287
|
+
path: formatPath(cwd, path14.join(f.path, "spec.md"))
|
|
11246
11288
|
});
|
|
11247
11289
|
}
|
|
11248
11290
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -11250,7 +11292,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11250
11292
|
level: "warn",
|
|
11251
11293
|
code: "plan_status_unset",
|
|
11252
11294
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
11253
|
-
path: formatPath(cwd,
|
|
11295
|
+
path: formatPath(cwd, path14.join(f.path, "plan.md"))
|
|
11254
11296
|
});
|
|
11255
11297
|
}
|
|
11256
11298
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -11258,11 +11300,11 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11258
11300
|
level: "warn",
|
|
11259
11301
|
code: "tasks_empty",
|
|
11260
11302
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
11261
|
-
path: formatPath(cwd,
|
|
11303
|
+
path: formatPath(cwd, path14.join(f.path, "tasks.md"))
|
|
11262
11304
|
});
|
|
11263
11305
|
}
|
|
11264
11306
|
if (f.docs.tasksExists) {
|
|
11265
|
-
const tasksPath =
|
|
11307
|
+
const tasksPath = path14.join(f.path, "tasks.md");
|
|
11266
11308
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
11267
11309
|
const unknownPrdTags = [...new Set(
|
|
11268
11310
|
parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
|
|
@@ -11284,7 +11326,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11284
11326
|
level: "warn",
|
|
11285
11327
|
code: "tasks_doc_status_missing",
|
|
11286
11328
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
11287
|
-
path: formatPath(cwd,
|
|
11329
|
+
path: formatPath(cwd, path14.join(f.path, "tasks.md"))
|
|
11288
11330
|
});
|
|
11289
11331
|
}
|
|
11290
11332
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -11292,7 +11334,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11292
11334
|
level: "warn",
|
|
11293
11335
|
code: "tasks_doc_status_unset",
|
|
11294
11336
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
11295
|
-
path: formatPath(cwd,
|
|
11337
|
+
path: formatPath(cwd, path14.join(f.path, "tasks.md"))
|
|
11296
11338
|
});
|
|
11297
11339
|
}
|
|
11298
11340
|
}
|
|
@@ -11316,7 +11358,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11316
11358
|
level: "warn",
|
|
11317
11359
|
code: "missing_feature_id",
|
|
11318
11360
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
11319
|
-
path: formatPath(cwd,
|
|
11361
|
+
path: formatPath(cwd, path14.join(config.docsDir, p))
|
|
11320
11362
|
});
|
|
11321
11363
|
}
|
|
11322
11364
|
return issues;
|
|
@@ -11447,7 +11489,7 @@ function doctorCommand(program2) {
|
|
|
11447
11489
|
}
|
|
11448
11490
|
console.log();
|
|
11449
11491
|
console.log(chalk9.bold(tr(lang, "cli", "doctor.title")));
|
|
11450
|
-
console.log(chalk9.gray(`- Docs: ${
|
|
11492
|
+
console.log(chalk9.gray(`- Docs: ${path14.relative(cwd, docsDir)}`));
|
|
11451
11493
|
console.log(chalk9.gray(`- Type: ${projectType}`));
|
|
11452
11494
|
console.log(chalk9.gray(`- Lang: ${lang}`));
|
|
11453
11495
|
console.log();
|
|
@@ -11628,7 +11670,7 @@ async function runView(featureName, options) {
|
|
|
11628
11670
|
}
|
|
11629
11671
|
console.log();
|
|
11630
11672
|
console.log(chalk9.bold("\u{1F4CA} Workflow View"));
|
|
11631
|
-
console.log(chalk9.gray(`- Docs: ${
|
|
11673
|
+
console.log(chalk9.gray(`- Docs: ${path14.relative(cwd, config.docsDir)}`));
|
|
11632
11674
|
console.log(
|
|
11633
11675
|
chalk9.gray(
|
|
11634
11676
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
@@ -11713,13 +11755,13 @@ function normalizeRunId(raw) {
|
|
|
11713
11755
|
return value;
|
|
11714
11756
|
}
|
|
11715
11757
|
function getFlowRunBaseDir(cwd) {
|
|
11716
|
-
return
|
|
11758
|
+
return path14.join(getRuntimeStateDir(cwd), "flow-runs");
|
|
11717
11759
|
}
|
|
11718
11760
|
function getFlowRunPath(cwd, runId) {
|
|
11719
|
-
return
|
|
11761
|
+
return path14.join(getFlowRunBaseDir(cwd), `${runId}.json`);
|
|
11720
11762
|
}
|
|
11721
11763
|
function getFlowRunLockPath(cwd, runId) {
|
|
11722
|
-
return
|
|
11764
|
+
return path14.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
|
|
11723
11765
|
}
|
|
11724
11766
|
async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
11725
11767
|
const normalized = normalizeRunId(runId);
|
|
@@ -11745,7 +11787,7 @@ async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
|
11745
11787
|
}
|
|
11746
11788
|
async function writeFlowRunRecord(cwd, record) {
|
|
11747
11789
|
const filePath = getFlowRunPath(cwd, record.runId);
|
|
11748
|
-
await fs.ensureDir(
|
|
11790
|
+
await fs.ensureDir(path14.dirname(filePath));
|
|
11749
11791
|
await fs.writeJson(filePath, record, { spaces: 2 });
|
|
11750
11792
|
}
|
|
11751
11793
|
async function createFlowRunRecord(cwd, input) {
|
|
@@ -11895,6 +11937,7 @@ function toCompactAutoRun(autoRun) {
|
|
|
11895
11937
|
iterations: autoRun.iterations,
|
|
11896
11938
|
executionCount: autoRun.executions.length,
|
|
11897
11939
|
lastExecution,
|
|
11940
|
+
delegated: autoRun.delegated ?? null,
|
|
11898
11941
|
gate: autoRun.gate ?? null,
|
|
11899
11942
|
manual: autoRun.manual ?? null,
|
|
11900
11943
|
resume: autoRun.resume,
|
|
@@ -11939,6 +11982,7 @@ function buildAgentOrchestrationPolicy2(autoRun, featureRef) {
|
|
|
11939
11982
|
pauseAndReportWhen: [
|
|
11940
11983
|
"approvalRequest.required=true",
|
|
11941
11984
|
"AUTO_GATE_REACHED",
|
|
11985
|
+
"AUTO_DELEGATED_HANDOFF",
|
|
11942
11986
|
"AUTO_MANUAL_REQUIRED",
|
|
11943
11987
|
"command execution error"
|
|
11944
11988
|
],
|
|
@@ -12164,6 +12208,7 @@ function resolveAutoMode(config, options, requestText) {
|
|
|
12164
12208
|
}
|
|
12165
12209
|
function toAutoReasonCode(status) {
|
|
12166
12210
|
const map = {
|
|
12211
|
+
delegated_handoff: "AUTO_DELEGATED_HANDOFF",
|
|
12167
12212
|
gate_reached: "AUTO_GATE_REACHED",
|
|
12168
12213
|
manual_required: "AUTO_MANUAL_REQUIRED",
|
|
12169
12214
|
no_action_options: "AUTO_NO_ACTION_OPTIONS",
|
|
@@ -12187,6 +12232,7 @@ function isAutoRunFailureStatus(status) {
|
|
|
12187
12232
|
}
|
|
12188
12233
|
function toFlowRunStatus(status) {
|
|
12189
12234
|
switch (status) {
|
|
12235
|
+
case "delegated_handoff":
|
|
12190
12236
|
case "gate_reached":
|
|
12191
12237
|
case "manual_required":
|
|
12192
12238
|
return "paused";
|
|
@@ -12201,6 +12247,17 @@ function toFlowRunStatus(status) {
|
|
|
12201
12247
|
return "failed";
|
|
12202
12248
|
}
|
|
12203
12249
|
}
|
|
12250
|
+
function isTaskCommitCheckpointOption(option, state) {
|
|
12251
|
+
if (!option || state.status !== "single_matched" || !state.matchedFeature) {
|
|
12252
|
+
return false;
|
|
12253
|
+
}
|
|
12254
|
+
if (state.matchedFeature.currentSubstateId !== "task_commit_pending") {
|
|
12255
|
+
return false;
|
|
12256
|
+
}
|
|
12257
|
+
if (option.action.type !== "command") return false;
|
|
12258
|
+
if (option.action.category === "docs_commit") return true;
|
|
12259
|
+
return option.action.category === "task_execute" && option.action.scope === "project" && /\bgit\s+commit\b/i.test(option.action.cmd);
|
|
12260
|
+
}
|
|
12204
12261
|
async function runAutoUntilCategory(config, featureName, selectionOptions, untilCategories, requestText, metadata) {
|
|
12205
12262
|
const contextArgs = [
|
|
12206
12263
|
"context",
|
|
@@ -12378,6 +12435,29 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
12378
12435
|
manual: null
|
|
12379
12436
|
};
|
|
12380
12437
|
}
|
|
12438
|
+
const taskCommitCheckpoint = actionOptions.find(
|
|
12439
|
+
(option) => isTaskCommitCheckpointOption(option, state)
|
|
12440
|
+
);
|
|
12441
|
+
if (taskCommitCheckpoint) {
|
|
12442
|
+
return {
|
|
12443
|
+
enabled: true,
|
|
12444
|
+
untilCategories,
|
|
12445
|
+
request: requestText,
|
|
12446
|
+
preset: metadata?.preset ?? null,
|
|
12447
|
+
source: metadata?.source ?? null,
|
|
12448
|
+
resume,
|
|
12449
|
+
status: "manual_required",
|
|
12450
|
+
reasonCode: toAutoReasonCode("manual_required"),
|
|
12451
|
+
iterations,
|
|
12452
|
+
executions,
|
|
12453
|
+
gate: null,
|
|
12454
|
+
manual: {
|
|
12455
|
+
label: taskCommitCheckpoint.label,
|
|
12456
|
+
category: taskCommitCheckpoint.action.category,
|
|
12457
|
+
detail: taskCommitCheckpoint.detail
|
|
12458
|
+
}
|
|
12459
|
+
};
|
|
12460
|
+
}
|
|
12381
12461
|
const executable = actionOptions.find(
|
|
12382
12462
|
(option) => option.action.type === "command"
|
|
12383
12463
|
);
|
|
@@ -12463,16 +12543,18 @@ async function runAutoUntilCategory(config, featureName, selectionOptions, until
|
|
|
12463
12543
|
preset: metadata?.preset ?? null,
|
|
12464
12544
|
source: metadata?.source ?? null,
|
|
12465
12545
|
resume,
|
|
12466
|
-
status: "
|
|
12467
|
-
reasonCode: toAutoReasonCode("
|
|
12546
|
+
status: "delegated_handoff",
|
|
12547
|
+
reasonCode: toAutoReasonCode("delegated_handoff"),
|
|
12468
12548
|
iterations,
|
|
12469
12549
|
executions,
|
|
12470
|
-
|
|
12471
|
-
manual: {
|
|
12550
|
+
delegated: {
|
|
12472
12551
|
label: executable.label,
|
|
12473
12552
|
category: executable.action.category,
|
|
12474
|
-
detail: typeof executeResult?.nextMainState === "string" ? `Complete the delegated handoff work, then continue from ${executeResult.nextMainState}.` : executable.detail
|
|
12475
|
-
|
|
12553
|
+
detail: typeof executeResult?.nextMainState === "string" ? `Complete the delegated handoff work, then continue from ${executeResult.nextMainState}.` : executable.detail,
|
|
12554
|
+
nextMainState: executeResult?.nextMainState
|
|
12555
|
+
},
|
|
12556
|
+
gate: null,
|
|
12557
|
+
manual: null
|
|
12476
12558
|
};
|
|
12477
12559
|
}
|
|
12478
12560
|
if (executeResult?.status !== "approved_executed") {
|
|
@@ -12663,6 +12745,20 @@ async function runFlow(featureName, options) {
|
|
|
12663
12745
|
"`--request` requires auto mode. Use `--auto-until-category`, `--auto-preset`, or configure `workflow.auto.defaultPreset`."
|
|
12664
12746
|
);
|
|
12665
12747
|
}
|
|
12748
|
+
if (autoMode && !resolvedFeatureName && requestText) {
|
|
12749
|
+
const bootstrapped = await bootstrapFeatureFromIdeaRequest(
|
|
12750
|
+
config,
|
|
12751
|
+
requestText,
|
|
12752
|
+
selectedComponent
|
|
12753
|
+
);
|
|
12754
|
+
if (bootstrapped) {
|
|
12755
|
+
resolvedFeatureName = bootstrapped.featureRef;
|
|
12756
|
+
if (!selectedComponent && bootstrapped.component) {
|
|
12757
|
+
selectedComponent = bootstrapped.component;
|
|
12758
|
+
selectionOptions.component = bootstrapped.component;
|
|
12759
|
+
}
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12666
12762
|
if (autoMode && !featureName) {
|
|
12667
12763
|
if (!resolvedFeatureName) {
|
|
12668
12764
|
throw createCliError(
|
|
@@ -12757,7 +12853,8 @@ async function runFlow(featureName, options) {
|
|
|
12757
12853
|
},
|
|
12758
12854
|
lastAutoStatus: autoRun.status,
|
|
12759
12855
|
lastReasonCode: autoRun.reasonCode,
|
|
12760
|
-
lastError: autoRun.error
|
|
12856
|
+
lastError: autoRun.error,
|
|
12857
|
+
lastDelegatedHandoff: autoRun.delegated ?? null
|
|
12761
12858
|
})
|
|
12762
12859
|
);
|
|
12763
12860
|
autoRun.run = {
|
|
@@ -12943,6 +13040,45 @@ async function runFlow(featureName, options) {
|
|
|
12943
13040
|
);
|
|
12944
13041
|
console.log();
|
|
12945
13042
|
}
|
|
13043
|
+
async function bootstrapFeatureFromIdeaRequest(config, requestText, selectedComponent) {
|
|
13044
|
+
const ideaRef = extractExplicitIdeaRef(requestText);
|
|
13045
|
+
if (!ideaRef) return null;
|
|
13046
|
+
const resolvedIdea = await resolveIdeaReference(
|
|
13047
|
+
config.docsDir,
|
|
13048
|
+
ideaRef,
|
|
13049
|
+
config.lang
|
|
13050
|
+
);
|
|
13051
|
+
const existingFeatureRef = await readIdeaMetadataValue(
|
|
13052
|
+
resolvedIdea.path,
|
|
13053
|
+
"Feature"
|
|
13054
|
+
);
|
|
13055
|
+
if (existingFeatureRef && existingFeatureRef !== "-") {
|
|
13056
|
+
return { featureRef: existingFeatureRef, component: selectedComponent };
|
|
13057
|
+
}
|
|
13058
|
+
let component = selectedComponent;
|
|
13059
|
+
if (config.projectType === "multi" && !component) {
|
|
13060
|
+
const ideaComponent = await readIdeaMetadataValue(
|
|
13061
|
+
resolvedIdea.path,
|
|
13062
|
+
"Component"
|
|
13063
|
+
);
|
|
13064
|
+
if (ideaComponent && ideaComponent !== "-" && ideaComponent !== "all") {
|
|
13065
|
+
component = ideaComponent.toLowerCase();
|
|
13066
|
+
} else {
|
|
13067
|
+
return null;
|
|
13068
|
+
}
|
|
13069
|
+
}
|
|
13070
|
+
const featureName = await deriveFeatureNameFromIdea(resolvedIdea.path);
|
|
13071
|
+
const result = await runFeature(featureName, {
|
|
13072
|
+
component,
|
|
13073
|
+
idea: ideaRef,
|
|
13074
|
+
nonInteractive: true,
|
|
13075
|
+
json: true
|
|
13076
|
+
});
|
|
13077
|
+
return {
|
|
13078
|
+
featureRef: `${result.featureId}-${result.featureName}`,
|
|
13079
|
+
component
|
|
13080
|
+
};
|
|
13081
|
+
}
|
|
12946
13082
|
function runProcess(bin, args, cwd) {
|
|
12947
13083
|
const result = spawnSync(bin, args, {
|
|
12948
13084
|
cwd,
|
|
@@ -13056,27 +13192,27 @@ function tg(lang, key, vars = {}) {
|
|
|
13056
13192
|
function detectGithubCliLangSync(cwd) {
|
|
13057
13193
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
13058
13194
|
const startDirs = [
|
|
13059
|
-
explicitDocsDir ?
|
|
13060
|
-
|
|
13195
|
+
explicitDocsDir ? path14.resolve(explicitDocsDir) : "",
|
|
13196
|
+
path14.resolve(cwd)
|
|
13061
13197
|
].filter(Boolean);
|
|
13062
13198
|
const scanOrder = [];
|
|
13063
13199
|
const seen = /* @__PURE__ */ new Set();
|
|
13064
13200
|
for (const start of startDirs) {
|
|
13065
13201
|
let current = start;
|
|
13066
13202
|
while (true) {
|
|
13067
|
-
const abs =
|
|
13203
|
+
const abs = path14.resolve(current);
|
|
13068
13204
|
if (!seen.has(abs)) {
|
|
13069
13205
|
scanOrder.push(abs);
|
|
13070
13206
|
seen.add(abs);
|
|
13071
13207
|
}
|
|
13072
|
-
const parent =
|
|
13208
|
+
const parent = path14.dirname(abs);
|
|
13073
13209
|
if (parent === abs) break;
|
|
13074
13210
|
current = parent;
|
|
13075
13211
|
}
|
|
13076
13212
|
}
|
|
13077
13213
|
for (const base of scanOrder) {
|
|
13078
|
-
for (const docsDir of [
|
|
13079
|
-
const configPath =
|
|
13214
|
+
for (const docsDir of [path14.join(base, "docs"), base]) {
|
|
13215
|
+
const configPath = path14.join(docsDir, ".lee-spec-kit.json");
|
|
13080
13216
|
if (fs.existsSync(configPath)) {
|
|
13081
13217
|
try {
|
|
13082
13218
|
const parsed = fs.readJsonSync(configPath);
|
|
@@ -13085,11 +13221,11 @@ function detectGithubCliLangSync(cwd) {
|
|
|
13085
13221
|
} catch {
|
|
13086
13222
|
}
|
|
13087
13223
|
}
|
|
13088
|
-
const agentsPath =
|
|
13089
|
-
const featuresPath =
|
|
13224
|
+
const agentsPath = path14.join(docsDir, "agents");
|
|
13225
|
+
const featuresPath = path14.join(docsDir, "features");
|
|
13090
13226
|
if (!fs.existsSync(agentsPath) || !fs.existsSync(featuresPath)) continue;
|
|
13091
13227
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
13092
|
-
const file =
|
|
13228
|
+
const file = path14.join(agentsPath, probe);
|
|
13093
13229
|
if (!fs.existsSync(file)) continue;
|
|
13094
13230
|
try {
|
|
13095
13231
|
const content = fs.readFileSync(file, "utf-8");
|
|
@@ -13109,13 +13245,13 @@ function parseLabels(raw, lang) {
|
|
|
13109
13245
|
}
|
|
13110
13246
|
return [...new Set(labels)];
|
|
13111
13247
|
}
|
|
13112
|
-
function
|
|
13248
|
+
function escapeRegExp5(value) {
|
|
13113
13249
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13114
13250
|
}
|
|
13115
13251
|
function extractDraftMetadataValue(content, keys) {
|
|
13116
13252
|
for (const key of keys) {
|
|
13117
13253
|
const re = new RegExp(
|
|
13118
|
-
`^\\s*-\\s*\\*\\*${
|
|
13254
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp5(key)}\\*\\*\\s*:\\s*(.*?)\\s*$`,
|
|
13119
13255
|
"mi"
|
|
13120
13256
|
);
|
|
13121
13257
|
const match = content.match(re);
|
|
@@ -13194,7 +13330,7 @@ async function prepareGithubBody(params) {
|
|
|
13194
13330
|
};
|
|
13195
13331
|
}
|
|
13196
13332
|
}
|
|
13197
|
-
await fs.ensureDir(
|
|
13333
|
+
await fs.ensureDir(path14.dirname(defaultBodyFile));
|
|
13198
13334
|
await fs.writeFile(defaultBodyFile, generatedBody, "utf-8");
|
|
13199
13335
|
return {
|
|
13200
13336
|
body: generatedBody,
|
|
@@ -13234,7 +13370,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
13234
13370
|
};
|
|
13235
13371
|
const hasMetadataField = (field) => {
|
|
13236
13372
|
const re = new RegExp(
|
|
13237
|
-
`^\\s*-\\s*\\*\\*${
|
|
13373
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp5(field)}\\*\\*\\s*:`,
|
|
13238
13374
|
"m"
|
|
13239
13375
|
);
|
|
13240
13376
|
return re.test(body);
|
|
@@ -13264,7 +13400,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
13264
13400
|
}
|
|
13265
13401
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
13266
13402
|
const missing = relativePaths.filter(
|
|
13267
|
-
(relativePath) => !fs.existsSync(
|
|
13403
|
+
(relativePath) => !fs.existsSync(path14.join(docsDir, relativePath))
|
|
13268
13404
|
);
|
|
13269
13405
|
if (missing.length > 0) {
|
|
13270
13406
|
throw createCliError(
|
|
@@ -13274,18 +13410,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
13274
13410
|
}
|
|
13275
13411
|
}
|
|
13276
13412
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
13277
|
-
const key = `${
|
|
13413
|
+
const key = `${path14.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
13278
13414
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
13279
13415
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
13280
13416
|
}
|
|
13281
13417
|
function toBodyFilePath(raw, kind, docsDir, component, lang) {
|
|
13282
|
-
const selected = raw?.trim() ||
|
|
13418
|
+
const selected = raw?.trim() || path14.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
13283
13419
|
assertValid(
|
|
13284
13420
|
validatePathWithLang(selected, lang),
|
|
13285
13421
|
`github.${kind}.bodyFile`,
|
|
13286
13422
|
lang
|
|
13287
13423
|
);
|
|
13288
|
-
return
|
|
13424
|
+
return path14.resolve(selected);
|
|
13289
13425
|
}
|
|
13290
13426
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
13291
13427
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -14122,7 +14258,7 @@ function stripMarkdownCodeContexts(body) {
|
|
|
14122
14258
|
function hasIssueClosingKeyword(body, issueNumber) {
|
|
14123
14259
|
if (!issueNumber) return false;
|
|
14124
14260
|
const cleaned = stripMarkdownCodeContexts(body);
|
|
14125
|
-
const issue =
|
|
14261
|
+
const issue = escapeRegExp5(issueNumber);
|
|
14126
14262
|
const closeKeywordRegex = new RegExp(
|
|
14127
14263
|
`\\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\b\\s*(?:[a-zA-Z0-9_.-]+\\/)?#\\s*${issue}\\b`,
|
|
14128
14264
|
"i"
|
|
@@ -14340,7 +14476,7 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
14340
14476
|
function commitAndPushPaths(cwd, absPaths, message, lang, options) {
|
|
14341
14477
|
const uniqueRelativePaths = [
|
|
14342
14478
|
...new Set(
|
|
14343
|
-
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) =>
|
|
14479
|
+
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path14.relative(cwd, absPath) || absPath)
|
|
14344
14480
|
)
|
|
14345
14481
|
];
|
|
14346
14482
|
if (uniqueRelativePaths.length === 0) return;
|
|
@@ -14536,15 +14672,15 @@ function githubCommand(program2) {
|
|
|
14536
14672
|
config.lang
|
|
14537
14673
|
);
|
|
14538
14674
|
const specContent = await fs.readFile(
|
|
14539
|
-
|
|
14675
|
+
path14.join(config.docsDir, paths.specPath),
|
|
14540
14676
|
"utf-8"
|
|
14541
14677
|
);
|
|
14542
14678
|
const planContent = await fs.readFile(
|
|
14543
|
-
|
|
14679
|
+
path14.join(config.docsDir, paths.planPath),
|
|
14544
14680
|
"utf-8"
|
|
14545
14681
|
);
|
|
14546
14682
|
const tasksContent = await fs.readFile(
|
|
14547
|
-
|
|
14683
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14548
14684
|
"utf-8"
|
|
14549
14685
|
);
|
|
14550
14686
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14587,7 +14723,7 @@ function githubCommand(program2) {
|
|
|
14587
14723
|
create: options.create,
|
|
14588
14724
|
explicitBodyFile,
|
|
14589
14725
|
defaultBodyFile,
|
|
14590
|
-
workflowDraftPath:
|
|
14726
|
+
workflowDraftPath: path14.join(config.docsDir, paths.issuePath),
|
|
14591
14727
|
generatedBody,
|
|
14592
14728
|
requiredSections: getRequiredIssueSections(config.lang),
|
|
14593
14729
|
kindLabel: tg(config.lang, "kindIssue"),
|
|
@@ -14603,7 +14739,7 @@ function githubCommand(program2) {
|
|
|
14603
14739
|
`${feature.type}-issue-sanitized`,
|
|
14604
14740
|
config.lang
|
|
14605
14741
|
);
|
|
14606
|
-
await fs.ensureDir(
|
|
14742
|
+
await fs.ensureDir(path14.dirname(sanitizedBodyFile));
|
|
14607
14743
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14608
14744
|
bodyFile = sanitizedBodyFile;
|
|
14609
14745
|
}
|
|
@@ -14652,12 +14788,12 @@ function githubCommand(program2) {
|
|
|
14652
14788
|
const syncedIssueNumber = extractIssueNumberFromUrl(issueUrl);
|
|
14653
14789
|
if (syncedIssueNumber) {
|
|
14654
14790
|
const synced = syncTasksIssueMetadata(
|
|
14655
|
-
|
|
14791
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14656
14792
|
syncedIssueNumber,
|
|
14657
14793
|
config.lang
|
|
14658
14794
|
);
|
|
14659
14795
|
const draftSynced = syncIssueDraftMetadata(
|
|
14660
|
-
|
|
14796
|
+
path14.join(config.docsDir, paths.issuePath),
|
|
14661
14797
|
syncedIssueNumber
|
|
14662
14798
|
);
|
|
14663
14799
|
syncChanged = synced.changed || draftSynced.changed;
|
|
@@ -14768,13 +14904,13 @@ function githubCommand(program2) {
|
|
|
14768
14904
|
config.lang
|
|
14769
14905
|
);
|
|
14770
14906
|
const specContent = await fs.readFile(
|
|
14771
|
-
|
|
14907
|
+
path14.join(config.docsDir, paths.specPath),
|
|
14772
14908
|
"utf-8"
|
|
14773
14909
|
);
|
|
14774
|
-
const planPath =
|
|
14910
|
+
const planPath = path14.join(config.docsDir, paths.planPath);
|
|
14775
14911
|
const planContent = await fs.pathExists(planPath) ? await fs.readFile(planPath, "utf-8") : "";
|
|
14776
14912
|
const tasksContent = await fs.readFile(
|
|
14777
|
-
|
|
14913
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14778
14914
|
"utf-8"
|
|
14779
14915
|
);
|
|
14780
14916
|
const overview = resolveOverviewFromSpec(
|
|
@@ -14822,7 +14958,7 @@ function githubCommand(program2) {
|
|
|
14822
14958
|
create: options.create,
|
|
14823
14959
|
explicitBodyFile,
|
|
14824
14960
|
defaultBodyFile,
|
|
14825
|
-
workflowDraftPath:
|
|
14961
|
+
workflowDraftPath: path14.join(config.docsDir, paths.prPath),
|
|
14826
14962
|
generatedBody,
|
|
14827
14963
|
requiredSections: getRequiredPrSections(config.lang),
|
|
14828
14964
|
kindLabel: tg(config.lang, "kindPr"),
|
|
@@ -14840,7 +14976,7 @@ function githubCommand(program2) {
|
|
|
14840
14976
|
`${feature.type}-pr-sanitized`,
|
|
14841
14977
|
config.lang
|
|
14842
14978
|
);
|
|
14843
|
-
await fs.ensureDir(
|
|
14979
|
+
await fs.ensureDir(path14.dirname(sanitizedBodyFile));
|
|
14844
14980
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
14845
14981
|
bodyFile = sanitizedBodyFile;
|
|
14846
14982
|
}
|
|
@@ -14868,7 +15004,7 @@ function githubCommand(program2) {
|
|
|
14868
15004
|
if (preparedBody.source === "generated") {
|
|
14869
15005
|
await fs.writeFile(bodyFile, body, "utf-8");
|
|
14870
15006
|
} else {
|
|
14871
|
-
await fs.ensureDir(
|
|
15007
|
+
await fs.ensureDir(path14.dirname(fallbackBodyFile));
|
|
14872
15008
|
await fs.writeFile(fallbackBodyFile, body, "utf-8");
|
|
14873
15009
|
bodyFile = fallbackBodyFile;
|
|
14874
15010
|
}
|
|
@@ -14929,13 +15065,13 @@ function githubCommand(program2) {
|
|
|
14929
15065
|
}
|
|
14930
15066
|
if (prUrl && options.syncTasks !== false) {
|
|
14931
15067
|
const syncedTasks = syncTasksPrMetadata(
|
|
14932
|
-
|
|
15068
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14933
15069
|
prUrl,
|
|
14934
15070
|
"Review",
|
|
14935
15071
|
config.lang
|
|
14936
15072
|
);
|
|
14937
15073
|
const syncedDraft = syncPrDraftMetadata(
|
|
14938
|
-
|
|
15074
|
+
path14.join(config.docsDir, paths.prPath),
|
|
14939
15075
|
prUrl,
|
|
14940
15076
|
"Review"
|
|
14941
15077
|
);
|
|
@@ -14976,13 +15112,13 @@ function githubCommand(program2) {
|
|
|
14976
15112
|
mergeAlreadyMerged = merged.alreadyMerged;
|
|
14977
15113
|
if (prUrl && options.syncTasks !== false) {
|
|
14978
15114
|
const mergedTasksSync = syncTasksPrMetadata(
|
|
14979
|
-
|
|
15115
|
+
path14.join(config.docsDir, paths.tasksPath),
|
|
14980
15116
|
prUrl,
|
|
14981
15117
|
"Approved",
|
|
14982
15118
|
config.lang
|
|
14983
15119
|
);
|
|
14984
15120
|
const mergedDraftSync = syncPrDraftMetadata(
|
|
14985
|
-
|
|
15121
|
+
path14.join(config.docsDir, paths.prPath),
|
|
14986
15122
|
prUrl,
|
|
14987
15123
|
"Approved"
|
|
14988
15124
|
);
|
|
@@ -15253,7 +15389,7 @@ function docsCommand(program2) {
|
|
|
15253
15389
|
);
|
|
15254
15390
|
return;
|
|
15255
15391
|
}
|
|
15256
|
-
const relativeFromCwd =
|
|
15392
|
+
const relativeFromCwd = path14.relative(process.cwd(), loaded.entry.absolutePath);
|
|
15257
15393
|
console.log();
|
|
15258
15394
|
console.log(chalk9.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
15259
15395
|
console.log(
|
|
@@ -15333,7 +15469,7 @@ function detectCommand(program2) {
|
|
|
15333
15469
|
}
|
|
15334
15470
|
async function runDetect(options) {
|
|
15335
15471
|
const cwd = process.cwd();
|
|
15336
|
-
const targetCwd = options.dir ?
|
|
15472
|
+
const targetCwd = options.dir ? path14.resolve(cwd, options.dir) : cwd;
|
|
15337
15473
|
const config = await getConfig(targetCwd);
|
|
15338
15474
|
const detected = !!config;
|
|
15339
15475
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
@@ -15360,7 +15496,7 @@ async function runDetect(options) {
|
|
|
15360
15496
|
);
|
|
15361
15497
|
return;
|
|
15362
15498
|
}
|
|
15363
|
-
const configPath2 =
|
|
15499
|
+
const configPath2 = path14.join(config.docsDir, ".lee-spec-kit.json");
|
|
15364
15500
|
const configFilePresent2 = await fs.pathExists(configPath2);
|
|
15365
15501
|
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
15366
15502
|
console.log(
|
|
@@ -15394,7 +15530,7 @@ async function runDetect(options) {
|
|
|
15394
15530
|
console.log();
|
|
15395
15531
|
return;
|
|
15396
15532
|
}
|
|
15397
|
-
const configPath =
|
|
15533
|
+
const configPath = path14.join(config.docsDir, ".lee-spec-kit.json");
|
|
15398
15534
|
const configFilePresent = await fs.pathExists(configPath);
|
|
15399
15535
|
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
15400
15536
|
console.log(chalk9.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
@@ -15471,19 +15607,19 @@ function hasTemplateMarkers(content) {
|
|
|
15471
15607
|
return patterns.some((pattern) => pattern.test(content));
|
|
15472
15608
|
}
|
|
15473
15609
|
async function countFeatureDirs(ctx, docsDir, projectType) {
|
|
15474
|
-
const featuresRoot =
|
|
15610
|
+
const featuresRoot = path14.join(docsDir, "features");
|
|
15475
15611
|
if (projectType === "single") {
|
|
15476
15612
|
const dirs = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15477
|
-
return dirs.filter((value) =>
|
|
15613
|
+
return dirs.filter((value) => path14.basename(value) !== "feature-base").length;
|
|
15478
15614
|
}
|
|
15479
15615
|
const components = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15480
15616
|
let total = 0;
|
|
15481
15617
|
for (const componentDir of components) {
|
|
15482
|
-
const componentName =
|
|
15618
|
+
const componentName = path14.basename(componentDir).trim().toLowerCase();
|
|
15483
15619
|
if (!componentName || componentName === "feature-base") continue;
|
|
15484
15620
|
const dirs = await listSubdirectories(ctx.fs, componentDir);
|
|
15485
15621
|
total += dirs.filter(
|
|
15486
|
-
(value) =>
|
|
15622
|
+
(value) => path14.basename(value) !== "feature-base"
|
|
15487
15623
|
).length;
|
|
15488
15624
|
}
|
|
15489
15625
|
return total;
|
|
@@ -15495,7 +15631,7 @@ async function hasUserPrdFile(ctx, prdDir) {
|
|
|
15495
15631
|
ignoreDirs: ["node_modules"]
|
|
15496
15632
|
});
|
|
15497
15633
|
return files.some(
|
|
15498
|
-
(absolutePath) =>
|
|
15634
|
+
(absolutePath) => path14.basename(absolutePath).toLowerCase() !== "readme.md"
|
|
15499
15635
|
);
|
|
15500
15636
|
}
|
|
15501
15637
|
function finalizeChecks(checks) {
|
|
@@ -15664,7 +15800,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15664
15800
|
});
|
|
15665
15801
|
}
|
|
15666
15802
|
}
|
|
15667
|
-
const constitutionPath =
|
|
15803
|
+
const constitutionPath = path14.join(docsDir, "agents", "constitution.md");
|
|
15668
15804
|
if (!await fs.pathExists(constitutionPath)) {
|
|
15669
15805
|
checks.push({
|
|
15670
15806
|
id: "constitution_exists",
|
|
@@ -15706,7 +15842,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15706
15842
|
});
|
|
15707
15843
|
}
|
|
15708
15844
|
}
|
|
15709
|
-
const customPath =
|
|
15845
|
+
const customPath = path14.join(docsDir, "agents", "custom.md");
|
|
15710
15846
|
if (await fs.pathExists(customPath)) {
|
|
15711
15847
|
const content = await fs.readFile(customPath, "utf-8");
|
|
15712
15848
|
if (hasTemplateMarkers(content)) {
|
|
@@ -15735,7 +15871,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15735
15871
|
});
|
|
15736
15872
|
}
|
|
15737
15873
|
}
|
|
15738
|
-
const prdDir =
|
|
15874
|
+
const prdDir = path14.join(docsDir, "prd");
|
|
15739
15875
|
const featureCount = await countFeatureDirs(ctx, docsDir, config.projectType);
|
|
15740
15876
|
const prdReady = await hasUserPrdFile(ctx, prdDir);
|
|
15741
15877
|
if (!prdReady) {
|
|
@@ -15753,7 +15889,7 @@ async function runOnboardChecks(ctx) {
|
|
|
15753
15889
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
15754
15890
|
),
|
|
15755
15891
|
path: prdDir,
|
|
15756
|
-
suggestedCommand: `touch ${quotePath(
|
|
15892
|
+
suggestedCommand: `touch ${quotePath(path14.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
15757
15893
|
});
|
|
15758
15894
|
} else {
|
|
15759
15895
|
checks.push({
|
|
@@ -16191,7 +16327,7 @@ var PrePrReviewValidator = class {
|
|
|
16191
16327
|
return result.evidence;
|
|
16192
16328
|
}
|
|
16193
16329
|
async validateEvidenceWithScope(evidencePath, projectRoot) {
|
|
16194
|
-
const fullPath =
|
|
16330
|
+
const fullPath = path14.resolve(evidencePath);
|
|
16195
16331
|
if (!await fs.pathExists(fullPath)) {
|
|
16196
16332
|
throw createCliError(
|
|
16197
16333
|
"INVALID_ARGUMENT",
|
|
@@ -16289,9 +16425,9 @@ var PrePrReviewValidator = class {
|
|
|
16289
16425
|
]);
|
|
16290
16426
|
const reviewedFiles = new Set(
|
|
16291
16427
|
normalizedEvidence.files.map(
|
|
16292
|
-
(f) =>
|
|
16428
|
+
(f) => path14.relative(
|
|
16293
16429
|
projectRoot,
|
|
16294
|
-
|
|
16430
|
+
path14.resolve(projectRoot, f.path)
|
|
16295
16431
|
)
|
|
16296
16432
|
).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
|
|
16297
16433
|
);
|
|
@@ -16487,7 +16623,7 @@ var DEFAULT_EVIDENCE_FOR_ANY_MODE = {
|
|
|
16487
16623
|
residualRisks: ["none"],
|
|
16488
16624
|
commandsExecuted: []
|
|
16489
16625
|
};
|
|
16490
|
-
function
|
|
16626
|
+
function escapeRegExp6(value) {
|
|
16491
16627
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16492
16628
|
}
|
|
16493
16629
|
function normalizeDecision(raw) {
|
|
@@ -16501,14 +16637,14 @@ function normalizeDecision(raw) {
|
|
|
16501
16637
|
return null;
|
|
16502
16638
|
}
|
|
16503
16639
|
function findSpecLineIndex(lines, keys) {
|
|
16504
|
-
const escaped = keys.map((key) =>
|
|
16640
|
+
const escaped = keys.map((key) => escapeRegExp6(key));
|
|
16505
16641
|
const re = new RegExp(
|
|
16506
16642
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
|
|
16507
16643
|
);
|
|
16508
16644
|
return lines.findIndex((line) => re.test(line));
|
|
16509
16645
|
}
|
|
16510
16646
|
function replaceSpecLine(line, keys, preferredKey, value) {
|
|
16511
|
-
const escaped = keys.map((key) =>
|
|
16647
|
+
const escaped = keys.map((key) => escapeRegExp6(key));
|
|
16512
16648
|
const re = new RegExp(
|
|
16513
16649
|
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
16514
16650
|
);
|
|
@@ -16752,7 +16888,7 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16752
16888
|
);
|
|
16753
16889
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16754
16890
|
const preferred = getPreferredKeys(config.lang);
|
|
16755
|
-
const tasksPath =
|
|
16891
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
16756
16892
|
let tasksUpdated = false;
|
|
16757
16893
|
if (await fs.pathExists(tasksPath)) {
|
|
16758
16894
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -16799,8 +16935,6 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
16799
16935
|
handoffOnly: true,
|
|
16800
16936
|
advancesWorkflow: false,
|
|
16801
16937
|
reuseKey: `pre-pr:${featureRef}`,
|
|
16802
|
-
suggestedParallelism: 1,
|
|
16803
|
-
fallbackToMainAgentWhenQuotaExceeded: true,
|
|
16804
16938
|
nextStepRequirement: "generate_review_trace_then_record",
|
|
16805
16939
|
delegatedWorkRequired: true,
|
|
16806
16940
|
nextMainState: "pre_pr_review_in_progress",
|
|
@@ -16863,7 +16997,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16863
16997
|
`tasks.md not found for feature: ${feature.folderName}`
|
|
16864
16998
|
);
|
|
16865
16999
|
}
|
|
16866
|
-
const tasksPath =
|
|
17000
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
16867
17001
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
16868
17002
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
16869
17003
|
const preferred = getPreferredKeys(config.lang);
|
|
@@ -16961,7 +17095,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
16961
17095
|
}
|
|
16962
17096
|
}
|
|
16963
17097
|
}
|
|
16964
|
-
const decisionsPath =
|
|
17098
|
+
const decisionsPath = path14.join(feature.path, "decisions.md");
|
|
16965
17099
|
const decisionLogEntry = buildReportContent({
|
|
16966
17100
|
folderName: feature.folderName,
|
|
16967
17101
|
date,
|
|
@@ -16977,9 +17111,9 @@ async function runPrePrReview(featureName, options) {
|
|
|
16977
17111
|
await fs.writeFile(decisionsPath, nextDecisions, "utf-8");
|
|
16978
17112
|
}
|
|
16979
17113
|
const decisionsPathFromDocs = normalizePathForDoc(
|
|
16980
|
-
|
|
17114
|
+
path14.join(feature.docs.featurePathFromDocs, "decisions.md")
|
|
16981
17115
|
);
|
|
16982
|
-
const evidencePath =
|
|
17116
|
+
const evidencePath = path14.basename(config.docsDir) === "docs" ? normalizePathForDoc(path14.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
|
|
16983
17117
|
let nextTasks = tasksContent;
|
|
16984
17118
|
nextTasks = upsertSpecLine(
|
|
16985
17119
|
nextTasks,
|
|
@@ -17034,18 +17168,18 @@ async function runPrePrReview(featureName, options) {
|
|
|
17034
17168
|
}
|
|
17035
17169
|
console.log();
|
|
17036
17170
|
}
|
|
17037
|
-
function
|
|
17171
|
+
function escapeRegExp7(value) {
|
|
17038
17172
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17039
17173
|
}
|
|
17040
17174
|
function findSpecLineIndex2(lines, keys) {
|
|
17041
|
-
const escaped = keys.map((key) =>
|
|
17175
|
+
const escaped = keys.map((key) => escapeRegExp7(key));
|
|
17042
17176
|
const re = new RegExp(
|
|
17043
17177
|
`^\\s*-\\s*\\*\\*(?:${escaped.join("|")})\\*\\*\\s*:\\s*`
|
|
17044
17178
|
);
|
|
17045
17179
|
return lines.findIndex((line) => re.test(line));
|
|
17046
17180
|
}
|
|
17047
17181
|
function replaceSpecLine2(line, keys, preferredKey, value) {
|
|
17048
|
-
const escaped = keys.map((key) =>
|
|
17182
|
+
const escaped = keys.map((key) => escapeRegExp7(key));
|
|
17049
17183
|
const re = new RegExp(
|
|
17050
17184
|
`^(\\s*-\\s*\\*\\*)(?:${escaped.join("|")})(\\*\\*\\s*:\\s*)(.*)$`
|
|
17051
17185
|
);
|
|
@@ -17120,7 +17254,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
17120
17254
|
);
|
|
17121
17255
|
}
|
|
17122
17256
|
const feature = state.matchedFeature;
|
|
17123
|
-
const tasksPath =
|
|
17257
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
17124
17258
|
let tasksUpdated = false;
|
|
17125
17259
|
if (await fs.pathExists(tasksPath)) {
|
|
17126
17260
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -17150,12 +17284,10 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
17150
17284
|
handoffOnly: true,
|
|
17151
17285
|
advancesWorkflow: false,
|
|
17152
17286
|
reuseKey: `code-review:${feature.folderName}`,
|
|
17153
|
-
suggestedParallelism: 1,
|
|
17154
|
-
fallbackToMainAgentWhenQuotaExceeded: true,
|
|
17155
17287
|
nextMainState: "code_review_running",
|
|
17156
17288
|
tasksUpdated,
|
|
17157
17289
|
tasksPath,
|
|
17158
|
-
decisionsPath:
|
|
17290
|
+
decisionsPath: path14.join(feature.path, "decisions.md"),
|
|
17159
17291
|
prompt,
|
|
17160
17292
|
recordedAt: getLocalDateString()
|
|
17161
17293
|
};
|
|
@@ -17173,12 +17305,6 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
17173
17305
|
console.log(chalk9.gray(`- substate: ${payload.substateId}`));
|
|
17174
17306
|
console.log(chalk9.gray(`- owner: ${payload.owner}`));
|
|
17175
17307
|
console.log(chalk9.gray(`- reuse key: ${payload.reuseKey}`));
|
|
17176
|
-
console.log(chalk9.gray(`- suggested parallelism: ${payload.suggestedParallelism}`));
|
|
17177
|
-
console.log(
|
|
17178
|
-
chalk9.gray(
|
|
17179
|
-
`- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
|
|
17180
|
-
)
|
|
17181
|
-
);
|
|
17182
17308
|
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
17183
17309
|
if (tasksUpdated) {
|
|
17184
17310
|
console.log(chalk9.gray(`- tasks.md updated: ${payload.tasksPath}`));
|
|
@@ -17280,7 +17406,7 @@ async function runRequirements(options) {
|
|
|
17280
17406
|
}
|
|
17281
17407
|
for (const feature of scan.features) {
|
|
17282
17408
|
if (!feature.docs.tasksExists) continue;
|
|
17283
|
-
const tasksPath =
|
|
17409
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
17284
17410
|
let tasksContent = "";
|
|
17285
17411
|
try {
|
|
17286
17412
|
tasksContent = await ctx.fs.readFile(tasksPath, "utf-8");
|
|
@@ -17414,7 +17540,7 @@ async function runRequirements(options) {
|
|
|
17414
17540
|
process.stdout.write(`${lines.join("\n")}
|
|
17415
17541
|
`);
|
|
17416
17542
|
if (options.write) {
|
|
17417
|
-
const outputPath =
|
|
17543
|
+
const outputPath = path14.join(docsDir, "prd", "status.md");
|
|
17418
17544
|
await ctx.fs.writeFile(outputPath, `${lines.join("\n")}
|
|
17419
17545
|
`, "utf-8");
|
|
17420
17546
|
console.log(chalk9.green(`\u2705 wrote: ${outputPath}`));
|
|
@@ -17499,7 +17625,7 @@ async function resolveTaskRunContext(featureName, options) {
|
|
|
17499
17625
|
}
|
|
17500
17626
|
async function runTaskRun(featureName, options) {
|
|
17501
17627
|
const { config, feature } = await resolveTaskRunContext(featureName, options);
|
|
17502
|
-
const tasksPath =
|
|
17628
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
17503
17629
|
if (!await fs.pathExists(tasksPath)) {
|
|
17504
17630
|
throw createCliError(
|
|
17505
17631
|
"PRECONDITION_FAILED",
|
|
@@ -17556,8 +17682,6 @@ async function runTaskRun(featureName, options) {
|
|
|
17556
17682
|
owner: "subagent",
|
|
17557
17683
|
handoffOnly: true,
|
|
17558
17684
|
reuseKey: `task:${feature.folderName}:${resolvedTask.taskId}`,
|
|
17559
|
-
suggestedParallelism: 1,
|
|
17560
|
-
fallbackToMainAgentWhenQuotaExceeded: true,
|
|
17561
17685
|
nextMainState: "task_complete",
|
|
17562
17686
|
tasksUpdated,
|
|
17563
17687
|
tasksPath,
|
|
@@ -17573,12 +17697,6 @@ async function runTaskRun(featureName, options) {
|
|
|
17573
17697
|
console.log(chalk9.gray(`- substate: ${payload.substateId}`));
|
|
17574
17698
|
console.log(chalk9.gray(`- owner: ${payload.owner}`));
|
|
17575
17699
|
console.log(chalk9.gray(`- reuse key: ${payload.reuseKey}`));
|
|
17576
|
-
console.log(chalk9.gray(`- suggested parallelism: ${payload.suggestedParallelism}`));
|
|
17577
|
-
console.log(
|
|
17578
|
-
chalk9.gray(
|
|
17579
|
-
`- quota fallback: ${payload.fallbackToMainAgentWhenQuotaExceeded ? "continue in main agent" : "none"}`
|
|
17580
|
-
)
|
|
17581
|
-
);
|
|
17582
17700
|
console.log(chalk9.gray(`- next main state: ${payload.nextMainState}`));
|
|
17583
17701
|
if (tasksUpdated) {
|
|
17584
17702
|
console.log();
|
|
@@ -17658,7 +17776,7 @@ async function resolveTaskCompleteContext(featureName, options) {
|
|
|
17658
17776
|
}
|
|
17659
17777
|
async function runTaskComplete(featureName, options) {
|
|
17660
17778
|
const { feature } = await resolveTaskCompleteContext(featureName, options);
|
|
17661
|
-
const tasksPath =
|
|
17779
|
+
const tasksPath = path14.join(feature.path, "tasks.md");
|
|
17662
17780
|
if (!await fs.pathExists(tasksPath)) {
|
|
17663
17781
|
throw createCliError(
|
|
17664
17782
|
"PRECONDITION_FAILED",
|
|
@@ -17797,11 +17915,11 @@ ${version}
|
|
|
17797
17915
|
}
|
|
17798
17916
|
return `${ascii}${footer}`;
|
|
17799
17917
|
}
|
|
17800
|
-
var CACHE_FILE =
|
|
17918
|
+
var CACHE_FILE = path14.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
17801
17919
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
17802
17920
|
function getCurrentVersion() {
|
|
17803
17921
|
try {
|
|
17804
|
-
const packageJsonPath =
|
|
17922
|
+
const packageJsonPath = path14.join(__dirname$1, "..", "package.json");
|
|
17805
17923
|
if (fs.existsSync(packageJsonPath)) {
|
|
17806
17924
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17807
17925
|
return pkg.version;
|
|
@@ -17905,7 +18023,7 @@ function shouldCheckForUpdates() {
|
|
|
17905
18023
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
17906
18024
|
function getCliVersion() {
|
|
17907
18025
|
try {
|
|
17908
|
-
const packageJsonPath =
|
|
18026
|
+
const packageJsonPath = path14.join(__dirname$1, "..", "package.json");
|
|
17909
18027
|
if (fs.existsSync(packageJsonPath)) {
|
|
17910
18028
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
17911
18029
|
if (pkg?.version) return String(pkg.version);
|