sdd-flow-kit 1.0.27 → 1.1.1

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.
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isPrdFetched = isPrdFetched;
7
+ exports.isQuestionsClosed = isQuestionsClosed;
8
+ exports.areAnalysisDocsReady = areAnalysisDocsReady;
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = require("./fs");
11
+ const PRD_PLACEHOLDER_MARKERS = [
12
+ "请把《PRD》",
13
+ "占位",
14
+ "placeholder",
15
+ ];
16
+ async function isPrdFetched(outputRoot) {
17
+ const prdPath = path_1.default.join(outputRoot, "source", "PRD.md");
18
+ const content = await (0, fs_1.readFileIfExists)(prdPath);
19
+ if (!content || content.trim().length < 80) {
20
+ return { ok: false, detail: "source/PRD.md 缺失或过短" };
21
+ }
22
+ if (PRD_PLACEHOLDER_MARKERS.some((m) => content.includes(m))) {
23
+ return { ok: false, detail: "source/PRD.md 仍为占位内容,请先执行 Step1 拉取 PRD" };
24
+ }
25
+ return { ok: true, detail: "PRD 已回填" };
26
+ }
27
+ async function isQuestionsClosed(outputRoot) {
28
+ const p = path_1.default.join(outputRoot, "03-待确认问题清单.md");
29
+ const content = await (0, fs_1.readFileIfExists)(p);
30
+ if (!content) {
31
+ return { ok: false, detail: "缺少 03-待确认问题清单.md" };
32
+ }
33
+ if (/矛盾点池状态[::]\s*[`「]*未闭合/.test(content)) {
34
+ return { ok: false, detail: "矛盾点池状态:未闭合" };
35
+ }
36
+ if (!/矛盾点池状态[::]\s*[`「]*已闭合/.test(content)) {
37
+ return { ok: false, detail: "03 未标注「矛盾点池状态:已闭合」" };
38
+ }
39
+ return { ok: true, detail: "矛盾点池已闭合" };
40
+ }
41
+ async function areAnalysisDocsReady(outputRoot) {
42
+ const files = ["01-需求分析报告.md", "02-改动点清单.md", "04-技术文档草稿.md"];
43
+ for (const name of files) {
44
+ const content = await (0, fs_1.readFileIfExists)(path_1.default.join(outputRoot, name));
45
+ if (!content || content.trim().length < 120) {
46
+ return { ok: false, detail: `${name} 缺失或过短(阶段 B 未就绪)` };
47
+ }
48
+ if (name === "01-需求分析报告.md" && content.includes("(由 AI 工具生成")) {
49
+ return { ok: false, detail: `${name} 仍为模板占位` };
50
+ }
51
+ }
52
+ return { ok: true, detail: "01/02/04 文档齐全" };
53
+ }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveInstallRoot = resolveInstallRoot;
7
+ exports.isLegacyMonorepoMode = isLegacyMonorepoMode;
8
+ exports.resolveOpenspecRoot = resolveOpenspecRoot;
9
+ exports.resolvePackageRoot = resolvePackageRoot;
10
+ exports.listOpenspecPrdRoots = listOpenspecPrdRoots;
11
+ const promises_1 = __importDefault(require("fs/promises"));
12
+ const path_1 = __importDefault(require("path"));
13
+ async function pathExists(filePath) {
14
+ try {
15
+ await promises_1.default.access(filePath);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ /**
23
+ * 安装根:严格等于 CLI 的 projectRoot(默认 process.cwd())。
24
+ * 可通过 SDD_FLOW_KIT_INSTALL_ROOT 覆盖(绝对路径)。
25
+ * 原则:cd 到哪,package.json / .cursor/skills / opsx-workflow 就装到哪。
26
+ */
27
+ function resolveInstallRoot(projectRoot) {
28
+ var _a;
29
+ const fromEnv = (_a = process.env.SDD_FLOW_KIT_INSTALL_ROOT) === null || _a === void 0 ? void 0 : _a.trim();
30
+ if (fromEnv)
31
+ return path_1.default.resolve(fromEnv);
32
+ return path_1.default.resolve(projectRoot);
33
+ }
34
+ /** 是否启用旧版 monorepo 行为(在仓库根执行时自动落到 frontend/) */
35
+ function isLegacyMonorepoMode() {
36
+ return process.env.SDD_FLOW_KIT_LEGACY_MONOREPO === "1" || process.env.SDD_FLOW_KIT_LEGACY_MONOREPO === "true";
37
+ }
38
+ /**
39
+ * 从 installRoot 向上查找含 openspec/ 的目录(用于解析 PRD run、openspec CLI)。
40
+ * 找不到则退回 installRoot(将在其下新建 openspec/PRD)。
41
+ */
42
+ async function resolveOpenspecRoot(installRoot) {
43
+ let cur = path_1.default.resolve(installRoot);
44
+ for (let i = 0; i < 12; i += 1) {
45
+ if (await pathExists(path_1.default.join(cur, "openspec"))) {
46
+ return cur;
47
+ }
48
+ const parent = path_1.default.dirname(cur);
49
+ if (parent === cur)
50
+ break;
51
+ cur = parent;
52
+ }
53
+ return path_1.default.resolve(installRoot);
54
+ }
55
+ /**
56
+ * 交付/门禁用的包根:默认与 installRoot 相同。
57
+ * 仅当 SDD_FLOW_KIT_LEGACY_MONOREPO=1 且 installRoot 无 package.json 但存在 frontend/package.json 时回落到 frontend/。
58
+ */
59
+ async function resolvePackageRoot(installRoot) {
60
+ const root = resolveInstallRoot(installRoot);
61
+ if (!isLegacyMonorepoMode()) {
62
+ return root;
63
+ }
64
+ const pkgAtRoot = path_1.default.join(root, "package.json");
65
+ if (await pathExists(pkgAtRoot)) {
66
+ return root;
67
+ }
68
+ const fePkg = path_1.default.join(root, "frontend", "package.json");
69
+ if (await pathExists(fePkg)) {
70
+ return path_1.default.join(root, "frontend");
71
+ }
72
+ return root;
73
+ }
74
+ /**
75
+ * 收集自 installRoot 向上所有存在的 openspec/PRD 目录(用于 resolveRun)。
76
+ */
77
+ async function listOpenspecPrdRoots(startDir) {
78
+ const seen = new Set();
79
+ const out = [];
80
+ let cur = path_1.default.resolve(startDir);
81
+ for (let i = 0; i < 12; i += 1) {
82
+ const prdRoot = path_1.default.join(cur, "openspec", "PRD");
83
+ const key = prdRoot.toLowerCase();
84
+ if (!seen.has(key) && (await pathExists(prdRoot))) {
85
+ seen.add(key);
86
+ out.push(prdRoot);
87
+ }
88
+ const parent = path_1.default.dirname(cur);
89
+ if (parent === cur)
90
+ break;
91
+ cur = parent;
92
+ }
93
+ return out;
94
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readSession = readSession;
7
+ exports.writeSession = writeSession;
8
+ exports.defaultSession = defaultSession;
9
+ exports.normalizeSession = normalizeSession;
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_1 = require("./fs");
12
+ const SESSION_FILE = ".session-state.json";
13
+ async function readSession(outputRoot) {
14
+ const raw = await (0, fs_1.readFileIfExists)(path_1.default.join(outputRoot, SESSION_FILE));
15
+ if (!raw)
16
+ return null;
17
+ return JSON.parse(raw);
18
+ }
19
+ async function writeSession(outputRoot, state) {
20
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, SESSION_FILE), `${JSON.stringify(state, null, 2)}\n`);
21
+ }
22
+ function defaultSession(args) {
23
+ return {
24
+ product: args.product,
25
+ version: args.version,
26
+ runId: args.runId,
27
+ outputRoot: args.outputRoot,
28
+ phase: "A",
29
+ status: "awaiting_user_confirmation",
30
+ docModeOnly: true,
31
+ autoChainOpsx: true,
32
+ activeChange: null,
33
+ createdAtISO: new Date().toISOString(),
34
+ implGitBaseline: null,
35
+ };
36
+ }
37
+ /** 将旧版 session(无 phase 字段)迁移为当前结构 */
38
+ function normalizeSession(raw, outputRoot) {
39
+ var _a, _b, _c, _d, _e;
40
+ const phase = raw.phase || "A";
41
+ const status = raw.status || "awaiting_user_confirmation";
42
+ return {
43
+ product: String((_a = raw.product) !== null && _a !== void 0 ? _a : ""),
44
+ version: String((_b = raw.version) !== null && _b !== void 0 ? _b : ""),
45
+ runId: String((_c = raw.runId) !== null && _c !== void 0 ? _c : ""),
46
+ outputRoot: String((_d = raw.outputRoot) !== null && _d !== void 0 ? _d : outputRoot),
47
+ phase,
48
+ status,
49
+ docModeOnly: raw.docModeOnly !== false,
50
+ autoChainOpsx: raw.autoChainOpsx !== false,
51
+ activeChange: raw.activeChange ? String(raw.activeChange) : null,
52
+ createdAtISO: String((_e = raw.createdAtISO) !== null && _e !== void 0 ? _e : new Date().toISOString()),
53
+ implGitBaseline: raw.implGitBaseline ? String(raw.implGitBaseline) : null,
54
+ };
55
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeInitialNext = writeInitialNext;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = require("./fs");
9
+ /**
10
+ * run 初始化后写入 NEXT.md,强制 AI 按脚本推进而非自然语言跳步。
11
+ */
12
+ async function writeInitialNext(outputRoot, projectRoot, runId) {
13
+ const pr = projectRoot.replace(/\\/g, "/");
14
+ const lines = [
15
+ "# NEXT(脚本门禁 — 必须按顺序执行)",
16
+ "",
17
+ "> **禁止**跳过下列命令。失败 (exit≠0) 时不得进入下一阶段或修改 `src/`。",
18
+ "",
19
+ "## 1. Step1 — 拉取 PRD",
20
+ "按 `01-获取需求文档指引.md` 执行 confluence 脚本,然后:",
21
+ "```bash",
22
+ `npx sdd-flow-kit gate --project-root "${pr}" --run-id ${runId} --expect prd-fetched`,
23
+ `npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to after-prd`,
24
+ "```",
25
+ "",
26
+ "## 2. Step2 — 需求分析(提问模式)",
27
+ "执行 `02-需求分析提示词.md`。未闭合时仅改 `03`,并确认:",
28
+ "```bash",
29
+ `npx sdd-flow-kit gate --project-root "${pr}" --run-id ${runId} --expect questions-open`,
30
+ "```",
31
+ "",
32
+ "## 3. 用户确认后 — 阶段 B(仅文档)",
33
+ "生成 01/02/04 后:",
34
+ "```bash",
35
+ `npx sdd-flow-kit gate --project-root "${pr}" --run-id ${runId} --expect docs-closed`,
36
+ `npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to docs-done`,
37
+ "```",
38
+ "",
39
+ "## 4. 阶段 C — propose",
40
+ "```bash",
41
+ `npx sdd-flow-kit propose --project-root "${pr}" --run-id ${runId} --change <kebab-name>`,
42
+ `npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to propose-done --change <kebab-name>`,
43
+ "```",
44
+ "",
45
+ "## 5. 阶段 D — 实现 + 交付",
46
+ "```bash",
47
+ `npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to impl --change <kebab-name>`,
48
+ `npx sdd-flow-kit deliver --project-root "${pr}" --run-id ${runId} --change <kebab-name>`,
49
+ `npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to shipped --change <kebab-name>`,
50
+ "```",
51
+ "",
52
+ ];
53
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "NEXT.md"), lines.join("\n"));
54
+ }
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toRunOptions = exports.parseInvokeText = exports.runDoctor = exports.setupProject = exports.runFlow = exports.startFlowScaffold = void 0;
3
+ exports.loadRunSession = exports.resolveRunDir = exports.runDeliver = exports.runPropose = exports.runPhaseAdvance = exports.printGateResult = exports.runGate = exports.toRunOptions = exports.parseInvokeText = exports.runDoctor = exports.setupProject = exports.runFlow = exports.startFlowScaffold = void 0;
4
4
  var startFlow_1 = require("./steps/startFlow");
