lee-spec-kit 0.7.8 → 0.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +20 -19
- package/README.md +17 -19
- package/dist/index.js +732 -389
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +20 -4
- package/templates/en/common/agents/agents.md +17 -12
- package/templates/en/common/agents/skills/create-issue.md +14 -6
- package/templates/en/common/agents/skills/create-pr.md +15 -6
- package/templates/en/common/agents/skills/execute-task.md +4 -3
- package/templates/en/common/features/feature-base/tasks.md +9 -5
- package/templates/ko/common/README.md +20 -4
- package/templates/ko/common/agents/agents.md +17 -12
- package/templates/ko/common/agents/skills/create-issue.md +14 -6
- package/templates/ko/common/agents/skills/create-pr.md +15 -6
- package/templates/ko/common/agents/skills/execute-task.md +4 -3
- package/templates/ko/common/features/feature-base/tasks.md +9 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import path16 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 fs13 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 = () => path16.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 = path16.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 = path16.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 = path16.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 = path16.dirname(__filename2);
|
|
155
155
|
function getTemplatesDir() {
|
|
156
|
-
const rootDir =
|
|
157
|
-
return
|
|
156
|
+
const rootDir = path16.resolve(__dirname2, "..");
|
|
157
|
+
return path16.join(rootDir, "templates");
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// src/utils/locales/ko/cli.ts
|
|
@@ -255,7 +255,7 @@ var koCli = {
|
|
|
255
255
|
"init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
|
|
256
256
|
"init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
|
|
257
257
|
"init.log.nextSteps3": " 3. npx lee-spec-kit onboard --strict \uB85C \uCD08\uAE30 \uC124\uC815 \uC810\uAC80",
|
|
258
|
-
"init.log.nextSteps4": " 4. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 AGENTS.md \uC5C6\uC774 Codex\uB97C \uC4F4\uB2E4\uBA74 bootstrap \uC124\uCE58: npx lee-spec-kit
|
|
258
|
+
"init.log.nextSteps4": " 4. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 AGENTS.md \uC5C6\uC774 Codex\uB97C \uC4F4\uB2E4\uBA74 bootstrap helper \uB97C \uC120\uD0DD\uC801\uC73C\uB85C \uC124\uCE58: npx lee-spec-kit integrations codex",
|
|
259
259
|
"init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
|
|
260
260
|
"init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
|
|
261
261
|
"init.warn.stagedChangesSkip": '\u26A0\uFE0F \uD604\uC7AC Git index\uC5D0 \uC774\uBBF8 stage\uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. (--dir "." \uC778 \uACBD\uC6B0 \uCEE4\uBC0B \uBC94\uC704\uB97C \uC548\uC804\uD558\uAC8C \uC81C\uD55C\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uCEE4\uBC0B\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4)',
|
|
@@ -274,10 +274,10 @@ var koCli = {
|
|
|
274
274
|
"idea.nextSteps1": " 1. \uBC94\uC704, PRD Refs, \uC2B9\uACA9 \uBA54\uBAA8\uB97C \uC791\uC131",
|
|
275
275
|
"idea.nextSteps2": " 2. Feature\uB85C \uC2B9\uACA9: npx lee-spec-kit feature <name> --idea {ideaId}",
|
|
276
276
|
"idea.nextSteps3": " 3. Feature\uB85C \uB9CC\uB4E4\uC9C0 \uC54A\uC744 \uACBD\uC6B0 Dropped\uB85C \uD45C\uC2DC",
|
|
277
|
-
"setup.codexBootstrapInstalled": "\u2705 Codex bootstrap \uC124\uCE58 \uC644\uB8CC: {path}",
|
|
278
|
-
"setup.codexBootstrapAlreadyInstalled": "\u2705 Codex bootstrap \uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: {path}",
|
|
279
|
-
"setup.codexBootstrapRemoved": "\u2705 Codex bootstrap \uC81C\uAC70 \uC644\uB8CC: {path}",
|
|
280
|
-
"setup.codexBootstrapAlreadyAbsent": "\u2705 Codex bootstrap \uC774 \uC774\uBBF8 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
|
|
277
|
+
"setup.codexBootstrapInstalled": "\u2705 \uC120\uD0DD\uC801 Codex bootstrap \uC124\uCE58 \uC644\uB8CC: {path}",
|
|
278
|
+
"setup.codexBootstrapAlreadyInstalled": "\u2705 \uC120\uD0DD\uC801 Codex bootstrap \uC774 \uC774\uBBF8 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: {path}",
|
|
279
|
+
"setup.codexBootstrapRemoved": "\u2705 \uC120\uD0DD\uC801 Codex bootstrap \uC81C\uAC70 \uC644\uB8CC: {path}",
|
|
280
|
+
"setup.codexBootstrapAlreadyAbsent": "\u2705 \uC120\uD0DD\uC801 Codex bootstrap \uC774 \uC774\uBBF8 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
|
|
281
281
|
"github.cmdGithubDescription": "GitHub \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uB3C4\uC6B0\uBBF8 (issue/pr \uBCF8\uBB38 \uD15C\uD50C\uB9BF \uC0DD\uC131, \uAC80\uC99D, merge \uC7AC\uC2DC\uB3C4)",
|
|
282
282
|
"github.cmdIssueDescription": "feature \uBB38\uC11C \uAE30\uBC18 GitHub issue \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131",
|
|
283
283
|
"github.cmdPrDescription": "GitHub PR \uBCF8\uBB38 \uC0DD\uC131/\uC0DD\uC131 + tasks \uB3D9\uAE30\uD654 + merge \uC7AC\uC2DC\uB3C4",
|
|
@@ -832,7 +832,7 @@ var enCli = {
|
|
|
832
832
|
"init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
|
|
833
833
|
"init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
|
|
834
834
|
"init.log.nextSteps3": " 3. Run setup checks: npx lee-spec-kit onboard --strict",
|
|
835
|
-
"init.log.nextSteps4": " 4. If you use Codex without repo-root AGENTS.md, install bootstrap: npx lee-spec-kit
|
|
835
|
+
"init.log.nextSteps4": " 4. If you use Codex without repo-root AGENTS.md, you can optionally install the bootstrap helper: npx lee-spec-kit integrations codex",
|
|
836
836
|
"init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
|
|
837
837
|
"init.log.gitInit": "\u{1F4E6} Initializing Git...",
|
|
838
838
|
"init.warn.stagedChangesSkip": '\u26A0\uFE0F There are already staged changes in the Git index. (With --dir ".", commit scope cannot be safely restricted, so auto-commit is skipped.)',
|
|
@@ -851,10 +851,10 @@ var enCli = {
|
|
|
851
851
|
"idea.nextSteps1": " 1. Fill scope, PRD refs, and promotion notes",
|
|
852
852
|
"idea.nextSteps2": " 2. Promote it with: npx lee-spec-kit feature <name> --idea {ideaId}",
|
|
853
853
|
"idea.nextSteps3": " 3. Mark it dropped if it should not become a feature",
|
|
854
|
-
"setup.codexBootstrapInstalled": "\u2705 Codex bootstrap installed: {path}",
|
|
855
|
-
"setup.codexBootstrapAlreadyInstalled": "\u2705 Codex bootstrap already installed: {path}",
|
|
856
|
-
"setup.codexBootstrapRemoved": "\u2705 Codex bootstrap removed: {path}",
|
|
857
|
-
"setup.codexBootstrapAlreadyAbsent": "\u2705 Codex bootstrap is already absent: {path}",
|
|
854
|
+
"setup.codexBootstrapInstalled": "\u2705 Optional Codex bootstrap installed: {path}",
|
|
855
|
+
"setup.codexBootstrapAlreadyInstalled": "\u2705 Optional Codex bootstrap already installed: {path}",
|
|
856
|
+
"setup.codexBootstrapRemoved": "\u2705 Optional Codex bootstrap removed: {path}",
|
|
857
|
+
"setup.codexBootstrapAlreadyAbsent": "\u2705 Optional Codex bootstrap is already absent: {path}",
|
|
858
858
|
"github.cmdGithubDescription": "GitHub workflow helpers (issue/pr templates, validation, merge retry)",
|
|
859
859
|
"github.cmdIssueDescription": "Generate/create GitHub issue body from feature docs with validation",
|
|
860
860
|
"github.cmdPrDescription": "Generate/create GitHub PR body with validation, tasks PR sync, and merge retry",
|
|
@@ -1757,10 +1757,10 @@ var DEFAULT_STALE_MS = 2 * 6e4;
|
|
|
1757
1757
|
var RUNTIME_GIT_DIRNAME = "lee-spec-kit.runtime";
|
|
1758
1758
|
var RUNTIME_TEMP_DIRNAME = "lee-spec-kit-runtime";
|
|
1759
1759
|
function toScopeKey(value) {
|
|
1760
|
-
return createHash("sha1").update(
|
|
1760
|
+
return createHash("sha1").update(path16.resolve(value)).digest("hex").slice(0, 16);
|
|
1761
1761
|
}
|
|
1762
1762
|
function getTempRuntimeDir(scopePath) {
|
|
1763
|
-
return
|
|
1763
|
+
return path16.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
|
|
1764
1764
|
}
|
|
1765
1765
|
function resolveGitRuntimeDir(cwd) {
|
|
1766
1766
|
try {
|
|
@@ -1774,38 +1774,38 @@ function resolveGitRuntimeDir(cwd) {
|
|
|
1774
1774
|
}
|
|
1775
1775
|
).trim();
|
|
1776
1776
|
if (!out) return null;
|
|
1777
|
-
return
|
|
1777
|
+
return path16.isAbsolute(out) ? out : path16.resolve(cwd, out);
|
|
1778
1778
|
} catch {
|
|
1779
1779
|
return null;
|
|
1780
1780
|
}
|
|
1781
1781
|
}
|
|
1782
1782
|
function getRuntimeStateDir(cwd) {
|
|
1783
|
-
const resolved =
|
|
1783
|
+
const resolved = path16.resolve(cwd);
|
|
1784
1784
|
return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
|
|
1785
1785
|
}
|
|
1786
1786
|
function getDocsLockPath(docsDir) {
|
|
1787
|
-
return
|
|
1787
|
+
return path16.join(
|
|
1788
1788
|
getRuntimeStateDir(docsDir),
|
|
1789
1789
|
"locks",
|
|
1790
1790
|
`docs-${toScopeKey(docsDir)}.lock`
|
|
1791
1791
|
);
|
|
1792
1792
|
}
|
|
1793
1793
|
function getInitLockPath(targetDir) {
|
|
1794
|
-
return
|
|
1795
|
-
getRuntimeStateDir(
|
|
1794
|
+
return path16.join(
|
|
1795
|
+
getRuntimeStateDir(path16.dirname(path16.resolve(targetDir))),
|
|
1796
1796
|
"locks",
|
|
1797
1797
|
`init-${toScopeKey(targetDir)}.lock`
|
|
1798
1798
|
);
|
|
1799
1799
|
}
|
|
1800
1800
|
function getApprovalTicketStorePath(docsDir) {
|
|
1801
|
-
return
|
|
1801
|
+
return path16.join(
|
|
1802
1802
|
getRuntimeStateDir(docsDir),
|
|
1803
1803
|
"tickets",
|
|
1804
1804
|
`approval-${toScopeKey(docsDir)}.json`
|
|
1805
1805
|
);
|
|
1806
1806
|
}
|
|
1807
1807
|
function getProjectExecutionLockPath(cwd) {
|
|
1808
|
-
return
|
|
1808
|
+
return path16.join(getRuntimeStateDir(cwd), "locks", "project.lock");
|
|
1809
1809
|
}
|
|
1810
1810
|
async function isStaleLock(lockPath, staleMs) {
|
|
1811
1811
|
try {
|
|
@@ -1846,7 +1846,7 @@ function isProcessAlive(pid) {
|
|
|
1846
1846
|
}
|
|
1847
1847
|
}
|
|
1848
1848
|
async function tryAcquire(lockPath, owner) {
|
|
1849
|
-
await fs.ensureDir(
|
|
1849
|
+
await fs.ensureDir(path16.dirname(lockPath));
|
|
1850
1850
|
try {
|
|
1851
1851
|
const fd = await fs.open(lockPath, "wx");
|
|
1852
1852
|
const payload = JSON.stringify(
|
|
@@ -1923,30 +1923,30 @@ var ENGINE_MANAGED_AGENT_FILES = [
|
|
|
1923
1923
|
"pr-template.md"
|
|
1924
1924
|
];
|
|
1925
1925
|
var ENGINE_MANAGED_AGENT_DIRS = ["skills"];
|
|
1926
|
-
var ENGINE_MANAGED_FEATURE_PATH =
|
|
1926
|
+
var ENGINE_MANAGED_FEATURE_PATH = path16.join(
|
|
1927
1927
|
"features",
|
|
1928
1928
|
"feature-base"
|
|
1929
1929
|
);
|
|
1930
1930
|
async function pruneEngineManagedDocs(docsDir) {
|
|
1931
1931
|
const removed = [];
|
|
1932
1932
|
for (const file of ENGINE_MANAGED_AGENT_FILES) {
|
|
1933
|
-
const target =
|
|
1933
|
+
const target = path16.join(docsDir, "agents", file);
|
|
1934
1934
|
if (await fs.pathExists(target)) {
|
|
1935
1935
|
await fs.remove(target);
|
|
1936
|
-
removed.push(
|
|
1936
|
+
removed.push(path16.relative(docsDir, target));
|
|
1937
1937
|
}
|
|
1938
1938
|
}
|
|
1939
1939
|
for (const dir of ENGINE_MANAGED_AGENT_DIRS) {
|
|
1940
|
-
const target =
|
|
1940
|
+
const target = path16.join(docsDir, "agents", dir);
|
|
1941
1941
|
if (await fs.pathExists(target)) {
|
|
1942
1942
|
await fs.remove(target);
|
|
1943
|
-
removed.push(
|
|
1943
|
+
removed.push(path16.relative(docsDir, target));
|
|
1944
1944
|
}
|
|
1945
1945
|
}
|
|
1946
|
-
const featureBasePath =
|
|
1946
|
+
const featureBasePath = path16.join(docsDir, ENGINE_MANAGED_FEATURE_PATH);
|
|
1947
1947
|
if (await fs.pathExists(featureBasePath)) {
|
|
1948
1948
|
await fs.remove(featureBasePath);
|
|
1949
|
-
removed.push(
|
|
1949
|
+
removed.push(path16.relative(docsDir, featureBasePath));
|
|
1950
1950
|
}
|
|
1951
1951
|
return removed;
|
|
1952
1952
|
}
|
|
@@ -2127,10 +2127,10 @@ function renderManagedBlock2() {
|
|
|
2127
2127
|
function getCodexHome() {
|
|
2128
2128
|
const explicit = String(process.env.CODEX_HOME || "").trim();
|
|
2129
2129
|
if (explicit) return explicit;
|
|
2130
|
-
return
|
|
2130
|
+
return path16.join(os.homedir(), ".codex");
|
|
2131
2131
|
}
|
|
2132
2132
|
function getCodexConfigPath() {
|
|
2133
|
-
return
|
|
2133
|
+
return path16.join(getCodexHome(), "config.toml");
|
|
2134
2134
|
}
|
|
2135
2135
|
function contentIncludesRequiredBootstrap(content) {
|
|
2136
2136
|
const matchesCurrent = REQUIRED_FALLBACKS.every((value) => content.includes(value)) && REQUIRED_COMPACT_LINES.every((line) => content.includes(line));
|
|
@@ -2149,7 +2149,7 @@ async function hasLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
|
|
|
2149
2149
|
async function upsertLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
|
|
2150
2150
|
const block = renderManagedBlock2();
|
|
2151
2151
|
const segment = renderManagedSegment2();
|
|
2152
|
-
await fs.ensureDir(
|
|
2152
|
+
await fs.ensureDir(path16.dirname(filePath));
|
|
2153
2153
|
const exists = await fs.pathExists(filePath);
|
|
2154
2154
|
if (!exists) {
|
|
2155
2155
|
await fs.writeFile(filePath, block, "utf-8");
|
|
@@ -2283,37 +2283,24 @@ function validatePromptPathValue(value, lang) {
|
|
|
2283
2283
|
function validatePromptUrlValue(value, lang) {
|
|
2284
2284
|
return value.trim() ? true : tr(lang, "cli", "init.validation.enterUrl");
|
|
2285
2285
|
}
|
|
2286
|
-
var DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES = [
|
|
2287
|
-
"spec_approve",
|
|
2288
|
-
"implementation_approve"
|
|
2289
|
-
];
|
|
2290
|
-
function createDefaultApprovalConfig() {
|
|
2291
|
-
return {
|
|
2292
|
-
mode: "category",
|
|
2293
|
-
default: "skip",
|
|
2294
|
-
requireCheckCategories: [...DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES]
|
|
2295
|
-
};
|
|
2296
|
-
}
|
|
2297
2286
|
function getAncestorDirs(startDir) {
|
|
2298
2287
|
const dirs = [];
|
|
2299
|
-
let current =
|
|
2288
|
+
let current = path16.resolve(startDir);
|
|
2300
2289
|
while (true) {
|
|
2301
2290
|
dirs.push(current);
|
|
2302
|
-
const parent =
|
|
2291
|
+
const parent = path16.dirname(current);
|
|
2303
2292
|
if (parent === current) break;
|
|
2304
2293
|
current = parent;
|
|
2305
2294
|
}
|
|
2306
2295
|
return dirs;
|
|
2307
2296
|
}
|
|
2308
2297
|
function hasWorkspaceBoundary(dir) {
|
|
2309
|
-
return fs.existsSync(
|
|
2298
|
+
return fs.existsSync(path16.join(dir, "package.json")) || fs.existsSync(path16.join(dir, ".git"));
|
|
2310
2299
|
}
|
|
2311
2300
|
function getSearchBaseDirs(cwd) {
|
|
2312
2301
|
const ancestors = getAncestorDirs(cwd);
|
|
2313
2302
|
const boundaryIndex = ancestors.findIndex(hasWorkspaceBoundary);
|
|
2314
|
-
if (boundaryIndex === -1)
|
|
2315
|
-
return [ancestors[0]];
|
|
2316
|
-
}
|
|
2303
|
+
if (boundaryIndex === -1) return [ancestors[0]];
|
|
2317
2304
|
return ancestors.slice(0, boundaryIndex + 1);
|
|
2318
2305
|
}
|
|
2319
2306
|
var FEATURE_FOLDER_PATTERN = /^F\d{3,}-/i;
|
|
@@ -2322,7 +2309,7 @@ function normalizeComponentKeys(value) {
|
|
|
2322
2309
|
return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
|
|
2323
2310
|
}
|
|
2324
2311
|
async function inferComponentsFromFeaturesDir(docsDir) {
|
|
2325
|
-
const featuresPath =
|
|
2312
|
+
const featuresPath = path16.join(docsDir, "features");
|
|
2326
2313
|
if (!await fs.pathExists(featuresPath)) return [];
|
|
2327
2314
|
const entries = await fs.readdir(featuresPath, { withFileTypes: true });
|
|
2328
2315
|
const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
|
|
@@ -2330,24 +2317,42 @@ async function inferComponentsFromFeaturesDir(docsDir) {
|
|
|
2330
2317
|
);
|
|
2331
2318
|
return [...new Set(inferred)];
|
|
2332
2319
|
}
|
|
2333
|
-
|
|
2320
|
+
function toProjectConfig(docsDir, configFile, projectType, components) {
|
|
2321
|
+
return {
|
|
2322
|
+
schemaId: "lee-spec",
|
|
2323
|
+
docsDir,
|
|
2324
|
+
projectName: configFile.projectName,
|
|
2325
|
+
projectType,
|
|
2326
|
+
components: projectType === "multi" ? components : void 0,
|
|
2327
|
+
lang: configFile.lang,
|
|
2328
|
+
docsRepo: configFile.docsRepo,
|
|
2329
|
+
pushDocs: configFile.pushDocs,
|
|
2330
|
+
docsRemote: configFile.docsRemote,
|
|
2331
|
+
projectRoot: configFile.projectRoot,
|
|
2332
|
+
allowedDocsEntries: configFile.allowedDocsEntries,
|
|
2333
|
+
pr: configFile.pr,
|
|
2334
|
+
workflow: configFile.workflow,
|
|
2335
|
+
approval: configFile.approval
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
async function detectLeeSpecProject(cwd) {
|
|
2334
2339
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
2335
2340
|
const baseDirs = [
|
|
2336
|
-
...explicitDocsDir ? [
|
|
2341
|
+
...explicitDocsDir ? [path16.resolve(explicitDocsDir)] : [],
|
|
2337
2342
|
...getSearchBaseDirs(cwd)
|
|
2338
2343
|
];
|
|
2339
2344
|
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
2340
2345
|
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
2341
2346
|
for (const baseDir of baseDirs) {
|
|
2342
|
-
const resolvedBaseDir =
|
|
2347
|
+
const resolvedBaseDir = path16.resolve(baseDir);
|
|
2343
2348
|
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
2344
2349
|
visitedBaseDirs.add(resolvedBaseDir);
|
|
2345
|
-
const possibleDocsDirs = [
|
|
2350
|
+
const possibleDocsDirs = [path16.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
2346
2351
|
for (const docsDir of possibleDocsDirs) {
|
|
2347
|
-
const resolvedDocsDir =
|
|
2352
|
+
const resolvedDocsDir = path16.resolve(docsDir);
|
|
2348
2353
|
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
2349
2354
|
visitedDocsDirs.add(resolvedDocsDir);
|
|
2350
|
-
const configPath =
|
|
2355
|
+
const configPath = path16.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
2351
2356
|
if (await fs.pathExists(configPath)) {
|
|
2352
2357
|
try {
|
|
2353
2358
|
const configFile = await fs.readJson(configPath);
|
|
@@ -2361,33 +2366,32 @@ async function getConfig(cwd) {
|
|
|
2361
2366
|
Array.isArray(configFile.components) && configFile.components.length > 0 ? configFile.components : inferredComponents
|
|
2362
2367
|
);
|
|
2363
2368
|
return {
|
|
2369
|
+
detected: true,
|
|
2370
|
+
schemaId: "lee-spec",
|
|
2371
|
+
detectionSource: "config",
|
|
2364
2372
|
docsDir: resolvedDocsDir,
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
allowedDocsEntries: configFile.allowedDocsEntries,
|
|
2374
|
-
pr: configFile.pr,
|
|
2375
|
-
workflow: configFile.workflow,
|
|
2376
|
-
approval: configFile.approval
|
|
2373
|
+
configPath,
|
|
2374
|
+
configFilePresent: true,
|
|
2375
|
+
config: toProjectConfig(
|
|
2376
|
+
resolvedDocsDir,
|
|
2377
|
+
configFile,
|
|
2378
|
+
projectType,
|
|
2379
|
+
components
|
|
2380
|
+
)
|
|
2377
2381
|
};
|
|
2378
2382
|
} catch {
|
|
2379
2383
|
}
|
|
2380
2384
|
}
|
|
2381
|
-
const agentsPath =
|
|
2382
|
-
const featuresPath =
|
|
2385
|
+
const agentsPath = path16.join(resolvedDocsDir, "agents");
|
|
2386
|
+
const featuresPath = path16.join(resolvedDocsDir, "features");
|
|
2383
2387
|
if (await fs.pathExists(agentsPath) && await fs.pathExists(featuresPath)) {
|
|
2384
2388
|
const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
|
|
2385
2389
|
const projectType = inferredComponents.length > 0 ? "multi" : "single";
|
|
2386
2390
|
const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
|
|
2387
2391
|
const langProbeCandidates = [
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2392
|
+
path16.join(agentsPath, "custom.md"),
|
|
2393
|
+
path16.join(agentsPath, "constitution.md"),
|
|
2394
|
+
path16.join(agentsPath, "agents.md")
|
|
2391
2395
|
];
|
|
2392
2396
|
let lang = "en";
|
|
2393
2397
|
for (const candidate of langProbeCandidates) {
|
|
@@ -2398,11 +2402,198 @@ async function getConfig(cwd) {
|
|
|
2398
2402
|
break;
|
|
2399
2403
|
}
|
|
2400
2404
|
}
|
|
2401
|
-
return {
|
|
2405
|
+
return {
|
|
2406
|
+
detected: true,
|
|
2407
|
+
schemaId: "lee-spec",
|
|
2408
|
+
detectionSource: "heuristic",
|
|
2409
|
+
docsDir: resolvedDocsDir,
|
|
2410
|
+
configPath: null,
|
|
2411
|
+
configFilePresent: false,
|
|
2412
|
+
config: {
|
|
2413
|
+
schemaId: "lee-spec",
|
|
2414
|
+
docsDir: resolvedDocsDir,
|
|
2415
|
+
projectType,
|
|
2416
|
+
components,
|
|
2417
|
+
lang
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2402
2420
|
}
|
|
2403
2421
|
}
|
|
2404
2422
|
}
|
|
2405
|
-
return
|
|
2423
|
+
return {
|
|
2424
|
+
detected: false,
|
|
2425
|
+
schemaId: "lee-spec",
|
|
2426
|
+
detectionSource: null,
|
|
2427
|
+
docsDir: null,
|
|
2428
|
+
configPath: null,
|
|
2429
|
+
configFilePresent: false,
|
|
2430
|
+
config: null
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
function resolveLeeSpecFeaturePaths(input) {
|
|
2434
|
+
const featureFolderName = `${input.featureId}-${input.featureName}`;
|
|
2435
|
+
const featuresDir = input.projectType === "multi" ? path16.join(input.docsDir, "features", input.component || "") : path16.join(input.docsDir, "features");
|
|
2436
|
+
const featureDir = path16.join(featuresDir, featureFolderName);
|
|
2437
|
+
return {
|
|
2438
|
+
featureFolderName,
|
|
2439
|
+
featuresDir,
|
|
2440
|
+
featureDir,
|
|
2441
|
+
featurePathFromDocs: path16.relative(input.docsDir, featureDir)
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
async function getNextLeeSpecFeatureId(docsDir, projectType, components) {
|
|
2445
|
+
const featuresDir = path16.join(docsDir, "features");
|
|
2446
|
+
let max = 0;
|
|
2447
|
+
const scanDirs = [];
|
|
2448
|
+
if (projectType === "multi") {
|
|
2449
|
+
scanDirs.push(
|
|
2450
|
+
...components.map((component) => path16.join(featuresDir, component))
|
|
2451
|
+
);
|
|
2452
|
+
} else {
|
|
2453
|
+
scanDirs.push(featuresDir);
|
|
2454
|
+
}
|
|
2455
|
+
for (const dir of scanDirs) {
|
|
2456
|
+
if (!await fs.pathExists(dir)) continue;
|
|
2457
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2458
|
+
for (const entry of entries) {
|
|
2459
|
+
if (!entry.isDirectory()) continue;
|
|
2460
|
+
const match = entry.name.match(/^F(\d+)-/);
|
|
2461
|
+
if (!match) continue;
|
|
2462
|
+
const num = parseInt(match[1], 10);
|
|
2463
|
+
if (num > max) max = num;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
const next = max + 1;
|
|
2467
|
+
const width = Math.max(3, String(next).length);
|
|
2468
|
+
return `F${String(next).padStart(width, "0")}`;
|
|
2469
|
+
}
|
|
2470
|
+
function parseFeatureFolderName(folderName, component) {
|
|
2471
|
+
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
2472
|
+
if (!match) return null;
|
|
2473
|
+
const featureRef = {
|
|
2474
|
+
id: match[1],
|
|
2475
|
+
slug: match[2],
|
|
2476
|
+
folderName
|
|
2477
|
+
};
|
|
2478
|
+
if (component) {
|
|
2479
|
+
featureRef.component = component;
|
|
2480
|
+
}
|
|
2481
|
+
return featureRef;
|
|
2482
|
+
}
|
|
2483
|
+
async function listLeeSpecFeatures(cwd) {
|
|
2484
|
+
const detected = await detectLeeSpecProject(cwd);
|
|
2485
|
+
const docsDir = detected.docsDir;
|
|
2486
|
+
if (!docsDir) return [];
|
|
2487
|
+
const featuresRoot = path16.join(docsDir, "features");
|
|
2488
|
+
if (!await fs.pathExists(featuresRoot)) return [];
|
|
2489
|
+
const refs = [];
|
|
2490
|
+
const topLevelEntries = await fs.readdir(featuresRoot, { withFileTypes: true });
|
|
2491
|
+
for (const entry of topLevelEntries) {
|
|
2492
|
+
if (!entry.isDirectory()) continue;
|
|
2493
|
+
const singleProjectFeature = parseFeatureFolderName(entry.name);
|
|
2494
|
+
if (singleProjectFeature) {
|
|
2495
|
+
refs.push(singleProjectFeature);
|
|
2496
|
+
continue;
|
|
2497
|
+
}
|
|
2498
|
+
const component = entry.name.trim().toLowerCase();
|
|
2499
|
+
if (!component) continue;
|
|
2500
|
+
const componentDir = path16.join(featuresRoot, entry.name);
|
|
2501
|
+
const componentEntries = await fs.readdir(componentDir, { withFileTypes: true });
|
|
2502
|
+
for (const child of componentEntries) {
|
|
2503
|
+
if (!child.isDirectory()) continue;
|
|
2504
|
+
const featureRef = parseFeatureFolderName(child.name, component);
|
|
2505
|
+
if (featureRef) refs.push(featureRef);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
refs.sort((left, right) => {
|
|
2509
|
+
const leftKey = `${left.id || ""}:${left.component || ""}:${left.folderName}`;
|
|
2510
|
+
const rightKey = `${right.id || ""}:${right.component || ""}:${right.folderName}`;
|
|
2511
|
+
return leftKey.localeCompare(rightKey);
|
|
2512
|
+
});
|
|
2513
|
+
return refs;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
// src/adapters/schema/lee-spec-kit/index.ts
|
|
2517
|
+
var leeSpecSchemaAdapter = {
|
|
2518
|
+
schemaId: "lee-spec",
|
|
2519
|
+
async detect(cwd) {
|
|
2520
|
+
const detection = await detectLeeSpecProject(cwd);
|
|
2521
|
+
return {
|
|
2522
|
+
detected: detection.detected,
|
|
2523
|
+
docsDir: detection.docsDir,
|
|
2524
|
+
schemaId: detection.schemaId,
|
|
2525
|
+
detectionSource: detection.detectionSource,
|
|
2526
|
+
config: detection.config,
|
|
2527
|
+
configPath: detection.configPath,
|
|
2528
|
+
configFilePresent: detection.configFilePresent
|
|
2529
|
+
};
|
|
2530
|
+
},
|
|
2531
|
+
async listFeatures(cwd) {
|
|
2532
|
+
return listLeeSpecFeatures(cwd);
|
|
2533
|
+
},
|
|
2534
|
+
async getNextFeatureId(input) {
|
|
2535
|
+
return getNextLeeSpecFeatureId(
|
|
2536
|
+
input.docsDir,
|
|
2537
|
+
input.projectType,
|
|
2538
|
+
input.components
|
|
2539
|
+
);
|
|
2540
|
+
},
|
|
2541
|
+
resolveFeaturePaths(input) {
|
|
2542
|
+
return resolveLeeSpecFeaturePaths(input);
|
|
2543
|
+
}
|
|
2544
|
+
};
|
|
2545
|
+
|
|
2546
|
+
// src/adapters/schema/index.ts
|
|
2547
|
+
var SCHEMA_ADAPTERS = [leeSpecSchemaAdapter];
|
|
2548
|
+
function createEmptyDetection() {
|
|
2549
|
+
return {
|
|
2550
|
+
detected: false,
|
|
2551
|
+
docsDir: null,
|
|
2552
|
+
schemaId: null,
|
|
2553
|
+
detectionSource: null,
|
|
2554
|
+
config: null,
|
|
2555
|
+
configPath: null,
|
|
2556
|
+
configFilePresent: false,
|
|
2557
|
+
adapter: null
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
function getSchemaAdapterById(schemaId) {
|
|
2561
|
+
if (!schemaId) return null;
|
|
2562
|
+
return SCHEMA_ADAPTERS.find((adapter) => adapter.schemaId === schemaId) ?? null;
|
|
2563
|
+
}
|
|
2564
|
+
function getSchemaAdapterForConfig(config) {
|
|
2565
|
+
return getSchemaAdapterById(config?.schemaId ?? null);
|
|
2566
|
+
}
|
|
2567
|
+
async function detectSchemaProject(cwd) {
|
|
2568
|
+
for (const adapter of SCHEMA_ADAPTERS) {
|
|
2569
|
+
const detection = await adapter.detect(cwd);
|
|
2570
|
+
if (detection.detected) {
|
|
2571
|
+
return {
|
|
2572
|
+
...detection,
|
|
2573
|
+
adapter
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
return createEmptyDetection();
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// src/config/load.ts
|
|
2581
|
+
async function getConfig(cwd) {
|
|
2582
|
+
const detected = await detectSchemaProject(cwd);
|
|
2583
|
+
return detected.config;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// src/config/types.ts
|
|
2587
|
+
var DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES = [
|
|
2588
|
+
"spec_approve",
|
|
2589
|
+
"implementation_approve"
|
|
2590
|
+
];
|
|
2591
|
+
function createDefaultApprovalConfig() {
|
|
2592
|
+
return {
|
|
2593
|
+
mode: "category",
|
|
2594
|
+
default: "skip",
|
|
2595
|
+
requireCheckCategories: [...DEFAULT_APPROVAL_REQUIRE_CHECK_CATEGORIES]
|
|
2596
|
+
};
|
|
2406
2597
|
}
|
|
2407
2598
|
|
|
2408
2599
|
// src/commands/init.ts
|
|
@@ -2468,7 +2659,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
2468
2659
|
}
|
|
2469
2660
|
async function runInit(options) {
|
|
2470
2661
|
const cwd = process.cwd();
|
|
2471
|
-
const defaultName =
|
|
2662
|
+
const defaultName = path16.basename(cwd);
|
|
2472
2663
|
let projectName = options.name || defaultName;
|
|
2473
2664
|
let projectType = options.type;
|
|
2474
2665
|
let components = parseComponentsOption(options.components);
|
|
@@ -2479,7 +2670,7 @@ async function runInit(options) {
|
|
|
2479
2670
|
let docsRemote = options.docsRemote;
|
|
2480
2671
|
let projectRoot;
|
|
2481
2672
|
const componentProjectRoots = options.componentProjectRoots ? parseComponentProjectRootsOption(options.componentProjectRoots) : {};
|
|
2482
|
-
const targetDir =
|
|
2673
|
+
const targetDir = path16.resolve(cwd, options.dir || "./docs");
|
|
2483
2674
|
const skipPrompts = !!options.yes || !!options.nonInteractive;
|
|
2484
2675
|
if (options.docsRepo && !["embedded", "standalone"].includes(options.docsRepo)) {
|
|
2485
2676
|
throw createCliError(
|
|
@@ -2876,7 +3067,7 @@ async function runInit(options) {
|
|
|
2876
3067
|
);
|
|
2877
3068
|
console.log();
|
|
2878
3069
|
const templatesDir = getTemplatesDir();
|
|
2879
|
-
const commonPath =
|
|
3070
|
+
const commonPath = path16.join(templatesDir, lang, "common");
|
|
2880
3071
|
if (!await fs.pathExists(commonPath)) {
|
|
2881
3072
|
throw new Error(
|
|
2882
3073
|
tr(lang, "cli", "init.error.templateNotFound", { path: commonPath })
|
|
@@ -2885,11 +3076,11 @@ async function runInit(options) {
|
|
|
2885
3076
|
const fsAdapter = new DefaultFileSystemAdapter();
|
|
2886
3077
|
await copyTemplates(fsAdapter, commonPath, targetDir);
|
|
2887
3078
|
if (projectType === "multi") {
|
|
2888
|
-
const featuresRoot =
|
|
3079
|
+
const featuresRoot = path16.join(targetDir, "features");
|
|
2889
3080
|
for (const component of components) {
|
|
2890
|
-
const componentDir =
|
|
3081
|
+
const componentDir = path16.join(featuresRoot, component);
|
|
2891
3082
|
await fs.ensureDir(componentDir);
|
|
2892
|
-
const readmePath =
|
|
3083
|
+
const readmePath = path16.join(componentDir, "README.md");
|
|
2893
3084
|
if (!await fs.pathExists(readmePath)) {
|
|
2894
3085
|
await fs.writeFile(
|
|
2895
3086
|
readmePath,
|
|
@@ -2916,6 +3107,7 @@ async function runInit(options) {
|
|
|
2916
3107
|
createdAt: getLocalDateString(),
|
|
2917
3108
|
docsRepo,
|
|
2918
3109
|
workflow: {
|
|
3110
|
+
preset: workflowMode,
|
|
2919
3111
|
mode: workflowMode,
|
|
2920
3112
|
requireWorktree: false,
|
|
2921
3113
|
codeDirtyScope: "auto",
|
|
@@ -2948,20 +3140,20 @@ async function runInit(options) {
|
|
|
2948
3140
|
config.projectRoot = projectRoot;
|
|
2949
3141
|
}
|
|
2950
3142
|
}
|
|
2951
|
-
const configPath =
|
|
3143
|
+
const configPath = path16.join(targetDir, ".lee-spec-kit.json");
|
|
2952
3144
|
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
2953
3145
|
const extraCommitPathsAbs = [];
|
|
2954
3146
|
try {
|
|
2955
3147
|
if (docsRepo === "embedded") {
|
|
2956
3148
|
const repoRoot = getGitTopLevelOrNull(cwd) || cwd;
|
|
2957
|
-
const agentsMdPath =
|
|
3149
|
+
const agentsMdPath = path16.join(repoRoot, "AGENTS.md");
|
|
2958
3150
|
const result = await upsertLeeSpecKitAgentsMd(agentsMdPath, {
|
|
2959
3151
|
lang,
|
|
2960
3152
|
docsRepo
|
|
2961
3153
|
});
|
|
2962
3154
|
if (result.changed) extraCommitPathsAbs.push(agentsMdPath);
|
|
2963
3155
|
} else {
|
|
2964
|
-
await upsertLeeSpecKitAgentsMd(
|
|
3156
|
+
await upsertLeeSpecKitAgentsMd(path16.join(targetDir, "AGENTS.md"), {
|
|
2965
3157
|
lang,
|
|
2966
3158
|
docsRepo
|
|
2967
3159
|
});
|
|
@@ -2971,16 +3163,16 @@ async function runInit(options) {
|
|
|
2971
3163
|
} else if (projectRoot && typeof projectRoot === "object") {
|
|
2972
3164
|
roots.push(...Object.values(projectRoot));
|
|
2973
3165
|
}
|
|
2974
|
-
const resolvedCwd =
|
|
3166
|
+
const resolvedCwd = path16.resolve(cwd);
|
|
2975
3167
|
for (const raw of roots) {
|
|
2976
3168
|
const value = String(raw || "").trim();
|
|
2977
3169
|
if (!value) continue;
|
|
2978
|
-
const abs =
|
|
2979
|
-
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${
|
|
3170
|
+
const abs = path16.resolve(cwd, value);
|
|
3171
|
+
if (abs === resolvedCwd || abs.startsWith(`${resolvedCwd}${path16.sep}`)) {
|
|
2980
3172
|
if (await fs.pathExists(abs)) {
|
|
2981
3173
|
const stat = await fs.stat(abs);
|
|
2982
3174
|
if (stat.isDirectory()) {
|
|
2983
|
-
await upsertLeeSpecKitAgentsMd(
|
|
3175
|
+
await upsertLeeSpecKitAgentsMd(path16.join(abs, "AGENTS.md"), {
|
|
2984
3176
|
lang,
|
|
2985
3177
|
docsRepo
|
|
2986
3178
|
});
|
|
@@ -3073,7 +3265,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
3073
3265
|
console.log(chalk9.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
3074
3266
|
runGitOrThrow(["init"], gitWorkdir);
|
|
3075
3267
|
}
|
|
3076
|
-
const relativePath = docsRepo === "standalone" ? "." :
|
|
3268
|
+
const relativePath = docsRepo === "standalone" ? "." : path16.relative(gitWorkdir, targetDir);
|
|
3077
3269
|
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
3078
3270
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
3079
3271
|
console.log(chalk9.yellow(tr(lang, "cli", "init.warn.stagedChangesSkip")));
|
|
@@ -3100,7 +3292,7 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote, ext
|
|
|
3100
3292
|
console.log();
|
|
3101
3293
|
return;
|
|
3102
3294
|
}
|
|
3103
|
-
const extraRelativePaths = extraCommitPathsAbs.map((absPath) =>
|
|
3295
|
+
const extraRelativePaths = extraCommitPathsAbs.map((absPath) => path16.relative(gitWorkdir, absPath)).map((p) => p.replace(/\\/g, "/").trim()).filter((p) => !!p && p !== "." && !p.startsWith("../"));
|
|
3104
3296
|
const pathsToStage = [relativePath, ...extraRelativePaths];
|
|
3105
3297
|
for (const p of pathsToStage) {
|
|
3106
3298
|
runGitOrThrow(["add", p], gitWorkdir);
|
|
@@ -3188,13 +3380,13 @@ async function patchMarkdownIfExists(filePath, transform) {
|
|
|
3188
3380
|
await fs.writeFile(filePath, transform(content), "utf-8");
|
|
3189
3381
|
}
|
|
3190
3382
|
async function applyLocalWorkflowTemplateToFeatureDir(featureDir, lang) {
|
|
3191
|
-
await patchMarkdownIfExists(
|
|
3383
|
+
await patchMarkdownIfExists(path16.join(featureDir, "spec.md"), sanitizeSpecForLocal);
|
|
3192
3384
|
await patchMarkdownIfExists(
|
|
3193
|
-
|
|
3385
|
+
path16.join(featureDir, "tasks.md"),
|
|
3194
3386
|
(content) => sanitizeTasksForLocal(content, lang)
|
|
3195
3387
|
);
|
|
3196
|
-
await fs.remove(
|
|
3197
|
-
await fs.remove(
|
|
3388
|
+
await fs.remove(path16.join(featureDir, "issue.md"));
|
|
3389
|
+
await fs.remove(path16.join(featureDir, "pr.md"));
|
|
3198
3390
|
}
|
|
3199
3391
|
var IDEA_REF_PATTERN = /\b(I\d{3,}(?:-[A-Za-z0-9._-]+)?)\b/;
|
|
3200
3392
|
var IDEA_PATH_PATTERN = /\b(?:\.\/)?docs\/ideas\/[^\s]+\.md\b/;
|
|
@@ -3206,7 +3398,7 @@ function extractExplicitIdeaRef(requestText) {
|
|
|
3206
3398
|
return null;
|
|
3207
3399
|
}
|
|
3208
3400
|
async function resolveIdeaReference(docsDir, ref, lang) {
|
|
3209
|
-
const ideasDir =
|
|
3401
|
+
const ideasDir = path16.join(docsDir, "ideas");
|
|
3210
3402
|
const trimmedRef = ref.trim();
|
|
3211
3403
|
if (!trimmedRef) {
|
|
3212
3404
|
throw createCliError(
|
|
@@ -3215,7 +3407,7 @@ async function resolveIdeaReference(docsDir, ref, lang) {
|
|
|
3215
3407
|
);
|
|
3216
3408
|
}
|
|
3217
3409
|
if (trimmedRef.includes("/") || trimmedRef.endsWith(".md")) {
|
|
3218
|
-
const candidate =
|
|
3410
|
+
const candidate = path16.resolve(process.cwd(), trimmedRef);
|
|
3219
3411
|
if (await fs.pathExists(candidate)) {
|
|
3220
3412
|
return { path: candidate };
|
|
3221
3413
|
}
|
|
@@ -3234,11 +3426,11 @@ async function resolveIdeaReference(docsDir, ref, lang) {
|
|
|
3234
3426
|
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
|
|
3235
3427
|
const exactName = `${trimmedRef}.md`;
|
|
3236
3428
|
if (files.includes(exactName)) {
|
|
3237
|
-
return { path:
|
|
3429
|
+
return { path: path16.join(ideasDir, exactName) };
|
|
3238
3430
|
}
|
|
3239
3431
|
const byId = /^I\d{3,}$/.test(trimmedRef) ? files.filter((name) => name.startsWith(`${trimmedRef}-`)) : [];
|
|
3240
3432
|
if (byId.length === 1) {
|
|
3241
|
-
return { path:
|
|
3433
|
+
return { path: path16.join(ideasDir, byId[0]) };
|
|
3242
3434
|
}
|
|
3243
3435
|
if (byId.length > 1) {
|
|
3244
3436
|
throw createCliError(
|
|
@@ -3262,7 +3454,7 @@ async function readIdeaMetadataValue(ideaPath, label) {
|
|
|
3262
3454
|
async function deriveFeatureNameFromIdea(ideaPath) {
|
|
3263
3455
|
const ideaName = await readIdeaMetadataValue(ideaPath, "Idea Name");
|
|
3264
3456
|
if (ideaName && ideaName !== "-") return ideaName;
|
|
3265
|
-
const basename =
|
|
3457
|
+
const basename = path16.basename(ideaPath, ".md");
|
|
3266
3458
|
return basename.replace(/^I\d{3,}-/, "");
|
|
3267
3459
|
}
|
|
3268
3460
|
function escapeRegExp(value) {
|
|
@@ -3348,6 +3540,7 @@ async function runFeature(name, options) {
|
|
|
3348
3540
|
}
|
|
3349
3541
|
const { docsDir, projectType, lang } = config;
|
|
3350
3542
|
const projectName = config.projectName;
|
|
3543
|
+
const schemaAdapter = getSchemaAdapterForConfig(config);
|
|
3351
3544
|
const configuredComponents = resolveProjectComponents(
|
|
3352
3545
|
projectType,
|
|
3353
3546
|
config.components
|
|
@@ -3410,27 +3603,38 @@ async function runFeature(name, options) {
|
|
|
3410
3603
|
);
|
|
3411
3604
|
featureId = options.id;
|
|
3412
3605
|
} else {
|
|
3413
|
-
|
|
3606
|
+
if (!schemaAdapter?.getNextFeatureId) {
|
|
3607
|
+
throw createCliError(
|
|
3608
|
+
"PRECONDITION_FAILED",
|
|
3609
|
+
`Schema "${config.schemaId || "unknown"}" does not support feature ID allocation.`
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3612
|
+
featureId = await schemaAdapter.getNextFeatureId({
|
|
3414
3613
|
docsDir,
|
|
3415
3614
|
projectType,
|
|
3416
|
-
configuredComponents
|
|
3417
|
-
);
|
|
3615
|
+
components: configuredComponents
|
|
3616
|
+
});
|
|
3418
3617
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3618
|
+
if (!schemaAdapter?.resolveFeaturePaths) {
|
|
3619
|
+
throw createCliError(
|
|
3620
|
+
"PRECONDITION_FAILED",
|
|
3621
|
+
`Schema "${config.schemaId || "unknown"}" does not support feature path resolution.`
|
|
3622
|
+
);
|
|
3424
3623
|
}
|
|
3425
|
-
const featureFolderName =
|
|
3426
|
-
|
|
3624
|
+
const { featureFolderName, featureDir, featurePathFromDocs } = schemaAdapter.resolveFeaturePaths({
|
|
3625
|
+
docsDir,
|
|
3626
|
+
projectType,
|
|
3627
|
+
component: projectType === "multi" ? component : void 0,
|
|
3628
|
+
featureId,
|
|
3629
|
+
featureName: name
|
|
3630
|
+
});
|
|
3427
3631
|
if (await fs.pathExists(featureDir)) {
|
|
3428
3632
|
throw createCliError(
|
|
3429
3633
|
"INVALID_ARGUMENT",
|
|
3430
3634
|
tr(lang, "cli", "feature.folderExists", { path: featureDir })
|
|
3431
3635
|
);
|
|
3432
3636
|
}
|
|
3433
|
-
const featureBasePath =
|
|
3637
|
+
const featureBasePath = path16.join(
|
|
3434
3638
|
getTemplatesDir(),
|
|
3435
3639
|
lang,
|
|
3436
3640
|
"common",
|
|
@@ -3477,8 +3681,8 @@ async function runFeature(name, options) {
|
|
|
3477
3681
|
await replaceInFiles(fsAdapter, featureDir, replacements);
|
|
3478
3682
|
if (linkedIdea) {
|
|
3479
3683
|
await stampIdeaReferenceInSpec(
|
|
3480
|
-
|
|
3481
|
-
|
|
3684
|
+
path16.join(featureDir, "spec.md"),
|
|
3685
|
+
path16.relative(featureDir, linkedIdea.path)
|
|
3482
3686
|
);
|
|
3483
3687
|
await markIdeaAsFeatureized(linkedIdea.path, featureFolderName);
|
|
3484
3688
|
}
|
|
@@ -3506,7 +3710,7 @@ async function runFeature(name, options) {
|
|
|
3506
3710
|
featureName: name,
|
|
3507
3711
|
component: projectType === "multi" ? component : void 0,
|
|
3508
3712
|
featurePath: featureDir,
|
|
3509
|
-
featurePathFromDocs
|
|
3713
|
+
featurePathFromDocs
|
|
3510
3714
|
};
|
|
3511
3715
|
},
|
|
3512
3716
|
{ owner: "feature" }
|
|
@@ -3566,9 +3770,9 @@ function escapeRegExp2(value) {
|
|
|
3566
3770
|
async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
3567
3771
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
3568
3772
|
const candidates = [
|
|
3569
|
-
...explicitDocsDir ? [
|
|
3570
|
-
|
|
3571
|
-
|
|
3773
|
+
...explicitDocsDir ? [path16.resolve(explicitDocsDir)] : [],
|
|
3774
|
+
path16.resolve(cwd, "docs"),
|
|
3775
|
+
path16.resolve(cwd)
|
|
3572
3776
|
];
|
|
3573
3777
|
const endAt = Date.now() + timeoutMs;
|
|
3574
3778
|
while (Date.now() < endAt) {
|
|
@@ -3594,33 +3798,6 @@ async function waitForConfigAfterInit(cwd, timeoutMs = 8e3) {
|
|
|
3594
3798
|
}
|
|
3595
3799
|
return getConfig(cwd);
|
|
3596
3800
|
}
|
|
3597
|
-
async function getNextFeatureId(docsDir, projectType, components) {
|
|
3598
|
-
const featuresDir = path15.join(docsDir, "features");
|
|
3599
|
-
let max = 0;
|
|
3600
|
-
const scanDirs = [];
|
|
3601
|
-
if (projectType === "multi") {
|
|
3602
|
-
scanDirs.push(
|
|
3603
|
-
...components.map((component) => path15.join(featuresDir, component))
|
|
3604
|
-
);
|
|
3605
|
-
} else {
|
|
3606
|
-
scanDirs.push(featuresDir);
|
|
3607
|
-
}
|
|
3608
|
-
for (const dir of scanDirs) {
|
|
3609
|
-
if (!await fs.pathExists(dir)) continue;
|
|
3610
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
3611
|
-
for (const entry of entries) {
|
|
3612
|
-
if (!entry.isDirectory()) continue;
|
|
3613
|
-
const match = entry.name.match(/^F(\d+)-/);
|
|
3614
|
-
if (match) {
|
|
3615
|
-
const num = parseInt(match[1], 10);
|
|
3616
|
-
if (num > max) max = num;
|
|
3617
|
-
}
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
const next = max + 1;
|
|
3621
|
-
const width = Math.max(3, String(next).length);
|
|
3622
|
-
return `F${String(next).padStart(width, "0")}`;
|
|
3623
|
-
}
|
|
3624
3801
|
function ideaCommand(program2) {
|
|
3625
3802
|
program2.command("idea <name>").description("Create a new indexed idea document").option("--component <component>", "Component name (optional)").option("--id <id>", "Idea ID (default: auto)").option("-d, --desc <description>", "Idea description for the document").option("--non-interactive", "Reserved for parity with other generators").option("--json", "Output in JSON format for agents").action(async (name, options) => {
|
|
3626
3803
|
try {
|
|
@@ -3701,16 +3878,16 @@ async function runIdea(name, options) {
|
|
|
3701
3878
|
getDocsLockPath(docsDir),
|
|
3702
3879
|
async () => {
|
|
3703
3880
|
const ideaId = options.id ? validateProvidedIdeaId(options.id, lang) : await getNextIdeaId(docsDir);
|
|
3704
|
-
const ideasDir =
|
|
3881
|
+
const ideasDir = path16.join(docsDir, "ideas");
|
|
3705
3882
|
const ideaFileName = `${ideaId}-${name}.md`;
|
|
3706
|
-
const ideaPath =
|
|
3883
|
+
const ideaPath = path16.join(ideasDir, ideaFileName);
|
|
3707
3884
|
if (await fs.pathExists(ideaPath)) {
|
|
3708
3885
|
throw createCliError(
|
|
3709
3886
|
"INVALID_ARGUMENT",
|
|
3710
3887
|
tr(lang, "cli", "idea.fileExists", { path: ideaPath })
|
|
3711
3888
|
);
|
|
3712
3889
|
}
|
|
3713
|
-
const templatePath =
|
|
3890
|
+
const templatePath = path16.join(
|
|
3714
3891
|
getTemplatesDir(),
|
|
3715
3892
|
lang,
|
|
3716
3893
|
"common",
|
|
@@ -3748,7 +3925,7 @@ async function runIdea(name, options) {
|
|
|
3748
3925
|
ideaName: name,
|
|
3749
3926
|
component: component || void 0,
|
|
3750
3927
|
ideaPath,
|
|
3751
|
-
ideaPathFromDocs:
|
|
3928
|
+
ideaPathFromDocs: path16.relative(docsDir, ideaPath)
|
|
3752
3929
|
};
|
|
3753
3930
|
},
|
|
3754
3931
|
{ owner: "idea" }
|
|
@@ -3766,7 +3943,7 @@ function applyIdeaTemplate(template, values) {
|
|
|
3766
3943
|
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);
|
|
3767
3944
|
}
|
|
3768
3945
|
async function getNextIdeaId(docsDir) {
|
|
3769
|
-
const ideasDir =
|
|
3946
|
+
const ideasDir = path16.join(docsDir, "ideas");
|
|
3770
3947
|
let max = 0;
|
|
3771
3948
|
if (await fs.pathExists(ideasDir)) {
|
|
3772
3949
|
const entries = await fs.readdir(ideasDir, { withFileTypes: true });
|
|
@@ -3895,25 +4072,33 @@ var SUBAGENT_HANDOFF_CATEGORIES = [
|
|
|
3895
4072
|
"pre_pr_review_run"
|
|
3896
4073
|
];
|
|
3897
4074
|
|
|
3898
|
-
// src/
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
"
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
4075
|
+
// src/core/workflow/presets.ts
|
|
4076
|
+
function resolveWorkflowPreset(preset, mode) {
|
|
4077
|
+
const normalizedPreset = preset === "local" || preset === "strict" || preset === "github" ? preset : void 0;
|
|
4078
|
+
if (normalizedPreset === "local" || mode === "local") {
|
|
4079
|
+
return {
|
|
4080
|
+
mode: "local",
|
|
4081
|
+
requireIssue: false,
|
|
4082
|
+
requireBranch: false,
|
|
4083
|
+
requireWorktree: false,
|
|
4084
|
+
requirePr: false,
|
|
4085
|
+
requireReview: false,
|
|
4086
|
+
requireMerge: false
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
if (normalizedPreset === "strict") {
|
|
4090
|
+
return {
|
|
4091
|
+
mode: "github",
|
|
4092
|
+
requireIssue: true,
|
|
4093
|
+
requireBranch: true,
|
|
4094
|
+
requireWorktree: true,
|
|
4095
|
+
requirePr: true,
|
|
4096
|
+
requireReview: true,
|
|
4097
|
+
requireMerge: true
|
|
4098
|
+
};
|
|
4099
|
+
}
|
|
4100
|
+
return {
|
|
4101
|
+
mode: "github",
|
|
3917
4102
|
requireIssue: true,
|
|
3918
4103
|
requireBranch: true,
|
|
3919
4104
|
requireWorktree: false,
|
|
@@ -3921,6 +4106,20 @@ function resolveWorkflowPolicy(workflow) {
|
|
|
3921
4106
|
requireReview: true,
|
|
3922
4107
|
requireMerge: true
|
|
3923
4108
|
};
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
// src/core/workflow/policies.ts
|
|
4112
|
+
var DEFAULT_PRE_PR_REVIEW_SKILLS = ["code-review-excellence"];
|
|
4113
|
+
var DEFAULT_PRE_PR_DECISION_ENUM = [
|
|
4114
|
+
"approve",
|
|
4115
|
+
"changes_requested",
|
|
4116
|
+
"blocked"
|
|
4117
|
+
];
|
|
4118
|
+
function resolveWorkflowPolicy(workflow) {
|
|
4119
|
+
const policy = resolveWorkflowPreset(
|
|
4120
|
+
workflow?.preset,
|
|
4121
|
+
workflow?.mode
|
|
4122
|
+
);
|
|
3924
4123
|
if (typeof workflow?.requireIssue === "boolean") {
|
|
3925
4124
|
policy.requireIssue = workflow.requireIssue;
|
|
3926
4125
|
}
|
|
@@ -3986,18 +4185,9 @@ function normalizeDecisionEnumList(input) {
|
|
|
3986
4185
|
for (const raw of input) {
|
|
3987
4186
|
const value = String(raw || "").trim().toLowerCase();
|
|
3988
4187
|
if (!value) continue;
|
|
3989
|
-
if (value === "approve")
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
}
|
|
3993
|
-
if (value === "changes_requested") {
|
|
3994
|
-
deduped.add("changes_requested");
|
|
3995
|
-
continue;
|
|
3996
|
-
}
|
|
3997
|
-
if (value === "blocked") {
|
|
3998
|
-
deduped.add("blocked");
|
|
3999
|
-
continue;
|
|
4000
|
-
}
|
|
4188
|
+
if (value === "approve") deduped.add("approve");
|
|
4189
|
+
if (value === "changes_requested") deduped.add("changes_requested");
|
|
4190
|
+
if (value === "blocked") deduped.add("blocked");
|
|
4001
4191
|
}
|
|
4002
4192
|
return [...deduped];
|
|
4003
4193
|
}
|
|
@@ -4034,6 +4224,15 @@ function resolvePrePrReviewPolicy(workflow) {
|
|
|
4034
4224
|
};
|
|
4035
4225
|
}
|
|
4036
4226
|
|
|
4227
|
+
// src/core/workflow/engine.ts
|
|
4228
|
+
function resolveWorkflowRuntime(workflow) {
|
|
4229
|
+
return {
|
|
4230
|
+
workflowPolicy: resolveWorkflowPolicy(workflow),
|
|
4231
|
+
prePrReviewPolicy: resolvePrePrReviewPolicy(workflow),
|
|
4232
|
+
taskCommitGatePolicy: resolveTaskCommitGatePolicy(workflow)
|
|
4233
|
+
};
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4037
4236
|
// src/utils/agent-orchestration.ts
|
|
4038
4237
|
function getPrePrReviewPrompt(lang, skills, fallbackText) {
|
|
4039
4238
|
if (lang === "ko") {
|
|
@@ -4207,7 +4406,7 @@ function isNonNegativeIntegerValue(value) {
|
|
|
4207
4406
|
}
|
|
4208
4407
|
function isLikelyCurrentPrePrReviewEvidence(evidencePath, feature) {
|
|
4209
4408
|
try {
|
|
4210
|
-
const raw =
|
|
4409
|
+
const raw = fs13.readFileSync(evidencePath, "utf-8");
|
|
4211
4410
|
const parsed = JSON.parse(raw);
|
|
4212
4411
|
const evidenceFeature = (parsed.feature || "").toString().trim();
|
|
4213
4412
|
if (evidenceFeature && evidenceFeature !== feature.folderName) {
|
|
@@ -4223,31 +4422,31 @@ function resolvePrePrReviewEvidencePath(feature) {
|
|
|
4223
4422
|
const candidates = [];
|
|
4224
4423
|
const explicit = (feature.prePrReview.evidence || "").trim();
|
|
4225
4424
|
if (explicit && explicit !== "-") {
|
|
4226
|
-
if (
|
|
4425
|
+
if (path16.isAbsolute(explicit)) {
|
|
4227
4426
|
candidates.push(explicit);
|
|
4228
4427
|
} else {
|
|
4229
|
-
candidates.push(
|
|
4230
|
-
candidates.push(
|
|
4428
|
+
candidates.push(path16.resolve(feature.path, explicit));
|
|
4429
|
+
candidates.push(path16.resolve(docsRoot, explicit));
|
|
4231
4430
|
const normalizedExplicit = explicit.replace(/\\/g, "/");
|
|
4232
4431
|
if (normalizedExplicit.startsWith("docs/")) {
|
|
4233
4432
|
const withoutDocsPrefix = normalizedExplicit.slice("docs/".length);
|
|
4234
4433
|
if (withoutDocsPrefix) {
|
|
4235
|
-
candidates.push(
|
|
4434
|
+
candidates.push(path16.resolve(docsRoot, withoutDocsPrefix));
|
|
4236
4435
|
}
|
|
4237
4436
|
}
|
|
4238
4437
|
}
|
|
4239
4438
|
}
|
|
4240
|
-
candidates.push(
|
|
4241
|
-
candidates.push(
|
|
4439
|
+
candidates.push(path16.join(feature.path, "review-trace.json"));
|
|
4440
|
+
candidates.push(path16.join(docsRoot, "review-trace.json"));
|
|
4242
4441
|
const seen = /* @__PURE__ */ new Set();
|
|
4243
4442
|
for (const candidate of candidates) {
|
|
4244
|
-
const abs =
|
|
4443
|
+
const abs = path16.resolve(candidate);
|
|
4245
4444
|
if (seen.has(abs)) continue;
|
|
4246
4445
|
seen.add(abs);
|
|
4247
|
-
if (!
|
|
4446
|
+
if (!fs13.existsSync(abs)) continue;
|
|
4248
4447
|
if (!abs.toLowerCase().endsWith(".json")) continue;
|
|
4249
4448
|
if (!isLikelyCurrentPrePrReviewEvidence(abs, feature)) continue;
|
|
4250
|
-
const rel =
|
|
4449
|
+
const rel = path16.relative(docsRoot, abs).replace(/\\/g, "/");
|
|
4251
4450
|
if (rel && !rel.startsWith("../")) {
|
|
4252
4451
|
return rel;
|
|
4253
4452
|
}
|
|
@@ -4288,8 +4487,8 @@ function getReviewFixCommitGuidance(feature, lang, options) {
|
|
|
4288
4487
|
}
|
|
4289
4488
|
function resolveManagedWorktreeCleanupPaths(projectGitCwd) {
|
|
4290
4489
|
if (!projectGitCwd) return null;
|
|
4291
|
-
const normalized =
|
|
4292
|
-
const marker = `${
|
|
4490
|
+
const normalized = path16.resolve(projectGitCwd);
|
|
4491
|
+
const marker = `${path16.sep}.worktrees${path16.sep}`;
|
|
4293
4492
|
const markerIndex = normalized.lastIndexOf(marker);
|
|
4294
4493
|
if (markerIndex <= 0) return null;
|
|
4295
4494
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -4336,7 +4535,7 @@ function toTaskKey(rawTitle) {
|
|
|
4336
4535
|
function countDoneTransitionsInLatestTasksCommit(ctx, feature) {
|
|
4337
4536
|
const docsGitCwd = feature.git.docsGitCwd;
|
|
4338
4537
|
const tasksRelativePath = normalizeGitRelativePath(
|
|
4339
|
-
|
|
4538
|
+
path16.join(feature.docs.featurePathFromDocs, "tasks.md")
|
|
4340
4539
|
);
|
|
4341
4540
|
const diff = readGitText(ctx, docsGitCwd, [
|
|
4342
4541
|
"diff",
|
|
@@ -4411,7 +4610,7 @@ function checkTaskCommitGate(ctx, feature) {
|
|
|
4411
4610
|
return { pass: true };
|
|
4412
4611
|
}
|
|
4413
4612
|
const args = ["log", "-n", "1", "--pretty=%s", "--", "."];
|
|
4414
|
-
const relativeDocsDir =
|
|
4613
|
+
const relativeDocsDir = path16.relative(projectGitCwd, feature.git.docsGitCwd);
|
|
4415
4614
|
const normalizedDocsDir = normalizeGitRelativePath(relativeDocsDir);
|
|
4416
4615
|
if (normalizedDocsDir && normalizedDocsDir !== "." && normalizedDocsDir !== ".." && !normalizedDocsDir.startsWith("../")) {
|
|
4417
4616
|
args.push(`:(exclude)${normalizedDocsDir}/**`);
|
|
@@ -4448,10 +4647,11 @@ function getTaskCommitGateReasonText(lang, check) {
|
|
|
4448
4647
|
}
|
|
4449
4648
|
function getStepDefinitions(ctx) {
|
|
4450
4649
|
const lang = ctx.config.lang;
|
|
4451
|
-
const
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4650
|
+
const {
|
|
4651
|
+
workflowPolicy,
|
|
4652
|
+
prePrReviewPolicy,
|
|
4653
|
+
taskCommitGatePolicy
|
|
4654
|
+
} = resolveWorkflowRuntime(ctx.config.workflow);
|
|
4455
4655
|
const isTaskExecuteCurrent = (f) => f.docs.tasksExists && f.tasks.total > 0 && (f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)) && isTasksDocApproved(f) && (!workflowPolicy.requireBranch || f.git.onExpectedBranch || f.tasks.done === f.tasks.total);
|
|
4456
4656
|
const isTaskExecuteWorktreeBlocked = (f) => isTaskExecuteCurrent(f) && workflowPolicy.requireWorktree && f.tasks.done < f.tasks.total && !!f.issueNumber && !f.git.projectInManagedWorktree;
|
|
4457
4657
|
const isTaskExecuteFinalize = (f) => isTaskExecuteCurrent(f) && f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f);
|
|
@@ -6006,17 +6206,17 @@ function isGitPathIgnored(ctx, cwd, relativePath) {
|
|
|
6006
6206
|
}
|
|
6007
6207
|
}
|
|
6008
6208
|
var GIT_WORKTREE_CACHE = /* @__PURE__ */ new Map();
|
|
6009
|
-
var WORKTREE_MARKER = `${
|
|
6209
|
+
var WORKTREE_MARKER = `${path16.sep}.worktrees${path16.sep}`;
|
|
6010
6210
|
function resetContextGitCaches() {
|
|
6011
6211
|
GIT_WORKTREE_CACHE.clear();
|
|
6012
6212
|
}
|
|
6013
6213
|
function isManagedWorktreePath(cwd) {
|
|
6014
6214
|
if (!cwd) return false;
|
|
6015
|
-
const normalized =
|
|
6215
|
+
const normalized = path16.resolve(cwd);
|
|
6016
6216
|
return normalized.includes(WORKTREE_MARKER);
|
|
6017
6217
|
}
|
|
6018
6218
|
function resolveProjectRootFromGitCwd(cwd) {
|
|
6019
|
-
const normalized =
|
|
6219
|
+
const normalized = path16.resolve(cwd);
|
|
6020
6220
|
const markerIndex = normalized.lastIndexOf(WORKTREE_MARKER);
|
|
6021
6221
|
if (markerIndex <= 0) return normalized;
|
|
6022
6222
|
const projectRoot = normalized.slice(0, markerIndex);
|
|
@@ -6035,7 +6235,7 @@ function getGitTopLevel(ctx, cwd) {
|
|
|
6035
6235
|
}
|
|
6036
6236
|
function listGitWorktrees(ctx, cwd) {
|
|
6037
6237
|
const topLevel = getGitTopLevel(ctx, cwd) || cwd;
|
|
6038
|
-
const cacheKey =
|
|
6238
|
+
const cacheKey = path16.resolve(topLevel);
|
|
6039
6239
|
const cached = GIT_WORKTREE_CACHE.get(cacheKey);
|
|
6040
6240
|
if (cached) return cached;
|
|
6041
6241
|
try {
|
|
@@ -6523,17 +6723,17 @@ function resolveLocalEvidencePathCandidates(rawValue, context) {
|
|
|
6523
6723
|
if (!evidencePath) return [];
|
|
6524
6724
|
if (/^https?:\/\//i.test(evidencePath)) return [];
|
|
6525
6725
|
const candidates = /* @__PURE__ */ new Set();
|
|
6526
|
-
if (
|
|
6527
|
-
candidates.add(
|
|
6726
|
+
if (path16.isAbsolute(evidencePath)) {
|
|
6727
|
+
candidates.add(path16.resolve(evidencePath));
|
|
6528
6728
|
} else {
|
|
6529
|
-
candidates.add(
|
|
6530
|
-
candidates.add(
|
|
6531
|
-
candidates.add(
|
|
6729
|
+
candidates.add(path16.resolve(context.featurePath, evidencePath));
|
|
6730
|
+
candidates.add(path16.resolve(context.docsDir, evidencePath));
|
|
6731
|
+
candidates.add(path16.resolve(path16.dirname(context.docsDir), evidencePath));
|
|
6532
6732
|
const normalizedEvidencePath = evidencePath.replace(/\\/g, "/");
|
|
6533
6733
|
if (normalizedEvidencePath.startsWith("docs/")) {
|
|
6534
6734
|
const withoutDocsPrefix = normalizedEvidencePath.slice("docs/".length);
|
|
6535
6735
|
if (withoutDocsPrefix) {
|
|
6536
|
-
candidates.add(
|
|
6736
|
+
candidates.add(path16.resolve(context.docsDir, withoutDocsPrefix));
|
|
6537
6737
|
}
|
|
6538
6738
|
}
|
|
6539
6739
|
}
|
|
@@ -6595,13 +6795,13 @@ function parsePrLink(value) {
|
|
|
6595
6795
|
return trimmed;
|
|
6596
6796
|
}
|
|
6597
6797
|
function normalizeGitPath(value) {
|
|
6598
|
-
return value.split(
|
|
6798
|
+
return value.split(path16.sep).join("/");
|
|
6599
6799
|
}
|
|
6600
6800
|
function resolveProjectStatusPaths(projectGitCwd, docsDir) {
|
|
6601
|
-
const relativeDocsDir =
|
|
6801
|
+
const relativeDocsDir = path16.relative(projectGitCwd, docsDir);
|
|
6602
6802
|
if (!relativeDocsDir) return [];
|
|
6603
|
-
if (
|
|
6604
|
-
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${
|
|
6803
|
+
if (path16.isAbsolute(relativeDocsDir)) return [];
|
|
6804
|
+
if (relativeDocsDir === ".." || relativeDocsDir.startsWith(`..${path16.sep}`)) {
|
|
6605
6805
|
return [];
|
|
6606
6806
|
}
|
|
6607
6807
|
const normalizedDocsDir = normalizeGitPath(relativeDocsDir).replace(
|
|
@@ -6639,7 +6839,7 @@ function getExpectedWorktreeCandidates(projectGitCwd, issueNumber, slug, folderN
|
|
|
6639
6839
|
const seen = /* @__PURE__ */ new Set();
|
|
6640
6840
|
const out = [];
|
|
6641
6841
|
for (const name of names) {
|
|
6642
|
-
const candidate =
|
|
6842
|
+
const candidate = path16.resolve(projectRoot, ".worktrees", name);
|
|
6643
6843
|
if (seen.has(candidate)) continue;
|
|
6644
6844
|
seen.add(candidate);
|
|
6645
6845
|
out.push(candidate);
|
|
@@ -6653,7 +6853,7 @@ function resolveExistingExpectedWorktreePath(projectGitCwd, issueNumber, slug, f
|
|
|
6653
6853
|
slug,
|
|
6654
6854
|
folderName
|
|
6655
6855
|
)) {
|
|
6656
|
-
if (!
|
|
6856
|
+
if (!fs13.existsSync(candidate)) continue;
|
|
6657
6857
|
return candidate;
|
|
6658
6858
|
}
|
|
6659
6859
|
return void 0;
|
|
@@ -6683,7 +6883,7 @@ function resolveFeatureWorktreePath(ctx, projectGitCwd, issueNumber, slug, folde
|
|
|
6683
6883
|
slug,
|
|
6684
6884
|
folderName
|
|
6685
6885
|
)) {
|
|
6686
|
-
if (!
|
|
6886
|
+
if (!fs13.existsSync(candidate)) continue;
|
|
6687
6887
|
const branchName = getCurrentBranch(ctx, candidate);
|
|
6688
6888
|
if (!expectedBranchesSet.has(branchName)) continue;
|
|
6689
6889
|
return {
|
|
@@ -6824,10 +7024,10 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6824
7024
|
const normalizedCandidates = uniqueNormalizedPaths(
|
|
6825
7025
|
candidates.map((candidate) => {
|
|
6826
7026
|
if (!candidate) return "";
|
|
6827
|
-
if (!
|
|
6828
|
-
const relative =
|
|
7027
|
+
if (!path16.isAbsolute(candidate)) return candidate;
|
|
7028
|
+
const relative = path16.relative(projectGitCwd, candidate);
|
|
6829
7029
|
if (!relative) return "";
|
|
6830
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
7030
|
+
if (relative === ".." || relative.startsWith(`..${path16.sep}`))
|
|
6831
7031
|
return "";
|
|
6832
7032
|
return relative;
|
|
6833
7033
|
}).filter(Boolean)
|
|
@@ -6841,7 +7041,7 @@ async function resolveComponentStatusPaths(ctx, projectGitCwd, component, workfl
|
|
|
6841
7041
|
if (cached) return [...cached];
|
|
6842
7042
|
const existing = [];
|
|
6843
7043
|
for (const candidate of normalizedCandidates) {
|
|
6844
|
-
if (await ctx.fs.pathExists(
|
|
7044
|
+
if (await ctx.fs.pathExists(path16.join(projectGitCwd, candidate))) {
|
|
6845
7045
|
existing.push(candidate);
|
|
6846
7046
|
}
|
|
6847
7047
|
}
|
|
@@ -6927,18 +7127,19 @@ function isPrePrReviewSatisfied2(feature, policy) {
|
|
|
6927
7127
|
}
|
|
6928
7128
|
async function parseFeature(ctx, featurePath, type, context, options) {
|
|
6929
7129
|
const lang = options.lang;
|
|
6930
|
-
const workflowPolicy =
|
|
6931
|
-
|
|
6932
|
-
|
|
7130
|
+
const { workflowPolicy, prePrReviewPolicy } = resolveWorkflowRuntime(
|
|
7131
|
+
options.workflow
|
|
7132
|
+
);
|
|
7133
|
+
const folderName = path16.basename(featurePath);
|
|
6933
7134
|
const match = folderName.match(/^(F\d+)-(.+)$/);
|
|
6934
7135
|
const id = match?.[1];
|
|
6935
7136
|
const slug = match?.[2] || folderName;
|
|
6936
|
-
const specPath =
|
|
6937
|
-
const planPath =
|
|
6938
|
-
const tasksPath =
|
|
6939
|
-
const decisionsPath =
|
|
6940
|
-
const issueDocPath =
|
|
6941
|
-
const prDocPath =
|
|
7137
|
+
const specPath = path16.join(featurePath, "spec.md");
|
|
7138
|
+
const planPath = path16.join(featurePath, "plan.md");
|
|
7139
|
+
const tasksPath = path16.join(featurePath, "tasks.md");
|
|
7140
|
+
const decisionsPath = path16.join(featurePath, "decisions.md");
|
|
7141
|
+
const issueDocPath = path16.join(featurePath, "issue.md");
|
|
7142
|
+
const prDocPath = path16.join(featurePath, "pr.md");
|
|
6942
7143
|
let specStatus;
|
|
6943
7144
|
let issueNumber;
|
|
6944
7145
|
const specExists = await ctx.fs.pathExists(specPath);
|
|
@@ -7200,7 +7401,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7200
7401
|
} else if (workflowPolicy.requireWorktree && tasksSummary.total > tasksSummary.done && !projectInManagedWorktree) {
|
|
7201
7402
|
warnings.push(tr(lang, "warnings", "workflowWorktreeRequired"));
|
|
7202
7403
|
}
|
|
7203
|
-
const relativeFeaturePathFromDocs =
|
|
7404
|
+
const relativeFeaturePathFromDocs = path16.relative(
|
|
7204
7405
|
context.docsDir,
|
|
7205
7406
|
featurePath
|
|
7206
7407
|
);
|
|
@@ -7566,7 +7767,7 @@ async function parseFeature(ctx, featurePath, type, context, options) {
|
|
|
7566
7767
|
async function listFeatureDirs(ctx, rootDir) {
|
|
7567
7768
|
const dirs = await listSubdirectories(ctx.fs, rootDir);
|
|
7568
7769
|
return dirs.filter(
|
|
7569
|
-
(value) =>
|
|
7770
|
+
(value) => path16.basename(value).trim().toLowerCase() !== "feature-base"
|
|
7570
7771
|
);
|
|
7571
7772
|
}
|
|
7572
7773
|
function normalizeRelPath(value) {
|
|
@@ -7707,7 +7908,7 @@ async function scanFeatures(ctx) {
|
|
|
7707
7908
|
if (config.projectType === "single") {
|
|
7708
7909
|
const featureDirs = await listFeatureDirs(
|
|
7709
7910
|
ctx,
|
|
7710
|
-
|
|
7911
|
+
path16.join(config.docsDir, "features")
|
|
7711
7912
|
);
|
|
7712
7913
|
componentFeatureDirs.set("single", featureDirs);
|
|
7713
7914
|
allFeatureDirs.push(...featureDirs);
|
|
@@ -7719,14 +7920,14 @@ async function scanFeatures(ctx) {
|
|
|
7719
7920
|
for (const component of components) {
|
|
7720
7921
|
const componentDirs = await listFeatureDirs(
|
|
7721
7922
|
ctx,
|
|
7722
|
-
|
|
7923
|
+
path16.join(config.docsDir, "features", component)
|
|
7723
7924
|
);
|
|
7724
7925
|
componentFeatureDirs.set(component, componentDirs);
|
|
7725
7926
|
allFeatureDirs.push(...componentDirs);
|
|
7726
7927
|
}
|
|
7727
7928
|
}
|
|
7728
7929
|
const relativeFeaturePaths = allFeatureDirs.map(
|
|
7729
|
-
(dir) => normalizeRelPath(
|
|
7930
|
+
(dir) => normalizeRelPath(path16.relative(config.docsDir, dir))
|
|
7730
7931
|
);
|
|
7731
7932
|
const docsGitMeta = buildDocsFeatureGitMeta(
|
|
7732
7933
|
ctx,
|
|
@@ -7743,7 +7944,7 @@ async function scanFeatures(ctx) {
|
|
|
7743
7944
|
const parsed = await Promise.all(
|
|
7744
7945
|
target.dirs.map(async (dir) => {
|
|
7745
7946
|
const relativeFeaturePathFromDocs = normalizeRelPath(
|
|
7746
|
-
|
|
7947
|
+
path16.relative(config.docsDir, dir)
|
|
7747
7948
|
);
|
|
7748
7949
|
const docsMeta = docsGitMeta.get(relativeFeaturePathFromDocs);
|
|
7749
7950
|
return parseFeature(
|
|
@@ -7813,13 +8014,13 @@ async function runStatus(options) {
|
|
|
7813
8014
|
);
|
|
7814
8015
|
}
|
|
7815
8016
|
const { docsDir, projectType, projectName, lang } = ctx.config;
|
|
7816
|
-
const featuresDir =
|
|
8017
|
+
const featuresDir = path16.join(docsDir, "features");
|
|
7817
8018
|
const scan = await scanFeatures(ctx);
|
|
7818
8019
|
const features = [];
|
|
7819
8020
|
const idMap = /* @__PURE__ */ new Map();
|
|
7820
8021
|
for (const f of scan.features) {
|
|
7821
8022
|
const id = f.id || "UNKNOWN";
|
|
7822
|
-
const relPath =
|
|
8023
|
+
const relPath = path16.relative(docsDir, f.path);
|
|
7823
8024
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
7824
8025
|
idMap.get(id).push(relPath);
|
|
7825
8026
|
if (!f.docs.specExists || !f.docs.tasksExists) continue;
|
|
@@ -7910,7 +8111,7 @@ async function runStatus(options) {
|
|
|
7910
8111
|
}
|
|
7911
8112
|
console.log();
|
|
7912
8113
|
if (options.write) {
|
|
7913
|
-
const outputPath =
|
|
8114
|
+
const outputPath = path16.join(featuresDir, "status.md");
|
|
7914
8115
|
const date = getLocalDateString();
|
|
7915
8116
|
const content = [
|
|
7916
8117
|
"# Feature Status",
|
|
@@ -7936,7 +8137,7 @@ function escapeRegExp4(value) {
|
|
|
7936
8137
|
}
|
|
7937
8138
|
async function getFeatureNameFromSpec(fsAdapter, featureDir, fallbackSlug, fallbackFolderName) {
|
|
7938
8139
|
try {
|
|
7939
|
-
const specPath =
|
|
8140
|
+
const specPath = path16.join(featureDir, "spec.md");
|
|
7940
8141
|
if (!await fsAdapter.pathExists(specPath)) return fallbackSlug;
|
|
7941
8142
|
const content = await fsAdapter.readFile(specPath, "utf-8");
|
|
7942
8143
|
const keys = ["\uAE30\uB2A5\uBA85", "Feature Name"];
|
|
@@ -8024,8 +8225,8 @@ async function runUpdate(options) {
|
|
|
8024
8225
|
let updatedCount = 0;
|
|
8025
8226
|
if (updateAgents) {
|
|
8026
8227
|
console.log(chalk9.blue(tr(lang, "cli", "update.updatingAgents")));
|
|
8027
|
-
const commonAgentsBase =
|
|
8028
|
-
const targetAgentsBase =
|
|
8228
|
+
const commonAgentsBase = path16.join(templatesDir, lang, "common", "agents");
|
|
8229
|
+
const targetAgentsBase = path16.join(docsDir, "agents");
|
|
8029
8230
|
const commonAgents = commonAgentsBase;
|
|
8030
8231
|
const targetAgents = targetAgentsBase;
|
|
8031
8232
|
const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
|
|
@@ -8118,21 +8319,21 @@ async function collectAgentsMdTargets(cwd, config) {
|
|
|
8118
8319
|
const targets = /* @__PURE__ */ new Set();
|
|
8119
8320
|
const docsRepo = config.docsRepo ?? "embedded";
|
|
8120
8321
|
if (docsRepo === "embedded") {
|
|
8121
|
-
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) ||
|
|
8122
|
-
targets.add(
|
|
8322
|
+
const repoRoot = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || path16.resolve(config.docsDir, "..");
|
|
8323
|
+
targets.add(path16.join(repoRoot, "AGENTS.md"));
|
|
8123
8324
|
return [...targets];
|
|
8124
8325
|
}
|
|
8125
|
-
targets.add(
|
|
8326
|
+
targets.add(path16.join(config.docsDir, "AGENTS.md"));
|
|
8126
8327
|
const baseDir = getGitTopLevelOrNull2(cwd) || getGitTopLevelOrNull2(config.docsDir) || process.cwd();
|
|
8127
8328
|
const rawRoots = typeof config.projectRoot === "string" ? [config.projectRoot] : config.projectRoot && typeof config.projectRoot === "object" ? Object.values(config.projectRoot) : [];
|
|
8128
8329
|
for (const rawRoot of rawRoots) {
|
|
8129
8330
|
const value = String(rawRoot || "").trim();
|
|
8130
8331
|
if (!value) continue;
|
|
8131
|
-
const resolved =
|
|
8332
|
+
const resolved = path16.resolve(baseDir, value);
|
|
8132
8333
|
if (!await fs.pathExists(resolved)) continue;
|
|
8133
8334
|
const stat = await fs.stat(resolved);
|
|
8134
8335
|
if (!stat.isDirectory()) continue;
|
|
8135
|
-
targets.add(
|
|
8336
|
+
targets.add(path16.join(resolved, "AGENTS.md"));
|
|
8136
8337
|
}
|
|
8137
8338
|
return [...targets];
|
|
8138
8339
|
}
|
|
@@ -8172,7 +8373,7 @@ function normalizeDecisionEnumList2(raw) {
|
|
|
8172
8373
|
return [...deduped];
|
|
8173
8374
|
}
|
|
8174
8375
|
async function backfillMissingConfigDefaults(docsDir) {
|
|
8175
|
-
const configPath =
|
|
8376
|
+
const configPath = path16.join(docsDir, ".lee-spec-kit.json");
|
|
8176
8377
|
if (!await fs.pathExists(configPath)) {
|
|
8177
8378
|
return { changed: false, changedPaths: [] };
|
|
8178
8379
|
}
|
|
@@ -8191,6 +8392,8 @@ async function backfillMissingConfigDefaults(docsDir) {
|
|
|
8191
8392
|
changedPaths.push("workflow");
|
|
8192
8393
|
}
|
|
8193
8394
|
const workflow = raw.workflow;
|
|
8395
|
+
const inferredPreset = workflow.mode === "local" ? "local" : "github";
|
|
8396
|
+
setIfMissing(workflow, "preset", inferredPreset, "workflow.preset");
|
|
8194
8397
|
setIfMissing(workflow, "mode", "github", "workflow.mode");
|
|
8195
8398
|
setIfMissing(workflow, "requireWorktree", false, "workflow.requireWorktree");
|
|
8196
8399
|
setIfMissing(workflow, "codeDirtyScope", "auto", "workflow.codeDirtyScope");
|
|
@@ -8299,8 +8502,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
8299
8502
|
const files = await fs.readdir(sourceDir);
|
|
8300
8503
|
let updatedCount = 0;
|
|
8301
8504
|
for (const file of files) {
|
|
8302
|
-
const sourcePath =
|
|
8303
|
-
const targetPath =
|
|
8505
|
+
const sourcePath = path16.join(sourceDir, file);
|
|
8506
|
+
const targetPath = path16.join(targetDir, file);
|
|
8304
8507
|
const stat = await fs.stat(sourcePath);
|
|
8305
8508
|
if (stat.isFile()) {
|
|
8306
8509
|
if (protectedFiles.has(file)) {
|
|
@@ -8383,7 +8586,7 @@ function extractPorcelainPaths(line) {
|
|
|
8383
8586
|
function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
8384
8587
|
const top = getGitTopLevel2(docsDir);
|
|
8385
8588
|
if (!top) return null;
|
|
8386
|
-
const rel =
|
|
8589
|
+
const rel = path16.relative(top, docsDir) || ".";
|
|
8387
8590
|
try {
|
|
8388
8591
|
const output = execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
8389
8592
|
cwd: top,
|
|
@@ -8395,7 +8598,7 @@ function getDocsPorcelainStatus(docsDir, ignoredAbsPaths = []) {
|
|
|
8395
8598
|
}
|
|
8396
8599
|
const ignoredRelPaths = new Set(
|
|
8397
8600
|
ignoredAbsPaths.map(
|
|
8398
|
-
(absPath) => normalizeGitPath2(
|
|
8601
|
+
(absPath) => normalizeGitPath2(path16.relative(top, absPath) || ".")
|
|
8399
8602
|
)
|
|
8400
8603
|
);
|
|
8401
8604
|
const filtered = output.split("\n").filter((line) => {
|
|
@@ -8453,7 +8656,7 @@ ${tr(lang2, "cli", "common.canceled")}`));
|
|
|
8453
8656
|
}
|
|
8454
8657
|
async function runConfig(options) {
|
|
8455
8658
|
const cwd = process.cwd();
|
|
8456
|
-
const targetCwd = options.dir ?
|
|
8659
|
+
const targetCwd = options.dir ? path16.resolve(cwd, options.dir) : cwd;
|
|
8457
8660
|
const config = await getConfig(targetCwd);
|
|
8458
8661
|
if (!config) {
|
|
8459
8662
|
throw createCliError(
|
|
@@ -8461,7 +8664,7 @@ async function runConfig(options) {
|
|
|
8461
8664
|
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
8462
8665
|
);
|
|
8463
8666
|
}
|
|
8464
|
-
const configPath =
|
|
8667
|
+
const configPath = path16.join(config.docsDir, ".lee-spec-kit.json");
|
|
8465
8668
|
if (!options.projectRoot) {
|
|
8466
8669
|
console.log();
|
|
8467
8670
|
console.log(chalk9.blue(tr(config.lang, "cli", "config.currentTitle")));
|
|
@@ -8567,47 +8770,47 @@ var BUILTIN_DOC_DEFINITIONS = [
|
|
|
8567
8770
|
{
|
|
8568
8771
|
id: "agents",
|
|
8569
8772
|
title: { ko: "\uC5D0\uC774\uC804\uD2B8 \uC6B4\uC601 \uADDC\uCE59", en: "Agent Operating Rules" },
|
|
8570
|
-
relativePath: (_, lang) =>
|
|
8773
|
+
relativePath: (_, lang) => path16.join(lang, "common", "agents", "agents.md")
|
|
8571
8774
|
},
|
|
8572
8775
|
{
|
|
8573
8776
|
id: "git-workflow",
|
|
8574
8777
|
title: { ko: "Git \uC6CC\uD06C\uD50C\uB85C\uC6B0", en: "Git Workflow" },
|
|
8575
|
-
relativePath: (_, lang) =>
|
|
8778
|
+
relativePath: (_, lang) => path16.join(lang, "common", "agents", "git-workflow.md")
|
|
8576
8779
|
},
|
|
8577
8780
|
{
|
|
8578
8781
|
id: "issue-doc",
|
|
8579
8782
|
title: { ko: "Issue \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "Issue Document Template" },
|
|
8580
|
-
relativePath: (_, lang) =>
|
|
8783
|
+
relativePath: (_, lang) => path16.join(lang, "common", "features", "feature-base", "issue.md")
|
|
8581
8784
|
},
|
|
8582
8785
|
{
|
|
8583
8786
|
id: "pr-doc",
|
|
8584
8787
|
title: { ko: "PR \uBB38\uC11C \uD15C\uD50C\uB9BF", en: "PR Document Template" },
|
|
8585
|
-
relativePath: (_, lang) =>
|
|
8788
|
+
relativePath: (_, lang) => path16.join(lang, "common", "features", "feature-base", "pr.md")
|
|
8586
8789
|
},
|
|
8587
8790
|
{
|
|
8588
8791
|
id: "create-feature",
|
|
8589
8792
|
title: { ko: "create-feature \uC2A4\uD0AC", en: "create-feature skill" },
|
|
8590
|
-
relativePath: (_, lang) =>
|
|
8793
|
+
relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "create-feature.md")
|
|
8591
8794
|
},
|
|
8592
8795
|
{
|
|
8593
8796
|
id: "execute-task",
|
|
8594
8797
|
title: { ko: "execute-task \uC2A4\uD0AC", en: "execute-task skill" },
|
|
8595
|
-
relativePath: (_, lang) =>
|
|
8798
|
+
relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "execute-task.md")
|
|
8596
8799
|
},
|
|
8597
8800
|
{
|
|
8598
8801
|
id: "create-issue",
|
|
8599
8802
|
title: { ko: "create-issue \uC2A4\uD0AC", en: "create-issue skill" },
|
|
8600
|
-
relativePath: (_, lang) =>
|
|
8803
|
+
relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "create-issue.md")
|
|
8601
8804
|
},
|
|
8602
8805
|
{
|
|
8603
8806
|
id: "create-pr",
|
|
8604
8807
|
title: { ko: "create-pr \uC2A4\uD0AC", en: "create-pr skill" },
|
|
8605
|
-
relativePath: (_, lang) =>
|
|
8808
|
+
relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "create-pr.md")
|
|
8606
8809
|
},
|
|
8607
8810
|
{
|
|
8608
8811
|
id: "split-feature",
|
|
8609
8812
|
title: { ko: "feature \uBD84\uD560 \uAC00\uC774\uB4DC", en: "feature split guide" },
|
|
8610
|
-
relativePath: (_, lang) =>
|
|
8813
|
+
relativePath: (_, lang) => path16.join(lang, "common", "agents", "skills", "split-feature.md")
|
|
8611
8814
|
}
|
|
8612
8815
|
];
|
|
8613
8816
|
var DOC_FOLLOWUPS = {
|
|
@@ -8705,7 +8908,7 @@ function listBuiltinDocs(projectType, lang) {
|
|
|
8705
8908
|
id: doc.id,
|
|
8706
8909
|
title: doc.title[lang],
|
|
8707
8910
|
relativePath,
|
|
8708
|
-
absolutePath:
|
|
8911
|
+
absolutePath: path16.join(templatesDir, relativePath)
|
|
8709
8912
|
};
|
|
8710
8913
|
});
|
|
8711
8914
|
}
|
|
@@ -8759,7 +8962,7 @@ function toAllowedSet(values, extras) {
|
|
|
8759
8962
|
);
|
|
8760
8963
|
}
|
|
8761
8964
|
function isDocLikeFile(name) {
|
|
8762
|
-
return DOC_LIKE_FILE_EXTENSIONS.has(
|
|
8965
|
+
return DOC_LIKE_FILE_EXTENSIONS.has(path16.extname(name).toLowerCase());
|
|
8763
8966
|
}
|
|
8764
8967
|
async function collectUnmanagedDocsEntries(docsDir, allowed) {
|
|
8765
8968
|
const allowedDirs = toAllowedSet(DEFAULT_MANAGED_DOC_DIRS, allowed?.dirs);
|
|
@@ -8775,7 +8978,7 @@ async function collectUnmanagedDocsEntries(docsDir, allowed) {
|
|
|
8775
8978
|
unmanaged.push({
|
|
8776
8979
|
name,
|
|
8777
8980
|
kind: "dir",
|
|
8778
|
-
absPath:
|
|
8981
|
+
absPath: path16.join(docsDir, name),
|
|
8779
8982
|
relPath: `docs/${name}`
|
|
8780
8983
|
});
|
|
8781
8984
|
continue;
|
|
@@ -8786,7 +8989,7 @@ async function collectUnmanagedDocsEntries(docsDir, allowed) {
|
|
|
8786
8989
|
unmanaged.push({
|
|
8787
8990
|
name,
|
|
8788
8991
|
kind: "file",
|
|
8789
|
-
absPath:
|
|
8992
|
+
absPath: path16.join(docsDir, name),
|
|
8790
8993
|
relPath: `docs/${name}`
|
|
8791
8994
|
});
|
|
8792
8995
|
}
|
|
@@ -9843,7 +10046,7 @@ async function loadApprovalTicketStore(storePath) {
|
|
|
9843
10046
|
}
|
|
9844
10047
|
}
|
|
9845
10048
|
async function saveApprovalTicketStore(storePath, payload) {
|
|
9846
|
-
await fs.ensureDir(
|
|
10049
|
+
await fs.ensureDir(path16.dirname(storePath));
|
|
9847
10050
|
await fs.writeJson(storePath, payload, { spaces: 2 });
|
|
9848
10051
|
}
|
|
9849
10052
|
function pruneApprovalTickets(tickets, nowMs) {
|
|
@@ -10387,9 +10590,11 @@ async function runContext(featureName, options) {
|
|
|
10387
10590
|
const cwd = process.cwd();
|
|
10388
10591
|
const config = await getConfig(cwd);
|
|
10389
10592
|
const lang = config?.lang ?? "en";
|
|
10390
|
-
const
|
|
10391
|
-
|
|
10392
|
-
|
|
10593
|
+
const {
|
|
10594
|
+
workflowPolicy,
|
|
10595
|
+
prePrReviewPolicy,
|
|
10596
|
+
taskCommitGatePolicy
|
|
10597
|
+
} = resolveWorkflowRuntime(config?.workflow);
|
|
10393
10598
|
if (!config) {
|
|
10394
10599
|
throw createCliError(
|
|
10395
10600
|
"CONFIG_NOT_FOUND",
|
|
@@ -10907,7 +11112,7 @@ async function runContext(featureName, options) {
|
|
|
10907
11112
|
if (f.issueNumber) {
|
|
10908
11113
|
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
10909
11114
|
}
|
|
10910
|
-
console.log(` \u2022 Path: ${
|
|
11115
|
+
console.log(` \u2022 Path: ${path16.relative(cwd, f.path)}`);
|
|
10911
11116
|
if (f.git.projectBranch) {
|
|
10912
11117
|
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
10913
11118
|
}
|
|
@@ -11059,7 +11264,7 @@ function extractTitleAfterId(line, id) {
|
|
|
11059
11264
|
return cleaned ? cleaned : void 0;
|
|
11060
11265
|
}
|
|
11061
11266
|
async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
11062
|
-
const prdDir =
|
|
11267
|
+
const prdDir = path16.join(docsDir, "prd");
|
|
11063
11268
|
const files = await walkFiles(fsAdapter, prdDir, {
|
|
11064
11269
|
extensions: [".md"],
|
|
11065
11270
|
ignoreDirs: [".git", "node_modules", "dist", "tmp"]
|
|
@@ -11067,7 +11272,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
11067
11272
|
const definitions = /* @__PURE__ */ new Map();
|
|
11068
11273
|
const duplicates = [];
|
|
11069
11274
|
for (const filePath of files) {
|
|
11070
|
-
if (
|
|
11275
|
+
if (path16.basename(filePath).toLowerCase() === "readme.md") {
|
|
11071
11276
|
continue;
|
|
11072
11277
|
}
|
|
11073
11278
|
let content = "";
|
|
@@ -11076,7 +11281,7 @@ async function scanPrdRequirements(fsAdapter, docsDir) {
|
|
|
11076
11281
|
} catch {
|
|
11077
11282
|
continue;
|
|
11078
11283
|
}
|
|
11079
|
-
const relFile = normalizeRelPath2(
|
|
11284
|
+
const relFile = normalizeRelPath2(path16.relative(docsDir, filePath));
|
|
11080
11285
|
const lines = content.split(/\r?\n/);
|
|
11081
11286
|
let inCodeBlock = false;
|
|
11082
11287
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -11151,7 +11356,7 @@ var FIXABLE_ISSUE_CODES = /* @__PURE__ */ new Set([
|
|
|
11151
11356
|
]);
|
|
11152
11357
|
function formatPath(cwd, p) {
|
|
11153
11358
|
if (!p) return "";
|
|
11154
|
-
return
|
|
11359
|
+
return path16.isAbsolute(p) ? path16.relative(cwd, p) : p;
|
|
11155
11360
|
}
|
|
11156
11361
|
function detectPlaceholders(content) {
|
|
11157
11362
|
const patterns = [
|
|
@@ -11310,7 +11515,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
11310
11515
|
const placeholderContext = {
|
|
11311
11516
|
projectName: config.projectName,
|
|
11312
11517
|
featureName: f.slug,
|
|
11313
|
-
featurePath: f.docs.featurePathFromDocs ||
|
|
11518
|
+
featurePath: f.docs.featurePathFromDocs || path16.relative(config.docsDir, f.path),
|
|
11314
11519
|
repoType: f.type,
|
|
11315
11520
|
featureNumber
|
|
11316
11521
|
};
|
|
@@ -11320,7 +11525,7 @@ async function applyDoctorFixes(config, cwd, features, dryRun) {
|
|
|
11320
11525
|
"tasks.md"
|
|
11321
11526
|
];
|
|
11322
11527
|
for (const file of files) {
|
|
11323
|
-
const fullPath =
|
|
11528
|
+
const fullPath = path16.join(f.path, file);
|
|
11324
11529
|
if (!await fs.pathExists(fullPath)) continue;
|
|
11325
11530
|
const original = await fs.readFile(fullPath, "utf-8");
|
|
11326
11531
|
let next = original;
|
|
@@ -11373,7 +11578,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
11373
11578
|
const issues = [];
|
|
11374
11579
|
const requiredDirs = ["agents", "features", "prd", "designs", "ideas"];
|
|
11375
11580
|
for (const dir of requiredDirs) {
|
|
11376
|
-
const p =
|
|
11581
|
+
const p = path16.join(config.docsDir, dir);
|
|
11377
11582
|
if (!await fs.pathExists(p)) {
|
|
11378
11583
|
issues.push({
|
|
11379
11584
|
level: "error",
|
|
@@ -11385,7 +11590,7 @@ async function checkDocsStructure(config, cwd) {
|
|
|
11385
11590
|
});
|
|
11386
11591
|
}
|
|
11387
11592
|
}
|
|
11388
|
-
const configPath =
|
|
11593
|
+
const configPath = path16.join(config.docsDir, ".lee-spec-kit.json");
|
|
11389
11594
|
if (!await fs.pathExists(configPath)) {
|
|
11390
11595
|
issues.push({
|
|
11391
11596
|
level: "warn",
|
|
@@ -11427,7 +11632,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11427
11632
|
}
|
|
11428
11633
|
const idMap = /* @__PURE__ */ new Map();
|
|
11429
11634
|
for (const f of features) {
|
|
11430
|
-
const rel = f.docs.featurePathFromDocs ||
|
|
11635
|
+
const rel = f.docs.featurePathFromDocs || path16.relative(config.docsDir, f.path);
|
|
11431
11636
|
const id = f.id || "UNKNOWN";
|
|
11432
11637
|
if (!idMap.has(id)) idMap.set(id, []);
|
|
11433
11638
|
idMap.get(id).push(rel);
|
|
@@ -11435,7 +11640,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11435
11640
|
if (!isInitialTemplateState) {
|
|
11436
11641
|
const featureDocs = ["spec.md", "plan.md", "tasks.md"];
|
|
11437
11642
|
for (const file of featureDocs) {
|
|
11438
|
-
const p =
|
|
11643
|
+
const p = path16.join(f.path, file);
|
|
11439
11644
|
if (!await fs.pathExists(p)) continue;
|
|
11440
11645
|
const content = await fs.readFile(p, "utf-8");
|
|
11441
11646
|
const placeholders = detectPlaceholders(content);
|
|
@@ -11450,7 +11655,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11450
11655
|
});
|
|
11451
11656
|
}
|
|
11452
11657
|
if (decisionsPlaceholderMode !== "off") {
|
|
11453
|
-
const decisionsPath =
|
|
11658
|
+
const decisionsPath = path16.join(f.path, "decisions.md");
|
|
11454
11659
|
if (await fs.pathExists(decisionsPath)) {
|
|
11455
11660
|
const content = await fs.readFile(decisionsPath, "utf-8");
|
|
11456
11661
|
const placeholders = detectPlaceholders(content);
|
|
@@ -11479,7 +11684,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11479
11684
|
level: "warn",
|
|
11480
11685
|
code: "spec_status_unset",
|
|
11481
11686
|
message: tr(config.lang, "cli", "doctor.issue.specStatusUnset"),
|
|
11482
|
-
path: formatPath(cwd,
|
|
11687
|
+
path: formatPath(cwd, path16.join(f.path, "spec.md"))
|
|
11483
11688
|
});
|
|
11484
11689
|
}
|
|
11485
11690
|
if (f.docs.planExists && !f.planStatus && !isInitialTemplateState) {
|
|
@@ -11487,7 +11692,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11487
11692
|
level: "warn",
|
|
11488
11693
|
code: "plan_status_unset",
|
|
11489
11694
|
message: tr(config.lang, "cli", "doctor.issue.planStatusUnset"),
|
|
11490
|
-
path: formatPath(cwd,
|
|
11695
|
+
path: formatPath(cwd, path16.join(f.path, "plan.md"))
|
|
11491
11696
|
});
|
|
11492
11697
|
}
|
|
11493
11698
|
if (f.docs.tasksExists && f.tasks.total === 0 && !isInitialTemplateState) {
|
|
@@ -11495,11 +11700,11 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11495
11700
|
level: "warn",
|
|
11496
11701
|
code: "tasks_empty",
|
|
11497
11702
|
message: tr(config.lang, "cli", "doctor.issue.tasksEmpty"),
|
|
11498
|
-
path: formatPath(cwd,
|
|
11703
|
+
path: formatPath(cwd, path16.join(f.path, "tasks.md"))
|
|
11499
11704
|
});
|
|
11500
11705
|
}
|
|
11501
11706
|
if (f.docs.tasksExists) {
|
|
11502
|
-
const tasksPath =
|
|
11707
|
+
const tasksPath = path16.join(f.path, "tasks.md");
|
|
11503
11708
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
11504
11709
|
const unknownPrdTags = [...new Set(
|
|
11505
11710
|
parseTaskLines(tasksContent).flatMap((task) => task.tags).filter((tag) => isPrdRequirementId(tag)).map((tag) => tag.trim().toUpperCase()).filter((tag) => !prdDefinitions.has(tag))
|
|
@@ -11521,7 +11726,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11521
11726
|
level: "warn",
|
|
11522
11727
|
code: "tasks_doc_status_missing",
|
|
11523
11728
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusMissing"),
|
|
11524
|
-
path: formatPath(cwd,
|
|
11729
|
+
path: formatPath(cwd, path16.join(f.path, "tasks.md"))
|
|
11525
11730
|
});
|
|
11526
11731
|
}
|
|
11527
11732
|
if (f.docs.tasksExists && f.docs.tasksDocStatusFieldExists && !f.tasksDocStatus && !isInitialTemplateState) {
|
|
@@ -11529,7 +11734,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11529
11734
|
level: "warn",
|
|
11530
11735
|
code: "tasks_doc_status_unset",
|
|
11531
11736
|
message: tr(config.lang, "cli", "doctor.issue.tasksDocStatusUnset"),
|
|
11532
|
-
path: formatPath(cwd,
|
|
11737
|
+
path: formatPath(cwd, path16.join(f.path, "tasks.md"))
|
|
11533
11738
|
});
|
|
11534
11739
|
}
|
|
11535
11740
|
}
|
|
@@ -11553,7 +11758,7 @@ async function checkFeatures(config, cwd, features, decisionsPlaceholderMode) {
|
|
|
11553
11758
|
level: "warn",
|
|
11554
11759
|
code: "missing_feature_id",
|
|
11555
11760
|
message: tr(config.lang, "cli", "doctor.issue.missingFeatureId"),
|
|
11556
|
-
path: formatPath(cwd,
|
|
11761
|
+
path: formatPath(cwd, path16.join(config.docsDir, p))
|
|
11557
11762
|
});
|
|
11558
11763
|
}
|
|
11559
11764
|
return issues;
|
|
@@ -11700,7 +11905,7 @@ function doctorCommand(program2) {
|
|
|
11700
11905
|
}
|
|
11701
11906
|
console.log();
|
|
11702
11907
|
console.log(chalk9.bold(tr(lang, "cli", "doctor.title")));
|
|
11703
|
-
console.log(chalk9.gray(`- Docs: ${
|
|
11908
|
+
console.log(chalk9.gray(`- Docs: ${path16.relative(cwd, docsDir)}`));
|
|
11704
11909
|
console.log(chalk9.gray(`- Type: ${projectType}`));
|
|
11705
11910
|
console.log(chalk9.gray(`- Lang: ${lang}`));
|
|
11706
11911
|
console.log();
|
|
@@ -11881,7 +12086,7 @@ async function runView(featureName, options) {
|
|
|
11881
12086
|
}
|
|
11882
12087
|
console.log();
|
|
11883
12088
|
console.log(chalk9.bold("\u{1F4CA} Workflow View"));
|
|
11884
|
-
console.log(chalk9.gray(`- Docs: ${
|
|
12089
|
+
console.log(chalk9.gray(`- Docs: ${path16.relative(cwd, config.docsDir)}`));
|
|
11885
12090
|
console.log(
|
|
11886
12091
|
chalk9.gray(
|
|
11887
12092
|
`- Features: ${state.features.length} (open ${state.openFeatures.length} / done ${state.doneFeatures.length})`
|
|
@@ -11966,13 +12171,13 @@ function normalizeRunId(raw) {
|
|
|
11966
12171
|
return value;
|
|
11967
12172
|
}
|
|
11968
12173
|
function getFlowRunBaseDir(cwd) {
|
|
11969
|
-
return
|
|
12174
|
+
return path16.join(getRuntimeStateDir(cwd), "flow-runs");
|
|
11970
12175
|
}
|
|
11971
12176
|
function getFlowRunPath(cwd, runId) {
|
|
11972
|
-
return
|
|
12177
|
+
return path16.join(getFlowRunBaseDir(cwd), `${runId}.json`);
|
|
11973
12178
|
}
|
|
11974
12179
|
function getFlowRunLockPath(cwd, runId) {
|
|
11975
|
-
return
|
|
12180
|
+
return path16.join(getRuntimeStateDir(cwd), "locks", `flow-run-${runId}.lock`);
|
|
11976
12181
|
}
|
|
11977
12182
|
async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
11978
12183
|
const normalized = normalizeRunId(runId);
|
|
@@ -11998,7 +12203,7 @@ async function readFlowRunRecordUnsafe(cwd, runId) {
|
|
|
11998
12203
|
}
|
|
11999
12204
|
async function writeFlowRunRecord(cwd, record) {
|
|
12000
12205
|
const filePath = getFlowRunPath(cwd, record.runId);
|
|
12001
|
-
await fs.ensureDir(
|
|
12206
|
+
await fs.ensureDir(path16.dirname(filePath));
|
|
12002
12207
|
await fs.writeJson(filePath, record, { spaces: 2 });
|
|
12003
12208
|
}
|
|
12004
12209
|
async function createFlowRunRecord(cwd, input) {
|
|
@@ -13414,27 +13619,27 @@ function tg(lang, key, vars = {}) {
|
|
|
13414
13619
|
function detectGithubCliLangSync(cwd) {
|
|
13415
13620
|
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
13416
13621
|
const startDirs = [
|
|
13417
|
-
explicitDocsDir ?
|
|
13418
|
-
|
|
13622
|
+
explicitDocsDir ? path16.resolve(explicitDocsDir) : "",
|
|
13623
|
+
path16.resolve(cwd)
|
|
13419
13624
|
].filter(Boolean);
|
|
13420
13625
|
const scanOrder = [];
|
|
13421
13626
|
const seen = /* @__PURE__ */ new Set();
|
|
13422
13627
|
for (const start of startDirs) {
|
|
13423
13628
|
let current = start;
|
|
13424
13629
|
while (true) {
|
|
13425
|
-
const abs =
|
|
13630
|
+
const abs = path16.resolve(current);
|
|
13426
13631
|
if (!seen.has(abs)) {
|
|
13427
13632
|
scanOrder.push(abs);
|
|
13428
13633
|
seen.add(abs);
|
|
13429
13634
|
}
|
|
13430
|
-
const parent =
|
|
13635
|
+
const parent = path16.dirname(abs);
|
|
13431
13636
|
if (parent === abs) break;
|
|
13432
13637
|
current = parent;
|
|
13433
13638
|
}
|
|
13434
13639
|
}
|
|
13435
13640
|
for (const base of scanOrder) {
|
|
13436
|
-
for (const docsDir of [
|
|
13437
|
-
const configPath =
|
|
13641
|
+
for (const docsDir of [path16.join(base, "docs"), base]) {
|
|
13642
|
+
const configPath = path16.join(docsDir, ".lee-spec-kit.json");
|
|
13438
13643
|
if (fs.existsSync(configPath)) {
|
|
13439
13644
|
try {
|
|
13440
13645
|
const parsed = fs.readJsonSync(configPath);
|
|
@@ -13443,11 +13648,11 @@ function detectGithubCliLangSync(cwd) {
|
|
|
13443
13648
|
} catch {
|
|
13444
13649
|
}
|
|
13445
13650
|
}
|
|
13446
|
-
const agentsPath =
|
|
13447
|
-
const featuresPath =
|
|
13651
|
+
const agentsPath = path16.join(docsDir, "agents");
|
|
13652
|
+
const featuresPath = path16.join(docsDir, "features");
|
|
13448
13653
|
if (!fs.existsSync(agentsPath) || !fs.existsSync(featuresPath)) continue;
|
|
13449
13654
|
for (const probe of ["custom.md", "constitution.md", "agents.md"]) {
|
|
13450
|
-
const file =
|
|
13655
|
+
const file = path16.join(agentsPath, probe);
|
|
13451
13656
|
if (!fs.existsSync(file)) continue;
|
|
13452
13657
|
try {
|
|
13453
13658
|
const content = fs.readFileSync(file, "utf-8");
|
|
@@ -13552,7 +13757,7 @@ async function prepareGithubBody(params) {
|
|
|
13552
13757
|
};
|
|
13553
13758
|
}
|
|
13554
13759
|
}
|
|
13555
|
-
await fs.ensureDir(
|
|
13760
|
+
await fs.ensureDir(path16.dirname(defaultBodyFile));
|
|
13556
13761
|
await fs.writeFile(defaultBodyFile, generatedBody, "utf-8");
|
|
13557
13762
|
return {
|
|
13558
13763
|
body: generatedBody,
|
|
@@ -13622,7 +13827,7 @@ function ensureSections(body, sections, kind, lang) {
|
|
|
13622
13827
|
}
|
|
13623
13828
|
function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
13624
13829
|
const missing = relativePaths.filter(
|
|
13625
|
-
(relativePath) => !fs.existsSync(
|
|
13830
|
+
(relativePath) => !fs.existsSync(path16.join(docsDir, relativePath))
|
|
13626
13831
|
);
|
|
13627
13832
|
if (missing.length > 0) {
|
|
13628
13833
|
throw createCliError(
|
|
@@ -13632,18 +13837,18 @@ function ensureDocsExist(docsDir, relativePaths, lang) {
|
|
|
13632
13837
|
}
|
|
13633
13838
|
}
|
|
13634
13839
|
function buildDefaultBodyFileName(kind, docsDir, component) {
|
|
13635
|
-
const key = `${
|
|
13840
|
+
const key = `${path16.resolve(docsDir)}::${component.trim().toLowerCase()}`;
|
|
13636
13841
|
const digest = createHash("sha1").update(key).digest("hex").slice(0, 12);
|
|
13637
13842
|
return `lee-spec-kit.${digest}.${kind}.md`;
|
|
13638
13843
|
}
|
|
13639
13844
|
function toBodyFilePath(raw, kind, docsDir, component, lang) {
|
|
13640
|
-
const selected = raw?.trim() ||
|
|
13845
|
+
const selected = raw?.trim() || path16.join(os.tmpdir(), buildDefaultBodyFileName(kind, docsDir, component));
|
|
13641
13846
|
assertValid(
|
|
13642
13847
|
validatePathWithLang(selected, lang),
|
|
13643
13848
|
`github.${kind}.bodyFile`,
|
|
13644
13849
|
lang
|
|
13645
13850
|
);
|
|
13646
|
-
return
|
|
13851
|
+
return path16.resolve(selected);
|
|
13647
13852
|
}
|
|
13648
13853
|
function toProjectRootDocsPath(relativePathFromDocs) {
|
|
13649
13854
|
const normalized = relativePathFromDocs.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -14777,7 +14982,7 @@ function ensureCleanWorktree(cwd, lang) {
|
|
|
14777
14982
|
function commitAndPushPaths(cwd, absPaths, message, lang, options) {
|
|
14778
14983
|
const uniqueRelativePaths = [
|
|
14779
14984
|
...new Set(
|
|
14780
|
-
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) =>
|
|
14985
|
+
absPaths.filter((absPath) => !!absPath && fs.existsSync(absPath)).map((absPath) => path16.relative(cwd, absPath) || absPath)
|
|
14781
14986
|
)
|
|
14782
14987
|
];
|
|
14783
14988
|
if (uniqueRelativePaths.length === 0) return;
|
|
@@ -14973,15 +15178,15 @@ function githubCommand(program2) {
|
|
|
14973
15178
|
config.lang
|
|
14974
15179
|
);
|
|
14975
15180
|
const specContent = await fs.readFile(
|
|
14976
|
-
|
|
15181
|
+
path16.join(config.docsDir, paths.specPath),
|
|
14977
15182
|
"utf-8"
|
|
14978
15183
|
);
|
|
14979
15184
|
const planContent = await fs.readFile(
|
|
14980
|
-
|
|
15185
|
+
path16.join(config.docsDir, paths.planPath),
|
|
14981
15186
|
"utf-8"
|
|
14982
15187
|
);
|
|
14983
15188
|
const tasksContent = await fs.readFile(
|
|
14984
|
-
|
|
15189
|
+
path16.join(config.docsDir, paths.tasksPath),
|
|
14985
15190
|
"utf-8"
|
|
14986
15191
|
);
|
|
14987
15192
|
const overview = resolveOverviewFromSpec(
|
|
@@ -15024,7 +15229,7 @@ function githubCommand(program2) {
|
|
|
15024
15229
|
create: options.create,
|
|
15025
15230
|
explicitBodyFile,
|
|
15026
15231
|
defaultBodyFile,
|
|
15027
|
-
workflowDraftPath:
|
|
15232
|
+
workflowDraftPath: path16.join(config.docsDir, paths.issuePath),
|
|
15028
15233
|
generatedBody,
|
|
15029
15234
|
requiredSections: getRequiredIssueSections(config.lang),
|
|
15030
15235
|
kindLabel: tg(config.lang, "kindIssue"),
|
|
@@ -15040,7 +15245,7 @@ function githubCommand(program2) {
|
|
|
15040
15245
|
`${feature.type}-issue-sanitized`,
|
|
15041
15246
|
config.lang
|
|
15042
15247
|
);
|
|
15043
|
-
await fs.ensureDir(
|
|
15248
|
+
await fs.ensureDir(path16.dirname(sanitizedBodyFile));
|
|
15044
15249
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
15045
15250
|
bodyFile = sanitizedBodyFile;
|
|
15046
15251
|
}
|
|
@@ -15089,12 +15294,12 @@ function githubCommand(program2) {
|
|
|
15089
15294
|
const syncedIssueNumber = extractIssueNumberFromUrl(issueUrl);
|
|
15090
15295
|
if (syncedIssueNumber) {
|
|
15091
15296
|
const synced = syncTasksIssueMetadata(
|
|
15092
|
-
|
|
15297
|
+
path16.join(config.docsDir, paths.tasksPath),
|
|
15093
15298
|
syncedIssueNumber,
|
|
15094
15299
|
config.lang
|
|
15095
15300
|
);
|
|
15096
15301
|
const draftSynced = syncIssueDraftMetadata(
|
|
15097
|
-
|
|
15302
|
+
path16.join(config.docsDir, paths.issuePath),
|
|
15098
15303
|
syncedIssueNumber
|
|
15099
15304
|
);
|
|
15100
15305
|
syncChanged = synced.changed || draftSynced.changed;
|
|
@@ -15205,13 +15410,13 @@ function githubCommand(program2) {
|
|
|
15205
15410
|
config.lang
|
|
15206
15411
|
);
|
|
15207
15412
|
const specContent = await fs.readFile(
|
|
15208
|
-
|
|
15413
|
+
path16.join(config.docsDir, paths.specPath),
|
|
15209
15414
|
"utf-8"
|
|
15210
15415
|
);
|
|
15211
|
-
const planPath =
|
|
15416
|
+
const planPath = path16.join(config.docsDir, paths.planPath);
|
|
15212
15417
|
const planContent = await fs.pathExists(planPath) ? await fs.readFile(planPath, "utf-8") : "";
|
|
15213
15418
|
const tasksContent = await fs.readFile(
|
|
15214
|
-
|
|
15419
|
+
path16.join(config.docsDir, paths.tasksPath),
|
|
15215
15420
|
"utf-8"
|
|
15216
15421
|
);
|
|
15217
15422
|
const overview = resolveOverviewFromSpec(
|
|
@@ -15259,7 +15464,7 @@ function githubCommand(program2) {
|
|
|
15259
15464
|
create: options.create,
|
|
15260
15465
|
explicitBodyFile,
|
|
15261
15466
|
defaultBodyFile,
|
|
15262
|
-
workflowDraftPath:
|
|
15467
|
+
workflowDraftPath: path16.join(config.docsDir, paths.prPath),
|
|
15263
15468
|
generatedBody,
|
|
15264
15469
|
requiredSections: getRequiredPrSections(config.lang),
|
|
15265
15470
|
kindLabel: tg(config.lang, "kindPr"),
|
|
@@ -15277,7 +15482,7 @@ function githubCommand(program2) {
|
|
|
15277
15482
|
`${feature.type}-pr-sanitized`,
|
|
15278
15483
|
config.lang
|
|
15279
15484
|
);
|
|
15280
|
-
await fs.ensureDir(
|
|
15485
|
+
await fs.ensureDir(path16.dirname(sanitizedBodyFile));
|
|
15281
15486
|
await fs.writeFile(sanitizedBodyFile, body, "utf-8");
|
|
15282
15487
|
bodyFile = sanitizedBodyFile;
|
|
15283
15488
|
}
|
|
@@ -15319,7 +15524,7 @@ function githubCommand(program2) {
|
|
|
15319
15524
|
if (preparedBody.source === "generated") {
|
|
15320
15525
|
await fs.writeFile(bodyFile, body, "utf-8");
|
|
15321
15526
|
} else {
|
|
15322
|
-
await fs.ensureDir(
|
|
15527
|
+
await fs.ensureDir(path16.dirname(fallbackBodyFile));
|
|
15323
15528
|
await fs.writeFile(fallbackBodyFile, body, "utf-8");
|
|
15324
15529
|
bodyFile = fallbackBodyFile;
|
|
15325
15530
|
}
|
|
@@ -15376,13 +15581,13 @@ function githubCommand(program2) {
|
|
|
15376
15581
|
}
|
|
15377
15582
|
if (prUrl && options.syncTasks !== false) {
|
|
15378
15583
|
const syncedTasks = syncTasksPrMetadata(
|
|
15379
|
-
|
|
15584
|
+
path16.join(config.docsDir, paths.tasksPath),
|
|
15380
15585
|
prUrl,
|
|
15381
15586
|
"Review",
|
|
15382
15587
|
config.lang
|
|
15383
15588
|
);
|
|
15384
15589
|
const syncedDraft = syncPrDraftMetadata(
|
|
15385
|
-
|
|
15590
|
+
path16.join(config.docsDir, paths.prPath),
|
|
15386
15591
|
prUrl,
|
|
15387
15592
|
"Review"
|
|
15388
15593
|
);
|
|
@@ -15423,13 +15628,13 @@ function githubCommand(program2) {
|
|
|
15423
15628
|
mergeAlreadyMerged = merged.alreadyMerged;
|
|
15424
15629
|
if (prUrl && options.syncTasks !== false) {
|
|
15425
15630
|
const mergedTasksSync = syncTasksPrMetadata(
|
|
15426
|
-
|
|
15631
|
+
path16.join(config.docsDir, paths.tasksPath),
|
|
15427
15632
|
prUrl,
|
|
15428
15633
|
"Approved",
|
|
15429
15634
|
config.lang
|
|
15430
15635
|
);
|
|
15431
15636
|
const mergedDraftSync = syncPrDraftMetadata(
|
|
15432
|
-
|
|
15637
|
+
path16.join(config.docsDir, paths.prPath),
|
|
15433
15638
|
prUrl,
|
|
15434
15639
|
"Approved"
|
|
15435
15640
|
);
|
|
@@ -15700,7 +15905,7 @@ function docsCommand(program2) {
|
|
|
15700
15905
|
);
|
|
15701
15906
|
return;
|
|
15702
15907
|
}
|
|
15703
|
-
const relativeFromCwd =
|
|
15908
|
+
const relativeFromCwd = path16.relative(process.cwd(), loaded.entry.absolutePath);
|
|
15704
15909
|
console.log();
|
|
15705
15910
|
console.log(chalk9.bold(`\u{1F4C4} ${loaded.entry.id}: ${loaded.entry.title}`));
|
|
15706
15911
|
console.log(
|
|
@@ -15780,8 +15985,9 @@ function detectCommand(program2) {
|
|
|
15780
15985
|
}
|
|
15781
15986
|
async function runDetect(options) {
|
|
15782
15987
|
const cwd = process.cwd();
|
|
15783
|
-
const targetCwd = options.dir ?
|
|
15784
|
-
const
|
|
15988
|
+
const targetCwd = options.dir ? path16.resolve(cwd, options.dir) : cwd;
|
|
15989
|
+
const detection = await detectSchemaProject(targetCwd);
|
|
15990
|
+
const config = detection.config;
|
|
15785
15991
|
const detected = !!config;
|
|
15786
15992
|
const reasonCode = detected ? "PROJECT_DETECTED" : "PROJECT_NOT_DETECTED";
|
|
15787
15993
|
if (options.json) {
|
|
@@ -15807,9 +16013,6 @@ async function runDetect(options) {
|
|
|
15807
16013
|
);
|
|
15808
16014
|
return;
|
|
15809
16015
|
}
|
|
15810
|
-
const configPath2 = path15.join(config.docsDir, ".lee-spec-kit.json");
|
|
15811
|
-
const configFilePresent2 = await fs.pathExists(configPath2);
|
|
15812
|
-
const detectionSource2 = configFilePresent2 ? "config" : "heuristic";
|
|
15813
16016
|
console.log(
|
|
15814
16017
|
JSON.stringify(
|
|
15815
16018
|
{
|
|
@@ -15817,10 +16020,10 @@ async function runDetect(options) {
|
|
|
15817
16020
|
reasonCode,
|
|
15818
16021
|
isLeeSpecKitProject: true,
|
|
15819
16022
|
targetCwd,
|
|
15820
|
-
docsDir:
|
|
15821
|
-
configPath:
|
|
15822
|
-
configFilePresent:
|
|
15823
|
-
detectionSource:
|
|
16023
|
+
docsDir: detection.docsDir,
|
|
16024
|
+
configPath: detection.configPath,
|
|
16025
|
+
configFilePresent: detection.configFilePresent,
|
|
16026
|
+
detectionSource: detection.detectionSource,
|
|
15824
16027
|
projectType: config.projectType,
|
|
15825
16028
|
lang: config.lang,
|
|
15826
16029
|
projectName: config.projectName ?? null
|
|
@@ -15841,14 +16044,11 @@ async function runDetect(options) {
|
|
|
15841
16044
|
console.log();
|
|
15842
16045
|
return;
|
|
15843
16046
|
}
|
|
15844
|
-
const configPath = path15.join(config.docsDir, ".lee-spec-kit.json");
|
|
15845
|
-
const configFilePresent = await fs.pathExists(configPath);
|
|
15846
|
-
const detectionSource = configFilePresent ? "config" : "heuristic";
|
|
15847
16047
|
console.log(chalk9.green(`- ${tr(lang, "cli", "detect.resultDetected")}`));
|
|
15848
|
-
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelDocsDir")}: ${
|
|
16048
|
+
console.log(chalk9.gray(`- ${tr(lang, "cli", "detect.labelDocsDir")}: ${detection.docsDir}`));
|
|
15849
16049
|
console.log(
|
|
15850
16050
|
chalk9.gray(
|
|
15851
|
-
`- ${tr(lang, "cli", "detect.labelConfigPath")}: ${
|
|
16051
|
+
`- ${tr(lang, "cli", "detect.labelConfigPath")}: ${detection.configPath || "-"}`
|
|
15852
16052
|
)
|
|
15853
16053
|
);
|
|
15854
16054
|
console.log(
|
|
@@ -15856,7 +16056,7 @@ async function runDetect(options) {
|
|
|
15856
16056
|
`- ${tr(lang, "cli", "detect.labelSource")}: ${tr(
|
|
15857
16057
|
lang,
|
|
15858
16058
|
"cli",
|
|
15859
|
-
detectionSource === "config" ? "detect.sourceConfig" : "detect.sourceHeuristic"
|
|
16059
|
+
detection.detectionSource === "config" ? "detect.sourceConfig" : "detect.sourceHeuristic"
|
|
15860
16060
|
)}`
|
|
15861
16061
|
)
|
|
15862
16062
|
);
|
|
@@ -15918,19 +16118,19 @@ function hasTemplateMarkers(content) {
|
|
|
15918
16118
|
return patterns.some((pattern) => pattern.test(content));
|
|
15919
16119
|
}
|
|
15920
16120
|
async function countFeatureDirs(ctx, docsDir, projectType) {
|
|
15921
|
-
const featuresRoot =
|
|
16121
|
+
const featuresRoot = path16.join(docsDir, "features");
|
|
15922
16122
|
if (projectType === "single") {
|
|
15923
16123
|
const dirs = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15924
|
-
return dirs.filter((value) =>
|
|
16124
|
+
return dirs.filter((value) => path16.basename(value) !== "feature-base").length;
|
|
15925
16125
|
}
|
|
15926
16126
|
const components = await listSubdirectories(ctx.fs, featuresRoot);
|
|
15927
16127
|
let total = 0;
|
|
15928
16128
|
for (const componentDir of components) {
|
|
15929
|
-
const componentName =
|
|
16129
|
+
const componentName = path16.basename(componentDir).trim().toLowerCase();
|
|
15930
16130
|
if (!componentName || componentName === "feature-base") continue;
|
|
15931
16131
|
const dirs = await listSubdirectories(ctx.fs, componentDir);
|
|
15932
16132
|
total += dirs.filter(
|
|
15933
|
-
(value) =>
|
|
16133
|
+
(value) => path16.basename(value) !== "feature-base"
|
|
15934
16134
|
).length;
|
|
15935
16135
|
}
|
|
15936
16136
|
return total;
|
|
@@ -15942,7 +16142,7 @@ async function hasUserPrdFile(ctx, prdDir) {
|
|
|
15942
16142
|
ignoreDirs: ["node_modules"]
|
|
15943
16143
|
});
|
|
15944
16144
|
return files.some(
|
|
15945
|
-
(absolutePath) =>
|
|
16145
|
+
(absolutePath) => path16.basename(absolutePath).toLowerCase() !== "readme.md"
|
|
15946
16146
|
);
|
|
15947
16147
|
}
|
|
15948
16148
|
function finalizeChecks(checks) {
|
|
@@ -16111,7 +16311,7 @@ async function runOnboardChecks(ctx) {
|
|
|
16111
16311
|
});
|
|
16112
16312
|
}
|
|
16113
16313
|
}
|
|
16114
|
-
const constitutionPath =
|
|
16314
|
+
const constitutionPath = path16.join(docsDir, "agents", "constitution.md");
|
|
16115
16315
|
if (!await fs.pathExists(constitutionPath)) {
|
|
16116
16316
|
checks.push({
|
|
16117
16317
|
id: "constitution_exists",
|
|
@@ -16153,7 +16353,7 @@ async function runOnboardChecks(ctx) {
|
|
|
16153
16353
|
});
|
|
16154
16354
|
}
|
|
16155
16355
|
}
|
|
16156
|
-
const customPath =
|
|
16356
|
+
const customPath = path16.join(docsDir, "agents", "custom.md");
|
|
16157
16357
|
if (await fs.pathExists(customPath)) {
|
|
16158
16358
|
const content = await fs.readFile(customPath, "utf-8");
|
|
16159
16359
|
if (hasTemplateMarkers(content)) {
|
|
@@ -16182,7 +16382,7 @@ async function runOnboardChecks(ctx) {
|
|
|
16182
16382
|
});
|
|
16183
16383
|
}
|
|
16184
16384
|
}
|
|
16185
|
-
const prdDir =
|
|
16385
|
+
const prdDir = path16.join(docsDir, "prd");
|
|
16186
16386
|
const featureCount = await countFeatureDirs(ctx, docsDir, config.projectType);
|
|
16187
16387
|
const prdReady = await hasUserPrdFile(ctx, prdDir);
|
|
16188
16388
|
if (!prdReady) {
|
|
@@ -16200,7 +16400,7 @@ async function runOnboardChecks(ctx) {
|
|
|
16200
16400
|
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
16201
16401
|
),
|
|
16202
16402
|
path: prdDir,
|
|
16203
|
-
suggestedCommand: `touch ${quotePath(
|
|
16403
|
+
suggestedCommand: `touch ${quotePath(path16.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
16204
16404
|
});
|
|
16205
16405
|
} else {
|
|
16206
16406
|
checks.push({
|
|
@@ -16638,7 +16838,7 @@ var PrePrReviewValidator = class {
|
|
|
16638
16838
|
return result.evidence;
|
|
16639
16839
|
}
|
|
16640
16840
|
async validateEvidenceWithScope(evidencePath, projectRoot) {
|
|
16641
|
-
const fullPath =
|
|
16841
|
+
const fullPath = path16.resolve(evidencePath);
|
|
16642
16842
|
if (!await fs.pathExists(fullPath)) {
|
|
16643
16843
|
throw createCliError(
|
|
16644
16844
|
"INVALID_ARGUMENT",
|
|
@@ -16736,9 +16936,9 @@ var PrePrReviewValidator = class {
|
|
|
16736
16936
|
]);
|
|
16737
16937
|
const reviewedFiles = new Set(
|
|
16738
16938
|
normalizedEvidence.files.map(
|
|
16739
|
-
(f) =>
|
|
16939
|
+
(f) => path16.relative(
|
|
16740
16940
|
projectRoot,
|
|
16741
|
-
|
|
16941
|
+
path16.resolve(projectRoot, f.path)
|
|
16742
16942
|
)
|
|
16743
16943
|
).map((entry) => normalizeGitPath3(entry)).filter(Boolean)
|
|
16744
16944
|
);
|
|
@@ -17199,7 +17399,7 @@ async function runPrePrReviewRun(featureName, options) {
|
|
|
17199
17399
|
);
|
|
17200
17400
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
17201
17401
|
const preferred = getPreferredKeys(config.lang);
|
|
17202
|
-
const tasksPath =
|
|
17402
|
+
const tasksPath = path16.join(feature.path, "tasks.md");
|
|
17203
17403
|
let tasksUpdated = false;
|
|
17204
17404
|
if (await fs.pathExists(tasksPath)) {
|
|
17205
17405
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -17308,7 +17508,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
17308
17508
|
`tasks.md not found for feature: ${feature.folderName}`
|
|
17309
17509
|
);
|
|
17310
17510
|
}
|
|
17311
|
-
const tasksPath =
|
|
17511
|
+
const tasksPath = path16.join(feature.path, "tasks.md");
|
|
17312
17512
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
17313
17513
|
const policy = resolvePrePrReviewPolicy(config.workflow);
|
|
17314
17514
|
const preferred = getPreferredKeys(config.lang);
|
|
@@ -17406,7 +17606,7 @@ async function runPrePrReview(featureName, options) {
|
|
|
17406
17606
|
}
|
|
17407
17607
|
}
|
|
17408
17608
|
}
|
|
17409
|
-
const decisionsPath =
|
|
17609
|
+
const decisionsPath = path16.join(feature.path, "decisions.md");
|
|
17410
17610
|
const decisionLogEntry = buildReportContent({
|
|
17411
17611
|
folderName: feature.folderName,
|
|
17412
17612
|
date,
|
|
@@ -17422,9 +17622,9 @@ async function runPrePrReview(featureName, options) {
|
|
|
17422
17622
|
await fs.writeFile(decisionsPath, nextDecisions, "utf-8");
|
|
17423
17623
|
}
|
|
17424
17624
|
const decisionsPathFromDocs = normalizePathForDoc(
|
|
17425
|
-
|
|
17625
|
+
path16.join(feature.docs.featurePathFromDocs, "decisions.md")
|
|
17426
17626
|
);
|
|
17427
|
-
const evidencePath =
|
|
17627
|
+
const evidencePath = path16.basename(config.docsDir) === "docs" ? normalizePathForDoc(path16.join("docs", decisionsPathFromDocs)) : decisionsPathFromDocs;
|
|
17428
17628
|
let nextTasks = tasksContent;
|
|
17429
17629
|
nextTasks = upsertSpecLine(
|
|
17430
17630
|
nextTasks,
|
|
@@ -17565,7 +17765,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
17565
17765
|
);
|
|
17566
17766
|
}
|
|
17567
17767
|
const feature = state.matchedFeature;
|
|
17568
|
-
const tasksPath =
|
|
17768
|
+
const tasksPath = path16.join(feature.path, "tasks.md");
|
|
17569
17769
|
let tasksUpdated = false;
|
|
17570
17770
|
if (await fs.pathExists(tasksPath)) {
|
|
17571
17771
|
const tasksContent = await fs.readFile(tasksPath, "utf-8");
|
|
@@ -17598,7 +17798,7 @@ async function runCodeReviewRun(featureName, options) {
|
|
|
17598
17798
|
nextMainState: "code_review_running",
|
|
17599
17799
|
tasksUpdated,
|
|
17600
17800
|
tasksPath,
|
|
17601
|
-
decisionsPath:
|
|
17801
|
+
decisionsPath: path16.join(feature.path, "decisions.md"),
|
|
17602
17802
|
prompt,
|
|
17603
17803
|
recordedAt: getLocalDateString()
|
|
17604
17804
|
};
|
|
@@ -17717,7 +17917,7 @@ async function runRequirements(options) {
|
|
|
17717
17917
|
}
|
|
17718
17918
|
for (const feature of scan.features) {
|
|
17719
17919
|
if (!feature.docs.tasksExists) continue;
|
|
17720
|
-
const tasksPath =
|
|
17920
|
+
const tasksPath = path16.join(feature.path, "tasks.md");
|
|
17721
17921
|
let tasksContent = "";
|
|
17722
17922
|
try {
|
|
17723
17923
|
tasksContent = await ctx.fs.readFile(tasksPath, "utf-8");
|
|
@@ -17851,7 +18051,7 @@ async function runRequirements(options) {
|
|
|
17851
18051
|
process.stdout.write(`${lines.join("\n")}
|
|
17852
18052
|
`);
|
|
17853
18053
|
if (options.write) {
|
|
17854
|
-
const outputPath =
|
|
18054
|
+
const outputPath = path16.join(docsDir, "prd", "status.md");
|
|
17855
18055
|
await ctx.fs.writeFile(outputPath, `${lines.join("\n")}
|
|
17856
18056
|
`, "utf-8");
|
|
17857
18057
|
console.log(chalk9.green(`\u2705 wrote: ${outputPath}`));
|
|
@@ -17875,8 +18075,105 @@ function parseTaskLine(line, index = -1) {
|
|
|
17875
18075
|
title: match[4]
|
|
17876
18076
|
};
|
|
17877
18077
|
}
|
|
18078
|
+
function countLeadingSpaces(line) {
|
|
18079
|
+
const match = line.match(/^(\s*)/);
|
|
18080
|
+
return match?.[1]?.length ?? 0;
|
|
18081
|
+
}
|
|
18082
|
+
function findTaskBlockEnd(lines, taskLineIndex) {
|
|
18083
|
+
let endIndex = lines.length;
|
|
18084
|
+
for (let index = taskLineIndex + 1; index < lines.length; index++) {
|
|
18085
|
+
if (parseTaskLine(lines[index]) || /^\s*##\s+/.test(lines[index])) {
|
|
18086
|
+
endIndex = index;
|
|
18087
|
+
break;
|
|
18088
|
+
}
|
|
18089
|
+
}
|
|
18090
|
+
return endIndex;
|
|
18091
|
+
}
|
|
18092
|
+
function isPlaceholderTaskItem(text) {
|
|
18093
|
+
const normalized = text.trim();
|
|
18094
|
+
return normalized === "" || normalized === "-" || /^todo$/i.test(normalized);
|
|
18095
|
+
}
|
|
18096
|
+
function parseTaskSectionItems(lines, taskLineIndex, headingPattern) {
|
|
18097
|
+
if (taskLineIndex < 0 || taskLineIndex >= lines.length) return void 0;
|
|
18098
|
+
const endIndex = findTaskBlockEnd(lines, taskLineIndex);
|
|
18099
|
+
let sectionHeaderIndex = -1;
|
|
18100
|
+
let sectionHeaderIndent = 0;
|
|
18101
|
+
for (let index = taskLineIndex + 1; index < endIndex; index++) {
|
|
18102
|
+
if (headingPattern.test(lines[index])) {
|
|
18103
|
+
sectionHeaderIndex = index;
|
|
18104
|
+
sectionHeaderIndent = countLeadingSpaces(lines[index]);
|
|
18105
|
+
break;
|
|
18106
|
+
}
|
|
18107
|
+
}
|
|
18108
|
+
if (sectionHeaderIndex === -1) return void 0;
|
|
18109
|
+
const items = [];
|
|
18110
|
+
let placeholderCount = 0;
|
|
18111
|
+
for (let index = sectionHeaderIndex + 1; index < endIndex; index++) {
|
|
18112
|
+
const line = lines[index];
|
|
18113
|
+
if (!line.trim()) continue;
|
|
18114
|
+
const indent = countLeadingSpaces(line);
|
|
18115
|
+
if (indent <= sectionHeaderIndent && /^\s*-\s+/.test(line)) {
|
|
18116
|
+
break;
|
|
18117
|
+
}
|
|
18118
|
+
const match = line.match(/^\s*-\s+(.+?)\s*$/);
|
|
18119
|
+
if (!match) continue;
|
|
18120
|
+
const text = match[1].trim();
|
|
18121
|
+
items.push(text);
|
|
18122
|
+
if (isPlaceholderTaskItem(text)) placeholderCount++;
|
|
18123
|
+
}
|
|
18124
|
+
if (items.length === 0) return void 0;
|
|
18125
|
+
return { items, placeholderCount };
|
|
18126
|
+
}
|
|
18127
|
+
function parseTaskAcceptance(lines, taskLineIndex) {
|
|
18128
|
+
return parseTaskSectionItems(lines, taskLineIndex, /^\s*-\s+Acceptance:\s*$/);
|
|
18129
|
+
}
|
|
18130
|
+
function parseTaskChecklist(lines, taskLineIndex) {
|
|
18131
|
+
if (taskLineIndex < 0 || taskLineIndex >= lines.length) return void 0;
|
|
18132
|
+
const endIndex = findTaskBlockEnd(lines, taskLineIndex);
|
|
18133
|
+
let checklistHeaderIndex = -1;
|
|
18134
|
+
let checklistHeaderIndent = 0;
|
|
18135
|
+
for (let index = taskLineIndex + 1; index < endIndex; index++) {
|
|
18136
|
+
if (/^\s*-\s+Checklist:\s*$/.test(lines[index])) {
|
|
18137
|
+
checklistHeaderIndex = index;
|
|
18138
|
+
checklistHeaderIndent = countLeadingSpaces(lines[index]);
|
|
18139
|
+
break;
|
|
18140
|
+
}
|
|
18141
|
+
}
|
|
18142
|
+
if (checklistHeaderIndex === -1) return void 0;
|
|
18143
|
+
let total = 0;
|
|
18144
|
+
let checked = 0;
|
|
18145
|
+
let placeholderCount = 0;
|
|
18146
|
+
for (let index = checklistHeaderIndex + 1; index < endIndex; index++) {
|
|
18147
|
+
const line = lines[index];
|
|
18148
|
+
if (!line.trim()) continue;
|
|
18149
|
+
const indent = countLeadingSpaces(line);
|
|
18150
|
+
if (indent <= checklistHeaderIndent && /^\s*-\s+/.test(line)) {
|
|
18151
|
+
break;
|
|
18152
|
+
}
|
|
18153
|
+
const match = line.match(/^\s*-\s*\[([ xX])\]\s+/);
|
|
18154
|
+
if (!match) continue;
|
|
18155
|
+
total++;
|
|
18156
|
+
if (match[1].toLowerCase() === "x") checked++;
|
|
18157
|
+
const text = line.replace(/^\s*-\s*\[[ xX]\]\s+/, "").trim();
|
|
18158
|
+
if (isPlaceholderTaskItem(text)) placeholderCount++;
|
|
18159
|
+
}
|
|
18160
|
+
if (total === 0) return void 0;
|
|
18161
|
+
return { total, checked, unchecked: total - checked, placeholderCount };
|
|
18162
|
+
}
|
|
17878
18163
|
|
|
17879
18164
|
// src/commands/task-run.ts
|
|
18165
|
+
function ensureTaskDetailsReady(lines, task) {
|
|
18166
|
+
const acceptance = parseTaskAcceptance(lines, task.index);
|
|
18167
|
+
const checklist = parseTaskChecklist(lines, task.index);
|
|
18168
|
+
const acceptanceHasPlaceholder = !!acceptance && (acceptance.items.length === 0 || acceptance.placeholderCount > 0);
|
|
18169
|
+
const checklistHasPlaceholder = !!checklist && (checklist.total === 0 || checklist.placeholderCount > 0);
|
|
18170
|
+
if (acceptanceHasPlaceholder || checklistHasPlaceholder) {
|
|
18171
|
+
throw createCliError(
|
|
18172
|
+
"PRECONDITION_FAILED",
|
|
18173
|
+
`Task "${task.taskId}" still contains Acceptance/Checklist placeholder content. Fill concrete Acceptance items and Checklist items before running task-run.`
|
|
18174
|
+
);
|
|
18175
|
+
}
|
|
18176
|
+
}
|
|
17880
18177
|
function buildTaskRunPrompt(input) {
|
|
17881
18178
|
const shared = [
|
|
17882
18179
|
"Read `spec.md`, `plan.md`, and `tasks.md` before editing code.",
|
|
@@ -17884,8 +18181,8 @@ function buildTaskRunPrompt(input) {
|
|
|
17884
18181
|
"Use additional helper agents only when parallel analysis is clearly worth the extra slot cost.",
|
|
17885
18182
|
"Keep one writer for overlapping files; do not let multiple sub-agents edit the same files concurrently.",
|
|
17886
18183
|
"If helper-agent quota is exhausted, continue the task in the main agent instead of blocking progress.",
|
|
17887
|
-
"Update the assigned task status and verification notes in `tasks.md` before leaving this task.",
|
|
17888
|
-
"Mark the task `DONE` only after code changes and verification are complete."
|
|
18184
|
+
"Update the assigned task status, task-local checklist boxes, and verification notes in `tasks.md` before leaving this task.",
|
|
18185
|
+
"Mark the task `DONE` only after code changes and verification are complete. `task-complete` will reject the transition if checklist items remain unchecked."
|
|
17889
18186
|
];
|
|
17890
18187
|
if (input.lang === "ko") {
|
|
17891
18188
|
return [
|
|
@@ -17941,7 +18238,7 @@ async function resolveTaskRunContext(featureName, options) {
|
|
|
17941
18238
|
}
|
|
17942
18239
|
async function runTaskRun(featureName, options) {
|
|
17943
18240
|
const { config, feature } = await resolveTaskRunContext(featureName, options);
|
|
17944
|
-
const tasksPath =
|
|
18241
|
+
const tasksPath = path16.join(feature.path, "tasks.md");
|
|
17945
18242
|
if (!await fs.pathExists(tasksPath)) {
|
|
17946
18243
|
throw createCliError(
|
|
17947
18244
|
"PRECONDITION_FAILED",
|
|
@@ -17973,6 +18270,7 @@ async function runTaskRun(featureName, options) {
|
|
|
17973
18270
|
`Task "${requestedTaskId}" is already DONE.`
|
|
17974
18271
|
);
|
|
17975
18272
|
}
|
|
18273
|
+
ensureTaskDetailsReady(lines, resolvedTask);
|
|
17976
18274
|
const mode = resolvedTask.status === "TODO" ? "start" : "continue";
|
|
17977
18275
|
let tasksUpdated = false;
|
|
17978
18276
|
if (resolvedTask.status === "TODO") {
|
|
@@ -18079,7 +18377,7 @@ async function resolveTaskCompleteContext(featureName, options) {
|
|
|
18079
18377
|
}
|
|
18080
18378
|
async function runTaskComplete(featureName, options) {
|
|
18081
18379
|
const { feature } = await resolveTaskCompleteContext(featureName, options);
|
|
18082
|
-
const tasksPath =
|
|
18380
|
+
const tasksPath = path16.join(feature.path, "tasks.md");
|
|
18083
18381
|
if (!await fs.pathExists(tasksPath)) {
|
|
18084
18382
|
throw createCliError(
|
|
18085
18383
|
"PRECONDITION_FAILED",
|
|
@@ -18117,6 +18415,13 @@ async function runTaskComplete(featureName, options) {
|
|
|
18117
18415
|
`Task "${requestedTaskId}" must be DOING/REVIEW before marking it DONE.`
|
|
18118
18416
|
);
|
|
18119
18417
|
}
|
|
18418
|
+
const checklist = parseTaskChecklist(lines, resolvedTask.index);
|
|
18419
|
+
if (checklist && checklist.unchecked > 0) {
|
|
18420
|
+
throw createCliError(
|
|
18421
|
+
"PRECONDITION_FAILED",
|
|
18422
|
+
`Task "${requestedTaskId}" still has unchecked checklist items (${checklist.checked}/${checklist.total}).`
|
|
18423
|
+
);
|
|
18424
|
+
}
|
|
18120
18425
|
lines[resolvedTask.index] = setTaskStatus2(resolvedTask, "DONE");
|
|
18121
18426
|
await fs.writeFile(tasksPath, lines.join("\n"), "utf-8");
|
|
18122
18427
|
const payload = {
|
|
@@ -18175,6 +18480,21 @@ function taskCompleteCommand(program2) {
|
|
|
18175
18480
|
}
|
|
18176
18481
|
);
|
|
18177
18482
|
}
|
|
18483
|
+
function collectRepeatableOption(value, previous = []) {
|
|
18484
|
+
return [...previous, value];
|
|
18485
|
+
}
|
|
18486
|
+
function normalizeTaskDetailItems(values, flagName) {
|
|
18487
|
+
const normalized = (values || []).map((value) => value.trim()).filter(Boolean);
|
|
18488
|
+
for (const value of normalized) {
|
|
18489
|
+
if (value === "-" || /^todo$/i.test(value)) {
|
|
18490
|
+
throw createCliError(
|
|
18491
|
+
"INVALID_ARGUMENT",
|
|
18492
|
+
`${flagName} must contain concrete text, not placeholder values like "-" or "TODO".`
|
|
18493
|
+
);
|
|
18494
|
+
}
|
|
18495
|
+
}
|
|
18496
|
+
return normalized;
|
|
18497
|
+
}
|
|
18178
18498
|
function escapeRegExp8(value) {
|
|
18179
18499
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18180
18500
|
}
|
|
@@ -18240,9 +18560,9 @@ function formatTaskBlock(input) {
|
|
|
18240
18560
|
`- [TODO][${input.ref}] ${input.taskId} ${input.title}`,
|
|
18241
18561
|
` - Date: ${input.recordedAt}`,
|
|
18242
18562
|
" - Acceptance:",
|
|
18243
|
-
" - -",
|
|
18563
|
+
...input.acceptanceItems.length > 0 ? input.acceptanceItems.map((item) => ` - ${item}`) : [" - -"],
|
|
18244
18564
|
" - Checklist:",
|
|
18245
|
-
" - [ ] -"
|
|
18565
|
+
...input.checklistItems.length > 0 ? input.checklistItems.map((item) => ` - [ ] ${item}`) : [" - [ ] -"]
|
|
18246
18566
|
];
|
|
18247
18567
|
}
|
|
18248
18568
|
async function resolveTaskFeature(featureName, component) {
|
|
@@ -18273,6 +18593,11 @@ async function runTaskAdd(featureName, options) {
|
|
|
18273
18593
|
if (!title) {
|
|
18274
18594
|
throw createCliError("INVALID_ARGUMENT", "`--title` must not be empty.");
|
|
18275
18595
|
}
|
|
18596
|
+
const acceptanceItems = normalizeTaskDetailItems(
|
|
18597
|
+
options.acceptance,
|
|
18598
|
+
"--acceptance"
|
|
18599
|
+
);
|
|
18600
|
+
const checklistItems = normalizeTaskDetailItems(options.check, "--check");
|
|
18276
18601
|
const ref = normalizeTaskRef(options.ref);
|
|
18277
18602
|
if (isPrdRequirementId(ref)) {
|
|
18278
18603
|
const { definitions } = await scanPrdRequirements(ctx.fs, ctx.config.docsDir);
|
|
@@ -18283,7 +18608,7 @@ async function runTaskAdd(featureName, options) {
|
|
|
18283
18608
|
);
|
|
18284
18609
|
}
|
|
18285
18610
|
}
|
|
18286
|
-
const tasksPath =
|
|
18611
|
+
const tasksPath = path16.join(feature.path, "tasks.md");
|
|
18287
18612
|
if (!await fs.pathExists(tasksPath)) {
|
|
18288
18613
|
throw createCliError(
|
|
18289
18614
|
"PRECONDITION_FAILED",
|
|
@@ -18312,7 +18637,14 @@ async function runTaskAdd(featureName, options) {
|
|
|
18312
18637
|
nextSectionHeadingIndex
|
|
18313
18638
|
);
|
|
18314
18639
|
const recordedAt = getLocalDateString();
|
|
18315
|
-
const blockLines = formatTaskBlock({
|
|
18640
|
+
const blockLines = formatTaskBlock({
|
|
18641
|
+
ref,
|
|
18642
|
+
taskId,
|
|
18643
|
+
title,
|
|
18644
|
+
recordedAt,
|
|
18645
|
+
acceptanceItems,
|
|
18646
|
+
checklistItems
|
|
18647
|
+
});
|
|
18316
18648
|
const shouldPrefixBlank = insertIndex > taskListHeadingIndex + 1 && (lines[insertIndex - 1] || "").trim() !== "";
|
|
18317
18649
|
const shouldSuffixBlank = insertIndex < lines.length && (lines[insertIndex] || "").trim() !== "";
|
|
18318
18650
|
const insertLines = [
|
|
@@ -18343,7 +18675,17 @@ async function runTaskAdd(featureName, options) {
|
|
|
18343
18675
|
}
|
|
18344
18676
|
function taskCommand(program2) {
|
|
18345
18677
|
const task = program2.command("task").description("Manage tasks");
|
|
18346
|
-
task.command("add [feature-name]").description("Append a new task to the end of Task List").requiredOption("--title <title>", "Task title").requiredOption("--ref <ref>", "Requirement ref: NON-PRD or PRD-FR-001").option(
|
|
18678
|
+
task.command("add [feature-name]").description("Append a new task to the end of Task List").requiredOption("--title <title>", "Task title").requiredOption("--ref <ref>", "Requirement ref: NON-PRD or PRD-FR-001").option(
|
|
18679
|
+
"--acceptance <text>",
|
|
18680
|
+
"Acceptance item. Repeat to add more than one.",
|
|
18681
|
+
collectRepeatableOption,
|
|
18682
|
+
[]
|
|
18683
|
+
).option(
|
|
18684
|
+
"--check <text>",
|
|
18685
|
+
"Checklist item. Repeat to add more than one.",
|
|
18686
|
+
collectRepeatableOption,
|
|
18687
|
+
[]
|
|
18688
|
+
).option("--component <component>", "Component name for multi projects").option("--json", "Output JSON").action(async (featureName, options) => {
|
|
18347
18689
|
try {
|
|
18348
18690
|
await runTaskAdd(featureName, options);
|
|
18349
18691
|
} catch (error) {
|
|
@@ -18369,10 +18711,9 @@ function taskCommand(program2) {
|
|
|
18369
18711
|
}
|
|
18370
18712
|
});
|
|
18371
18713
|
}
|
|
18372
|
-
function
|
|
18373
|
-
|
|
18374
|
-
|
|
18375
|
-
"Install a small Codex global bootstrap that reads ./AGENTS.md or ./docs/AGENTS.md"
|
|
18714
|
+
function registerCodexIntegration(parent) {
|
|
18715
|
+
parent.command("codex").alias("codex-bootstrap").description(
|
|
18716
|
+
"Install or remove the optional Codex bootstrap that re-reads ./AGENTS.md or ./docs/AGENTS.md"
|
|
18376
18717
|
).option(
|
|
18377
18718
|
"--remove",
|
|
18378
18719
|
"Remove the lee-spec-kit managed Codex bootstrap block"
|
|
@@ -18405,6 +18746,10 @@ function setupCommand(program2) {
|
|
|
18405
18746
|
}
|
|
18406
18747
|
});
|
|
18407
18748
|
}
|
|
18749
|
+
function integrationsCommand(program2) {
|
|
18750
|
+
const integrations = program2.command("integrations").alias("setup").description("Optional developer integration helpers");
|
|
18751
|
+
registerCodexIntegration(integrations);
|
|
18752
|
+
}
|
|
18408
18753
|
function isBannerDisabled() {
|
|
18409
18754
|
const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
|
|
18410
18755
|
return v === "1";
|
|
@@ -18448,11 +18793,11 @@ ${version}
|
|
|
18448
18793
|
}
|
|
18449
18794
|
return `${ascii}${footer}`;
|
|
18450
18795
|
}
|
|
18451
|
-
var CACHE_FILE =
|
|
18796
|
+
var CACHE_FILE = path16.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
18452
18797
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
18453
18798
|
function getCurrentVersion() {
|
|
18454
18799
|
try {
|
|
18455
|
-
const packageJsonPath =
|
|
18800
|
+
const packageJsonPath = path16.join(__dirname$1, "..", "package.json");
|
|
18456
18801
|
if (fs.existsSync(packageJsonPath)) {
|
|
18457
18802
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
18458
18803
|
return pkg.version;
|
|
@@ -18556,7 +18901,7 @@ function shouldCheckForUpdates() {
|
|
|
18556
18901
|
if (shouldCheckForUpdates()) checkForUpdates();
|
|
18557
18902
|
function getCliVersion() {
|
|
18558
18903
|
try {
|
|
18559
|
-
const packageJsonPath =
|
|
18904
|
+
const packageJsonPath = path16.join(__dirname$1, "..", "package.json");
|
|
18560
18905
|
if (fs.existsSync(packageJsonPath)) {
|
|
18561
18906
|
const pkg = fs.readJsonSync(packageJsonPath);
|
|
18562
18907
|
if (pkg?.version) return String(pkg.version);
|
|
@@ -18576,9 +18921,7 @@ function configureRootCommandSurface() {
|
|
|
18576
18921
|
}
|
|
18577
18922
|
}
|
|
18578
18923
|
var cliVersion = getCliVersion();
|
|
18579
|
-
program.name("lee-spec-kit").description(
|
|
18580
|
-
"Agent-guided development harness for spec-driven projects"
|
|
18581
|
-
).version(cliVersion).option("--no-banner", "Hide banner in help output");
|
|
18924
|
+
program.name("lee-spec-kit").description("Orchestration harness CLI for AI agent-driven development").version(cliVersion).option("--no-banner", "Hide banner in help output");
|
|
18582
18925
|
if (shouldShowBanner()) {
|
|
18583
18926
|
program.addHelpText("beforeAll", getBanner({ version: cliVersion }));
|
|
18584
18927
|
}
|
|
@@ -18602,7 +18945,7 @@ taskRunCommand(program);
|
|
|
18602
18945
|
taskCompleteCommand(program);
|
|
18603
18946
|
taskCommand(program);
|
|
18604
18947
|
requirementsCommand(program);
|
|
18605
|
-
|
|
18948
|
+
integrationsCommand(program);
|
|
18606
18949
|
configureRootCommandSurface();
|
|
18607
18950
|
await program.parseAsync();
|
|
18608
18951
|
//# sourceMappingURL=index.js.map
|