sdd-flow-kit 1.2.3 → 1.3.0
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.md +71 -45
- package/dist/adapters/index.js +6 -6
- package/dist/cli.js +45 -9
- package/dist/core/acMatrix.js +116 -0
- package/dist/core/bootstrap.js +14 -6
- package/dist/core/inferProject.js +9 -2
- package/dist/core/openspecGate.js +45 -0
- package/dist/core/prdArtifacts.js +26 -0
- package/dist/core/projectRoots.js +79 -2
- package/dist/core/sessionState.js +2 -0
- package/dist/core/writeNext.js +19 -2
- package/dist/postinstall.js +22 -8
- package/dist/steps/runDeliver.js +18 -6
- package/dist/steps/runGate.js +97 -0
- package/dist/steps/runPhaseAdvance.js +53 -6
- package/dist/steps/runPropose.js +3 -1
- package/dist/steps/runValidate.js +204 -0
- package/dist/steps/setupProject.js +65 -2
- package/dist/steps/startFlow.js +14 -11
- package/package.json +1 -1
- package/src/templates/artifacts/01-/351/234/200/346/261/202/345/210/206/346/236/220/346/212/245/345/221/212.template.md +6 -1
- package/src/templates/artifacts/04-/346/212/200/346/234/257/346/226/207/346/241/243.template.md +272 -11
- package/src/templates/artifacts/05-/351/252/214/346/224/266/346/270/205/345/215/225.template.md +38 -0
- package/src/templates/artifacts/06-agent-skill.template.md +23 -9
- package/src/templates/artifacts/{05-openspec- → 06-openspec-}/346/217/220/346/241/210/350/215/211/347/250/277.template.md +16 -4
- package/src/templates/artifacts/{06-opsx-auto-chain.template.md → 07-opsx-auto-chain.template.md} +23 -9
- package/src/templates/prompts/02-sdd-loop-prompt.md +26 -9
- package/src/templates/rules/sdd-no-implement-until-apply.mdc.template +27 -8
- package/src/templates/skills/confluence-doc.py.template +25 -1
- package/src/templates/skills/doc-skill.SKILL.md.template +2 -0
- package/src/templates/skills/layered-testing-strategy.md +35 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.MAX_VALIDATE_RETRIES = void 0;
|
|
40
|
+
exports.runValidate = runValidate;
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const resolveRun_1 = require("./resolveRun");
|
|
44
|
+
const runGate_1 = require("./runGate");
|
|
45
|
+
const opsxDelivery_1 = require("../core/opsxDelivery");
|
|
46
|
+
const projectRoots_1 = require("../core/projectRoots");
|
|
47
|
+
const sessionState_1 = require("../core/sessionState");
|
|
48
|
+
const fs_1 = require("../core/fs");
|
|
49
|
+
const openspecGate_1 = require("../core/openspecGate");
|
|
50
|
+
/** 验收回退最大次数,超过需用户确认 */
|
|
51
|
+
exports.MAX_VALIDATE_RETRIES = 3;
|
|
52
|
+
/**
|
|
53
|
+
* 验收命令:跑 lint+type+unit(delivery-pipeline:short)+ e2e/Playwright,写入 .validate-pass.json 或 .validate-fail.json。
|
|
54
|
+
* 验收不通过时回退到 apply_in_progress,超过 MAX_VALIDATE_RETRIES 次则需用户确认。
|
|
55
|
+
*/
|
|
56
|
+
async function runValidate(options) {
|
|
57
|
+
var _a, _b, _c;
|
|
58
|
+
const loaded = await (0, resolveRun_1.loadRunSession)(options.projectRoot, options.runId);
|
|
59
|
+
if (!(loaded === null || loaded === void 0 ? void 0 : loaded.resolved)) {
|
|
60
|
+
return { ok: false, detail: "未找到 PRD run", needUserConfirm: false };
|
|
61
|
+
}
|
|
62
|
+
// 前置门禁:impl-allowed
|
|
63
|
+
const implGate = await (0, runGate_1.runGate)({
|
|
64
|
+
projectRoot: options.projectRoot,
|
|
65
|
+
runId: loaded.resolved.runId,
|
|
66
|
+
expect: "impl-allowed",
|
|
67
|
+
change: options.change,
|
|
68
|
+
});
|
|
69
|
+
if (!implGate.ok) {
|
|
70
|
+
(0, runGate_1.printGateResult)(implGate);
|
|
71
|
+
return { ok: false, detail: "impl-allowed gate 未通过,不能验收", needUserConfirm: false };
|
|
72
|
+
}
|
|
73
|
+
const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
|
|
74
|
+
const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
|
|
75
|
+
const agent = options.agent || "cursor";
|
|
76
|
+
if (options.dryRun) {
|
|
77
|
+
return { ok: true, detail: `dry-run: would validate in ${pkgRoot} (installRoot=${installRoot})`, needUserConfirm: false };
|
|
78
|
+
}
|
|
79
|
+
// 确保 opsx 环境就绪
|
|
80
|
+
const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({ projectRoot: pkgRoot, agent, dryRun: false });
|
|
81
|
+
if (!ensured.ready) {
|
|
82
|
+
return { ok: false, detail: "ensure-opsx 未就绪", needUserConfirm: false };
|
|
83
|
+
}
|
|
84
|
+
// ---- 检查1: tasks.md 全完成 ----
|
|
85
|
+
const tasks = await (0, openspecGate_1.checkTasksAllDone)(installRoot, options.change);
|
|
86
|
+
// ---- 检查2: delivery-pipeline:short(lint + type check + unit test) ----
|
|
87
|
+
const shortRet = (0, child_process_1.spawnSync)("pnpm", ["run", "opsx:delivery-pipeline:short", "--", options.change], {
|
|
88
|
+
cwd: pkgRoot,
|
|
89
|
+
stdio: "inherit",
|
|
90
|
+
shell: process.platform === "win32",
|
|
91
|
+
});
|
|
92
|
+
const shortExitCode = (_a = shortRet.status) !== null && _a !== void 0 ? _a : 1;
|
|
93
|
+
// ---- 检查3: e2e / Playwright ----
|
|
94
|
+
let e2eExitCode = 0;
|
|
95
|
+
let e2eDetail = "e2e 未执行";
|
|
96
|
+
const hasPlaywrightScript = await hasScript(pkgRoot, "test:e2e");
|
|
97
|
+
if (hasPlaywrightScript) {
|
|
98
|
+
const e2eRet = (0, child_process_1.spawnSync)("pnpm", ["run", "test:e2e"], {
|
|
99
|
+
cwd: pkgRoot,
|
|
100
|
+
stdio: "inherit",
|
|
101
|
+
shell: process.platform === "win32",
|
|
102
|
+
env: { ...process.env, CI: "1" },
|
|
103
|
+
});
|
|
104
|
+
e2eExitCode = (_b = e2eRet.status) !== null && _b !== void 0 ? _b : 1;
|
|
105
|
+
e2eDetail = e2eExitCode === 0 ? "e2e/Playwright 全通过" : `e2e/Playwright 退出码 ${e2eExitCode}`;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// 尝试直接用 npx playwright test
|
|
109
|
+
const hasPlaywrightCli = (0, child_process_1.spawnSync)("npx", ["playwright", "--version"], {
|
|
110
|
+
cwd: pkgRoot,
|
|
111
|
+
stdio: "ignore",
|
|
112
|
+
}).status === 0;
|
|
113
|
+
if (hasPlaywrightCli) {
|
|
114
|
+
const e2eRet = (0, child_process_1.spawnSync)("npx", ["playwright", "test"], {
|
|
115
|
+
cwd: pkgRoot,
|
|
116
|
+
stdio: "inherit",
|
|
117
|
+
shell: process.platform === "win32",
|
|
118
|
+
env: { ...process.env, CI: "1" },
|
|
119
|
+
});
|
|
120
|
+
e2eExitCode = (_c = e2eRet.status) !== null && _c !== void 0 ? _c : 1;
|
|
121
|
+
e2eDetail = e2eExitCode === 0 ? "Playwright test 全通过" : `Playwright test 退出码 ${e2eExitCode}`;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
e2eDetail = "未检测到 Playwright,跳过 e2e(建议配置 test:e2e 脚本)";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ---- 综合判定 ----
|
|
128
|
+
const allPassed = tasks.ok && shortExitCode === 0 && e2eExitCode === 0;
|
|
129
|
+
const verdict = allPassed ? "pass" : "fail";
|
|
130
|
+
const payload = {
|
|
131
|
+
change: options.change,
|
|
132
|
+
verdict,
|
|
133
|
+
tasksOk: tasks.ok,
|
|
134
|
+
tasksDetail: tasks.detail,
|
|
135
|
+
pipelineShortExitCode: shortExitCode,
|
|
136
|
+
e2eExitCode,
|
|
137
|
+
e2eDetail,
|
|
138
|
+
at: new Date().toISOString(),
|
|
139
|
+
};
|
|
140
|
+
const markerFile = verdict === "pass" ? ".validate-pass.json" : ".validate-fail.json";
|
|
141
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(loaded.resolved.outputRoot, markerFile), `${JSON.stringify(payload, null, 2)}\n`);
|
|
142
|
+
// ---- 更新 session ----
|
|
143
|
+
let needUserConfirm = false;
|
|
144
|
+
if (loaded.session) {
|
|
145
|
+
if (verdict === "pass") {
|
|
146
|
+
const next = {
|
|
147
|
+
...loaded.session,
|
|
148
|
+
status: "validate_done",
|
|
149
|
+
};
|
|
150
|
+
await (0, sessionState_1.writeSession)(loaded.resolved.outputRoot, next);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// 验收失败:回退计数 +1
|
|
154
|
+
const newRetryCount = (loaded.session.validateRetryCount || 0) + 1;
|
|
155
|
+
needUserConfirm = newRetryCount > exports.MAX_VALIDATE_RETRIES;
|
|
156
|
+
const next = {
|
|
157
|
+
...loaded.session,
|
|
158
|
+
status: "apply_in_progress",
|
|
159
|
+
validateRetryCount: newRetryCount,
|
|
160
|
+
};
|
|
161
|
+
await (0, sessionState_1.writeSession)(loaded.resolved.outputRoot, next);
|
|
162
|
+
// 清理上次验收标记(重新实现后需要重新验收)
|
|
163
|
+
await cleanupValidateMarkers(loaded.resolved.outputRoot);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (verdict === "fail") {
|
|
167
|
+
const retryInfo = loaded.session
|
|
168
|
+
? `(第 ${loaded.session.validateRetryCount + 1} 次验收失败${needUserConfirm ? ",已达上限,需用户确认后继续" : ""})`
|
|
169
|
+
: "";
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
detail: `验收失败: tasks=${tasks.detail}, pipeline=${shortExitCode}, e2e=${e2eDetail} ${retryInfo}`,
|
|
173
|
+
needUserConfirm,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return { ok: true, detail: "验收通过(lint+type+unit+e2e),可进入交付", needUserConfirm: false };
|
|
177
|
+
}
|
|
178
|
+
/** 检查 package.json 是否有指定 script */
|
|
179
|
+
async function hasScript(pkgRoot, scriptName) {
|
|
180
|
+
var _a;
|
|
181
|
+
try {
|
|
182
|
+
const { readFile: readFileFs } = await Promise.resolve().then(() => __importStar(require("fs/promises")));
|
|
183
|
+
const pkgText = await readFileFs(path_1.default.join(pkgRoot, "package.json"), "utf8");
|
|
184
|
+
const pkg = JSON.parse(pkgText);
|
|
185
|
+
return Boolean((_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a[scriptName]);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** 清理验收标记文件,重新实现后需要重新验收 */
|
|
192
|
+
async function cleanupValidateMarkers(outputRoot) {
|
|
193
|
+
const { unlink } = await Promise.resolve().then(() => __importStar(require("fs/promises")));
|
|
194
|
+
for (const f of [".validate-pass.json", ".validate-fail.json"]) {
|
|
195
|
+
const p = path_1.default.join(outputRoot, f);
|
|
196
|
+
const content = await (0, fs_1.readFileIfExists)(p);
|
|
197
|
+
if (content !== null) {
|
|
198
|
+
try {
|
|
199
|
+
await unlink(p);
|
|
200
|
+
}
|
|
201
|
+
catch { /* ignore */ }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.setupProject = setupProject;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const process_1 = __importDefault(require("process"));
|
|
10
11
|
const fs_1 = require("../core/fs");
|
|
@@ -15,7 +16,7 @@ const packageJsonPatch_1 = require("../core/packageJsonPatch");
|
|
|
15
16
|
const projectRoots_1 = require("../core/projectRoots");
|
|
16
17
|
const templates_1 = require("../core/templates");
|
|
17
18
|
const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
|
|
18
|
-
const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools"];
|
|
19
|
+
const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools", "BreakX"];
|
|
19
20
|
function runGit(args, cwd) {
|
|
20
21
|
var _a, _b;
|
|
21
22
|
const out = (0, child_process_1.spawnSync)("git", args, { cwd, encoding: "utf8" });
|
|
@@ -87,6 +88,7 @@ async function askProjectKind(defaultKind) {
|
|
|
87
88
|
"2) ADI",
|
|
88
89
|
"3) 欢盟",
|
|
89
90
|
"4) AD Tools",
|
|
91
|
+
"5) BreakX",
|
|
90
92
|
"输入编号(回车默认 1):",
|
|
91
93
|
].join("\n");
|
|
92
94
|
const answer = (await (0, readlinePrompt_1.promptLine)(rl, `${prompt}\n`)).trim();
|
|
@@ -131,6 +133,7 @@ async function installAgentSkill(projectRoot, projectKind) {
|
|
|
131
133
|
if (targets.length === 0)
|
|
132
134
|
return;
|
|
133
135
|
const template = await (0, templates_1.loadTemplateText)("artifacts/06-agent-skill.template.md");
|
|
136
|
+
const layered = await (0, templates_1.loadTemplateText)("skills/layered-testing-strategy.md");
|
|
134
137
|
for (const target of targets) {
|
|
135
138
|
const agentName = target.includes("/.cursor/")
|
|
136
139
|
? "cursor"
|
|
@@ -145,6 +148,8 @@ async function installAgentSkill(projectRoot, projectKind) {
|
|
|
145
148
|
DEFAULT_PRODUCT: projectKind === "AD Tools" ? "AD Tools" : projectKind,
|
|
146
149
|
});
|
|
147
150
|
await (0, fs_1.writeTextFileEnsuredDir)(target, content);
|
|
151
|
+
const layeredPath = path_1.default.join(path_1.default.dirname(target), "LAYERED-TESTING.md");
|
|
152
|
+
await (0, fs_1.writeTextFileEnsuredDir)(layeredPath, layered);
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
155
|
async function installSddCursorRule(projectRoot) {
|
|
@@ -167,6 +172,8 @@ function docSkillDirName(kind) {
|
|
|
167
172
|
return { dir: "huan-doc-skill", prefix: "HUAN" };
|
|
168
173
|
case "AD Tools":
|
|
169
174
|
return { dir: "ad-tools-doc-skill", prefix: "ADTOOLS" };
|
|
175
|
+
case "BreakX":
|
|
176
|
+
return { dir: "breakx-doc-skill", prefix: "BreakX" };
|
|
170
177
|
case "OMS":
|
|
171
178
|
default:
|
|
172
179
|
return { dir: "oms-doc-skill", prefix: "OMS" };
|
|
@@ -183,12 +190,67 @@ async function installDocsSkill(projectRoot, kind) {
|
|
|
183
190
|
PRODUCT_PREFIX: prefix,
|
|
184
191
|
PRODUCT_PREFIX_LOWER: prefix.toLowerCase(),
|
|
185
192
|
SKILL_DIR: dir,
|
|
193
|
+
CONFLUENCE_SPACE_NOTE: kind === "BreakX"
|
|
194
|
+
? "- BreakX PRD 位于 Confluence **xhs** 空间,页面标题通常以 `Vx.y.z` 开头(如 `V1.0.2 审稿子任务...`)\n- 脚本默认 `CONFLUENCE_SPACE=xhs`,优先匹配该空间下的页面"
|
|
195
|
+
: "",
|
|
186
196
|
});
|
|
187
197
|
const py = (0, utils_1.replaceAllStr)(pyTemplate, "{{DOC_PRODUCT_PREFIX}}", prefix);
|
|
188
198
|
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(docsDir, "SKILL.md"), md);
|
|
189
199
|
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(docsDir, "confluence-doc.py"), py);
|
|
190
200
|
return { dirPath: docsDir };
|
|
191
201
|
}
|
|
202
|
+
function parseDevPortFromScript(script) {
|
|
203
|
+
var _a, _b;
|
|
204
|
+
// Common patterns:
|
|
205
|
+
// - vite --port 3000
|
|
206
|
+
// - next dev -p 3000
|
|
207
|
+
// - next dev --port 3000
|
|
208
|
+
// - nuxt dev -p 3000
|
|
209
|
+
const m = (_b = (_a = script.match(/(?:--port|-p)\s+(\d{2,5})\b/)) !== null && _a !== void 0 ? _a : script.match(/\bPORT=(\d{2,5})\b/)) !== null && _b !== void 0 ? _b : null;
|
|
210
|
+
if (!m)
|
|
211
|
+
return null;
|
|
212
|
+
const port = Number(m[1]);
|
|
213
|
+
if (!Number.isFinite(port) || port < 1 || port > 65535)
|
|
214
|
+
return null;
|
|
215
|
+
return port;
|
|
216
|
+
}
|
|
217
|
+
async function resolveDefaultDevServerUrl(projectRoot) {
|
|
218
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
219
|
+
const envUrl = ((_a = process_1.default.env.SDD_DEV_SERVER_URL) === null || _a === void 0 ? void 0 : _a.trim()) ||
|
|
220
|
+
((_b = process_1.default.env.OPSX_DEV_SERVER_URL) === null || _b === void 0 ? void 0 : _b.trim()) ||
|
|
221
|
+
((_c = process_1.default.env.DEV_SERVER_URL) === null || _c === void 0 ? void 0 : _c.trim()) ||
|
|
222
|
+
"";
|
|
223
|
+
if (envUrl)
|
|
224
|
+
return envUrl;
|
|
225
|
+
const envPortRaw = ((_d = process_1.default.env.SDD_DEV_SERVER_PORT) === null || _d === void 0 ? void 0 : _d.trim()) ||
|
|
226
|
+
((_e = process_1.default.env.OPSX_DEV_SERVER_PORT) === null || _e === void 0 ? void 0 : _e.trim()) ||
|
|
227
|
+
((_f = process_1.default.env.DEV_SERVER_PORT) === null || _f === void 0 ? void 0 : _f.trim()) ||
|
|
228
|
+
"";
|
|
229
|
+
if (envPortRaw) {
|
|
230
|
+
const port = Number(envPortRaw);
|
|
231
|
+
if (Number.isFinite(port) && port >= 1 && port <= 65535) {
|
|
232
|
+
return `http://localhost:${port}`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const pkgText = await promises_1.default.readFile(path_1.default.join(projectRoot, "package.json"), "utf8");
|
|
237
|
+
const pkg = JSON.parse(pkgText);
|
|
238
|
+
const devScript = (_h = (_g = pkg.scripts) === null || _g === void 0 ? void 0 : _g.dev) !== null && _h !== void 0 ? _h : "";
|
|
239
|
+
const parsedPort = devScript ? parseDevPortFromScript(devScript) : null;
|
|
240
|
+
if (parsedPort)
|
|
241
|
+
return `http://localhost:${parsedPort}`;
|
|
242
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
243
|
+
// Heuristics: Next.js defaults to 3000; Vite defaults to 5173.
|
|
244
|
+
if (deps.next)
|
|
245
|
+
return "http://localhost:3000";
|
|
246
|
+
if (deps.vite)
|
|
247
|
+
return "http://localhost:5173";
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// ignore and fallback
|
|
251
|
+
}
|
|
252
|
+
return "http://localhost:5173";
|
|
253
|
+
}
|
|
192
254
|
function getOpsxPathsByAgent(agent) {
|
|
193
255
|
switch (agent) {
|
|
194
256
|
case "cursor":
|
|
@@ -207,6 +269,7 @@ async function installOpsxConfig(projectRoot, agent) {
|
|
|
207
269
|
const target = path_1.default.join(projectRoot, ".opsx", "config.json");
|
|
208
270
|
const resolvedAgent = await (0, opsxDelivery_1.resolveOpsxAgent)(projectRoot, agent);
|
|
209
271
|
const paths = getOpsxPathsByAgent(resolvedAgent);
|
|
272
|
+
const devServerUrl = await resolveDefaultDevServerUrl(projectRoot);
|
|
210
273
|
const config = {
|
|
211
274
|
version: 1,
|
|
212
275
|
editor: paths.editor,
|
|
@@ -217,7 +280,7 @@ async function installOpsxConfig(projectRoot, agent) {
|
|
|
217
280
|
packageManager: "pnpm",
|
|
218
281
|
unitScript: "unit",
|
|
219
282
|
playwrightCommand: "exec playwright test",
|
|
220
|
-
devServerUrl
|
|
283
|
+
devServerUrl,
|
|
221
284
|
playwrightPicDir: "playwright-pic",
|
|
222
285
|
},
|
|
223
286
|
};
|
package/dist/steps/startFlow.js
CHANGED
|
@@ -69,17 +69,19 @@ async function startFlowScaffold(options) {
|
|
|
69
69
|
const file01Template = await (0, templates_1.loadTemplateText)("artifacts/01-需求分析报告.template.md");
|
|
70
70
|
const file02Template = await (0, templates_1.loadTemplateText)("artifacts/02-改动点清单.template.md");
|
|
71
71
|
const file03Template = await (0, templates_1.loadTemplateText)("artifacts/03-待确认问题清单.template.md");
|
|
72
|
+
const file05Template = await (0, templates_1.loadTemplateText)("artifacts/05-验收清单.template.md");
|
|
72
73
|
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "01-需求分析报告.md"), file01Template);
|
|
73
74
|
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "02-改动点清单.md"), file02Template);
|
|
74
75
|
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "03-待确认问题清单.md"), file03Template);
|
|
76
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "05-验收清单.md"), file05Template);
|
|
75
77
|
// 4) Step4 技术文档入口(草稿)
|
|
76
78
|
const techDocTemplate = await (0, templates_1.loadTemplateText)("artifacts/04-技术文档.template.md");
|
|
77
79
|
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "04-技术文档草稿.md"), techDocTemplate);
|
|
78
80
|
// 5) Step5 openspec 提案入口(草稿)
|
|
79
|
-
const openspecEntryTemplate = await (0, templates_1.loadTemplateText)("artifacts/
|
|
80
|
-
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "
|
|
81
|
-
const opsxAutoChainTemplate = await (0, templates_1.loadTemplateText)("artifacts/
|
|
82
|
-
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "
|
|
81
|
+
const openspecEntryTemplate = await (0, templates_1.loadTemplateText)("artifacts/06-openspec-提案草稿.template.md");
|
|
82
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "06-openspec-提案草稿.md"), openspecEntryTemplate);
|
|
83
|
+
const opsxAutoChainTemplate = await (0, templates_1.loadTemplateText)("artifacts/07-opsx-auto-chain.template.md");
|
|
84
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "07-opsx-自动串联指引.md"), opsxAutoChainTemplate);
|
|
83
85
|
const session = (0, sessionState_1.defaultSession)({
|
|
84
86
|
product: options.product,
|
|
85
87
|
version: options.version,
|
|
@@ -104,21 +106,22 @@ async function startFlowScaffold(options) {
|
|
|
104
106
|
"",
|
|
105
107
|
"## Step 3:人工核验(暂停点)",
|
|
106
108
|
"用户补充待确认点并确认闭环后,才进入 Step 4。",
|
|
107
|
-
"门禁:未闭环时禁止生成 01/02/04,禁止 /opsx-propose。",
|
|
109
|
+
"门禁:未闭环时禁止生成 01/02/04/05,禁止 /opsx-propose。",
|
|
108
110
|
"**重要**:用户粘贴确认点时,AI 仅更新 `openspec/PRD` 文档,**禁止**修改 `src/` 等业务代码。",
|
|
109
111
|
"",
|
|
110
|
-
"## Step 4:生成 01 / 02 / 闭环03
|
|
111
|
-
"
|
|
112
|
+
"## Step 4:生成 05 → 04 → 01 / 02 / 闭环03",
|
|
113
|
+
"用户确认后产出(顺序重要):",
|
|
112
114
|
"- `03-待确认问题清单.md`(已闭合)",
|
|
113
|
-
"- `
|
|
115
|
+
"- `05-验收清单.md`(从 `source/PRD.md` 逐条抽取 AC,标注「定义状态:已定义」)",
|
|
116
|
+
"- `04-技术文档草稿.md`(引用 AC-ID)",
|
|
117
|
+
"- `01-需求分析报告.md`(业务概述,非验收 SSOT)",
|
|
114
118
|
"- `02-改动点清单.md`",
|
|
115
|
-
"- `04-技术文档草稿.md`",
|
|
116
119
|
"",
|
|
117
|
-
"## Step 5–
|
|
120
|
+
"## Step 5–7:脚本门禁(禁止仅靠自然语言跳步)",
|
|
118
121
|
"每一步必须先执行 `NEXT.md` 中的 `npx sdd-flow-kit gate` / `phase advance` / `propose` / `deliver`。",
|
|
119
122
|
"命令失败 (exit≠0) 时 **不得** 改 `src/` 或宣称完成。",
|
|
120
123
|
"",
|
|
121
|
-
"详见:`NEXT.md`、`
|
|
124
|
+
"详见:`NEXT.md`、`07-opsx-自动串联指引.md`、`.cursor/skills/sdd-flow-kit/SKILL.md`。",
|
|
122
125
|
].join("\n"));
|
|
123
126
|
return {
|
|
124
127
|
runId,
|
package/package.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# 01-需求分析报告
|
|
2
2
|
|
|
3
|
+
> **定位**:业务概述、范围、风险与确认点摘要。
|
|
4
|
+
> **不是**完整验收标准 — implement/deliver 的唯一验收 SSOT 为 `05-验收清单.md`。
|
|
5
|
+
|
|
3
6
|
## 业务概述
|
|
4
7
|
- (在此处总结 PRD 核心目标)
|
|
5
8
|
|
|
@@ -16,7 +19,9 @@
|
|
|
16
19
|
- 兼容性问题:
|
|
17
20
|
- 副作用分析:
|
|
18
21
|
|
|
22
|
+
## 已闭合确认点摘要
|
|
23
|
+
- (引用 `03-待确认问题清单.md` 中的结论;Intentional 偏差须同步到 05)
|
|
24
|
+
|
|
19
25
|
---
|
|
20
26
|
|
|
21
27
|
> 矛盾点池状态:待填写(提问模式/交付模式)
|
|
22
|
-
|