5
5
  Object.defineProperty(exports, "startFlowScaffold", { enumerable: true, get: function () { return startFlow_1.startFlowScaffold; } });
6
6
  var runFlow_1 = require("./steps/runFlow");
@@ -12,3 +12,15 @@ Object.defineProperty(exports, "runDoctor", { enumerable: true, get: function ()
12
12
  var invokeFlow_1 = require("./steps/invokeFlow");
13
13
  Object.defineProperty(exports, "parseInvokeText", { enumerable: true, get: function () { return invokeFlow_1.parseInvokeText; } });
14
14
  Object.defineProperty(exports, "toRunOptions", { enumerable: true, get: function () { return invokeFlow_1.toRunOptions; } });
15
+ var runGate_1 = require("./steps/runGate");
16
+ Object.defineProperty(exports, "runGate", { enumerable: true, get: function () { return runGate_1.runGate; } });
17
+ Object.defineProperty(exports, "printGateResult", { enumerable: true, get: function () { return runGate_1.printGateResult; } });
18
+ var runPhaseAdvance_1 = require("./steps/runPhaseAdvance");
19
+ Object.defineProperty(exports, "runPhaseAdvance", { enumerable: true, get: function () { return runPhaseAdvance_1.runPhaseAdvance; } });
20
+ var runPropose_1 = require("./steps/runPropose");
21
+ Object.defineProperty(exports, "runPropose", { enumerable: true, get: function () { return runPropose_1.runPropose; } });
22
+ var runDeliver_1 = require("./steps/runDeliver");
23
+ Object.defineProperty(exports, "runDeliver", { enumerable: true, get: function () { return runDeliver_1.runDeliver; } });
24
+ var resolveRun_1 = require("./steps/resolveRun");
25
+ Object.defineProperty(exports, "resolveRunDir", { enumerable: true, get: function () { return resolveRun_1.resolveRunDir; } });
26
+ Object.defineProperty(exports, "loadRunSession", { enumerable: true, get: function () { return resolveRun_1.loadRunSession; } });
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveRunDir = resolveRunDir;
7
+ exports.loadRunSession = loadRunSession;
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const projectRoots_1 = require("../core/projectRoots");
11
+ const sessionState_1 = require("../core/sessionState");
12
+ const fs_1 = require("../core/fs");
13
+ /**
14
+ * 解析 openspec/PRD 目录:从 installRoot(cwd)向上查找;优先 --run-id,否则取最近修改的 run 目录。
15
+ */
16
+ async function resolveRunDir(projectRoot, runId) {
17
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(projectRoot);
18
+ const candidates = await (0, projectRoots_1.listOpenspecPrdRoots)(installRoot);
19
+ for (const prdRoot of candidates) {
20
+ try {
21
+ const entries = await promises_1.default.readdir(prdRoot, { withFileTypes: true });
22
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
23
+ if (dirs.length === 0)
24
+ continue;
25
+ if (runId) {
26
+ if (!dirs.includes(runId))
27
+ continue;
28
+ const outputRoot = path_1.default.join(prdRoot, runId);
29
+ return { runId, outputRoot, prdRoot };
30
+ }
31
+ let latest = null;
32
+ for (const name of dirs) {
33
+ const full = path_1.default.join(prdRoot, name);
34
+ const st = await promises_1.default.stat(full);
35
+ if (!latest || st.mtimeMs > latest.mtime) {
36
+ latest = { name, mtime: st.mtimeMs };
37
+ }
38
+ }
39
+ if (!latest)
40
+ continue;
41
+ const outputRoot = path_1.default.join(prdRoot, latest.name);
42
+ return { runId: latest.name, outputRoot, prdRoot };
43
+ }
44
+ catch {
45
+ // 目录不存在,尝试下一个候选
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ async function loadRunSession(projectRoot, runId) {
51
+ const resolved = await resolveRunDir(projectRoot, runId);
52
+ if (!resolved)
53
+ return null;
54
+ const raw = await (0, fs_1.readFileIfExists)(path_1.default.join(resolved.outputRoot, ".session-state.json"));
55
+ if (!raw)
56
+ return { resolved, session: null };
57
+ const parsed = JSON.parse(raw);
58
+ const session = (0, sessionState_1.normalizeSession)(parsed, resolved.outputRoot);
59
+ return { resolved, session };
60
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runDeliver = runDeliver;
7
+ const path_1 = __importDefault(require("path"));
8
+ const child_process_1 = require("child_process");
9
+ const resolveRun_1 = require("./resolveRun");
10
+ const runGate_1 = require("./runGate");
11
+ const opsxDelivery_1 = require("../core/opsxDelivery");
12
+ const projectRoots_1 = require("../core/projectRoots");
13
+ const fs_1 = require("../core/fs");
14
+ /**
15
+ * 实现阶段交付:先 gate impl-allowed,再 ensure-opsx,再跑 opsx:delivery-pipeline,写入 .delivery-pass.json。
16
+ */
17
+ async function runDeliver(options) {
18
+ var _a;
19
+ const loaded = await (0, resolveRun_1.loadRunSession)(options.projectRoot, options.runId);
20
+ if (!(loaded === null || loaded === void 0 ? void 0 : loaded.resolved)) {
21
+ return { ok: false, detail: "未找到 PRD run" };
22
+ }
23
+ const implGate = await (0, runGate_1.runGate)({
24
+ projectRoot: options.projectRoot,
25
+ runId: loaded.resolved.runId,
26
+ expect: "impl-allowed",
27
+ change: options.change,
28
+ });
29
+ if (!implGate.ok) {
30
+ (0, runGate_1.printGateResult)(implGate);
31
+ return { ok: false, detail: "impl-allowed gate 未通过" };
32
+ }
33
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
34
+ const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
35
+ const agent = options.agent || "cursor";
36
+ if (options.dryRun) {
37
+ return { ok: true, detail: `dry-run: would deliver in ${pkgRoot} (installRoot=${installRoot})` };
38
+ }
39
+ const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
40
+ projectRoot: pkgRoot,
41
+ agent,
42
+ dryRun: false,
43
+ });
44
+ if (!ensured.ready) {
45
+ return { ok: false, detail: "ensure-opsx 未就绪" };
46
+ }
47
+ let exitCode = 0;
48
+ if (!options.skipPipeline) {
49
+ const ret = (0, child_process_1.spawnSync)("pnpm", ["run", "opsx:delivery-pipeline", "--", options.change], {
50
+ cwd: pkgRoot,
51
+ stdio: "inherit",
52
+ shell: process.platform === "win32",
53
+ });
54
+ exitCode = (_a = ret.status) !== null && _a !== void 0 ? _a : 1;
55
+ }
56
+ const passPayload = {
57
+ change: options.change,
58
+ exitCode,
59
+ at: new Date().toISOString(),
60
+ projectRoot: pkgRoot,
61
+ };
62
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(loaded.resolved.outputRoot, ".delivery-pass.json"), `${JSON.stringify(passPayload, null, 2)}\n`);
63
+ if (exitCode !== 0) {
64
+ return { ok: false, detail: `delivery-pipeline 退出码 ${exitCode}` };
65
+ }
66
+ const shipped = await (0, runGate_1.runGate)({
67
+ projectRoot: options.projectRoot,
68
+ runId: loaded.resolved.runId,
69
+ expect: "shipped",
70
+ change: options.change,
71
+ });
72
+ (0, runGate_1.printGateResult)(shipped);
73
+ if (!shipped.ok) {
74
+ return { ok: false, detail: "pipeline 已跑但 shipped gate 未通过(检查 tasks.md)" };
75
+ }
76
+ return { ok: true, detail: "交付完成" };
77
+ }