sdd-flow-kit 1.3.6 → 1.3.11

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,300 @@
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.MAX_REMEDIATE_ROUNDS = exports.MAX_PRD_FIX_RETRIES = void 0;
7
+ exports.collectRemediableItems = collectRemediableItems;
8
+ exports.buildRemediatePrompt = buildRemediatePrompt;
9
+ exports.writeRemediateBrief = writeRemediateBrief;
10
+ exports.escalateFailuresToHumanConfirm = escalateFailuresToHumanConfirm;
11
+ exports.runPrdRemediateLoop = runPrdRemediateLoop;
12
+ const path_1 = __importDefault(require("path"));
13
+ const fs_1 = require("./fs");
14
+ const agentRemediateInvoke_1 = require("./agentRemediateInvoke");
15
+ const prdRemediateState_1 = require("./prdRemediateState");
16
+ /** 每项 PRD 原子最多自动修复次数 */
17
+ exports.MAX_PRD_FIX_RETRIES = 3;
18
+ /** 防止单项轮换时无限循环的安全上限(全局 agent 调用轮数) */
19
+ exports.MAX_REMEDIATE_ROUNDS = 50;
20
+ /** 需要从代码/测试层修复的语义 diff 条目 */
21
+ function collectRemediableItems(semantic) {
22
+ if (!semantic)
23
+ return [];
24
+ return semantic.items.filter((i) => {
25
+ if (i.reportVerdict === "待人工确认")
26
+ return false;
27
+ return (i.status === "missing_in_code" ||
28
+ i.status === "partial" ||
29
+ i.status === "report_mismatch" ||
30
+ (i.requiresAutomatedTest && !i.hasAutomatedTest) ||
31
+ (i.requiresAutomatedTest && !i.hasE2eTest));
32
+ });
33
+ }
34
+ function formatFailureHistoryBlock(atomId, state, maxPerItem) {
35
+ const itemState = state.items[atomId];
36
+ if (!itemState || itemState.history.length === 0)
37
+ return "";
38
+ const lines = [
39
+ "",
40
+ "**上次修复失败摘要(勿重复同样错误):**",
41
+ ];
42
+ for (const h of itemState.history) {
43
+ lines.push(`- 第 ${h.round} 轮尝试 (${h.at})`);
44
+ lines.push(` - 状态: \`${h.status}\``);
45
+ lines.push(` - 错误描述: ${h.detail}`);
46
+ if (h.missingSignals.length > 0) {
47
+ lines.push(` - 仍缺: ${h.missingSignals.join(";")}`);
48
+ }
49
+ lines.push(` - 当时错误代码/证据:`);
50
+ lines.push("```");
51
+ lines.push(h.wrongCode);
52
+ lines.push("```");
53
+ }
54
+ const remaining = maxPerItem - itemState.attempts;
55
+ if (remaining > 0) {
56
+ lines.push(`> 本项剩余自动修复次数: ${remaining}`);
57
+ }
58
+ return lines.join("\n");
59
+ }
60
+ function formatRemediateItemBlock(item, state, maxPerItem) {
61
+ const attemptNo = (0, prdRemediateState_1.getItemAttempts)(state, item.atomId) + 1;
62
+ const lines = [
63
+ `### ${item.atomId}(本项第 ${attemptNo}/${maxPerItem} 次)`,
64
+ `- 状态: \`${item.status}\``,
65
+ `- PRD: ${item.atomText.slice(0, 200)}`,
66
+ `- 问题: ${item.detail}`,
67
+ ];
68
+ if (item.missingSignals.length > 0) {
69
+ lines.push(`- 缺失信号: ${item.missingSignals.join(";")}`);
70
+ }
71
+ lines.push("- 当前错误代码/证据:");
72
+ lines.push("```");
73
+ lines.push((0, prdRemediateState_1.formatWrongCodeSnippet)(item));
74
+ lines.push("```");
75
+ lines.push(formatFailureHistoryBlock(item.atomId, state, maxPerItem));
76
+ return lines.join("\n");
77
+ }
78
+ function buildRemediatePrompt(args) {
79
+ const itemBlocks = args.failedItems
80
+ .slice(0, 30)
81
+ .map((i) => formatRemediateItemBlock(i, args.remediateState, args.maxPerItem))
82
+ .join("\n\n");
83
+ return [
84
+ `你在项目内执行 PRD 不一致自动修复(runId=${args.runId},change=${args.change},全局第 ${args.round} 轮)。`,
85
+ `本轮须修复 **${args.failedItems.length}** 项;**每项最多自动修复 ${args.maxPerItem} 次**,失败后须根据「上次修复失败摘要」换思路重试。`,
86
+ "",
87
+ "## 强制规则",
88
+ "1. **以 PRD 为准改代码**,禁止改 PRD 迁就实现。",
89
+ "2. 必读:`source/PRD.md`、`09-PRD语义diff报告.md`、`05-验收清单.md`。",
90
+ "3. 执行 **/opsx-apply** 工作流(openspec-apply-change → tdd-script → delivery-pipeline 相关 skill)。",
91
+ "4. 为每条失败项补代码 + 测试(e2e/ 须引用 `PRD-XXX` 或 PRD 字面量断言)。",
92
+ "5. **禁止揣摩、禁止兜底**;无法确定时不要改,留给人工确认。",
93
+ "6. 修复后更新 `08-PRD一致性复查报告.md` 对应行(证据路径+行号)。",
94
+ "7. **重试项必须先阅读下方「上次修复失败摘要」**,避免重复同样错误实现。",
95
+ "",
96
+ "## 本次须修复条目(仅剩余失败项;含错误代码与历史摘要)",
97
+ itemBlocks || "(无条目)",
98
+ "",
99
+ "## 完成判定",
100
+ "全部可机械修复项已按 PRD 改完且可重新通过 prd-diff 时,输出:`PRD_REMEDIATE=COMPLETE`",
101
+ "若本轮无法修复任何项,输出:`PRD_REMEDIATE=PARTIAL`",
102
+ ].join("\n");
103
+ }
104
+ async function writeRemediateBrief(outputRoot, round, prompt, agent) {
105
+ const hint = (0, agentRemediateInvoke_1.agentRemediateHint)(agent);
106
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, `10-PRD自动修复指引-第${round}轮.md`), [
107
+ `# PRD 自动修复指引(第 ${round} 轮)`,
108
+ "",
109
+ "> 由 `sdd-flow-kit prd-remediate` 根据 `09-PRD语义diff报告.md` 生成。",
110
+ "> 每项最多 3 次;重试时会附带上次错误代码与描述摘要。",
111
+ `> ${hint}`,
112
+ "",
113
+ prompt,
114
+ ].join("\n"));
115
+ }
116
+ function replaceTableRowVerdict(content, prdId, verdict, noteSuffix) {
117
+ const lines = content.split("\n");
118
+ return lines
119
+ .map((line) => {
120
+ var _a, _b;
121
+ const trimmed = line.trim();
122
+ if (!trimmed.startsWith("|"))
123
+ return line;
124
+ const cells = trimmed
125
+ .split("|")
126
+ .map((c) => c.trim())
127
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
128
+ if (((_a = cells[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== prdId)
129
+ return line;
130
+ if (cells.length < 5)
131
+ return line;
132
+ cells[3] = verdict;
133
+ const note = (_b = cells[4]) !== null && _b !== void 0 ? _b : "";
134
+ if (!note.includes(noteSuffix)) {
135
+ cells[4] = `${note} ${noteSuffix}`.trim();
136
+ }
137
+ return `| ${cells.join(" | ")} |`;
138
+ })
139
+ .join("\n");
140
+ }
141
+ /** 单项达上限仍失败后,写入 08 待人工确认汇总 */
142
+ async function escalateFailuresToHumanConfirm(outputRoot, items, maxPerItem) {
143
+ var _a;
144
+ const reportPath = path_1.default.join(outputRoot, "08-PRD一致性复查报告.md");
145
+ let content = (_a = (await (0, fs_1.readFileIfExists)(reportPath))) !== null && _a !== void 0 ? _a : "";
146
+ if (!content)
147
+ return;
148
+ const reason = `单项自动修复 ${maxPerItem} 次仍未通过 prd-diff(${new Date().toISOString()})`;
149
+ for (const item of items) {
150
+ content = replaceTableRowVerdict(content, item.atomId, "待人工确认", "| escalated-by-prd-remediate");
151
+ }
152
+ content = content.replace(/- \*\*复查状态\*\*[::].*/, `- **复查状态**:\`待人工确认(单项自动修复${maxPerItem}次未果)\``);
153
+ const newRows = items
154
+ .map((i) => `| ${i.atomId} | ${i.atomText.replace(/\|/g, "/").slice(0, 80)} | ${reason};${i.detail.replace(/\|/g, "/")} | 产品/需求负责人 |`)
155
+ .join("\n");
156
+ if (content.includes("| (无则写「无」) |")) {
157
+ content = content.replace("| (无则写「无」) | | | |", newRows);
158
+ }
159
+ else {
160
+ content = content.replace("## 待人工确认汇总", `## 待人工确认汇总\n\n${newRows}`);
161
+ }
162
+ await (0, fs_1.writeTextFileEnsuredDir)(reportPath, content);
163
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, ".prd-remediate-escalated.json"), `${JSON.stringify({
164
+ maxPerItem,
165
+ at: new Date().toISOString(),
166
+ atomIds: items.map((i) => i.atomId),
167
+ reason,
168
+ }, null, 2)}\n`);
169
+ }
170
+ function updateStateAfterRound(state, targetedIds, afterFailed, round) {
171
+ const afterMap = new Map(afterFailed.map((i) => [i.atomId, i]));
172
+ for (const atomId of targetedIds) {
173
+ const still = afterMap.get(atomId);
174
+ if (!still) {
175
+ (0, prdRemediateState_1.clearItemFromState)(state, atomId);
176
+ }
177
+ else {
178
+ (0, prdRemediateState_1.recordItemFailure)(state, still, round);
179
+ }
180
+ }
181
+ }
182
+ /**
183
+ * 失败 → /opsx-apply 按 09 修 → prd-diff。
184
+ * 每项最多 maxAttempts 次;每轮只修剩余失败项;重试时附带上次错误代码与描述摘要。
185
+ */
186
+ async function runPrdRemediateLoop(options) {
187
+ var _a;
188
+ const maxPerItem = (_a = options.maxAttempts) !== null && _a !== void 0 ? _a : exports.MAX_PRD_FIX_RETRIES;
189
+ const agent = await (0, agentRemediateInvoke_1.resolveRemediateAgent)(options.projectRoot, options.agent);
190
+ let diff = await options.runDiff();
191
+ if (diff.ok) {
192
+ await (0, prdRemediateState_1.clearRemediateState)(options.outputRoot);
193
+ return { ok: true, detail: "PRD 语义 diff 已通过,无需修复", attempts: 0, escalated: false, needUserConfirm: false };
194
+ }
195
+ if (options.dryRun) {
196
+ const failed = collectRemediableItems(diff.semantic);
197
+ return {
198
+ ok: false,
199
+ detail: `dry-run: would remediate ${failed.length} items (max ${maxPerItem} attempts each)`,
200
+ attempts: 0,
201
+ escalated: false,
202
+ needUserConfirm: false,
203
+ };
204
+ }
205
+ let state = await (0, prdRemediateState_1.loadRemediateState)(options.outputRoot);
206
+ let round = 0;
207
+ while (round < exports.MAX_REMEDIATE_ROUNDS) {
208
+ const failed = collectRemediableItems(diff.semantic);
209
+ if (failed.length === 0) {
210
+ break;
211
+ }
212
+ const eligible = (0, prdRemediateState_1.filterEligibleItems)(failed, state, maxPerItem);
213
+ const exhausted = (0, prdRemediateState_1.filterExhaustedItems)(failed, state, maxPerItem);
214
+ if (eligible.length === 0) {
215
+ if (exhausted.length > 0) {
216
+ await escalateFailuresToHumanConfirm(options.outputRoot, exhausted, maxPerItem);
217
+ return {
218
+ ok: false,
219
+ detail: `${exhausted.length} 项已达单项 ${maxPerItem} 次上限仍未通过,已写入 08 待人工确认汇总`,
220
+ attempts: round,
221
+ escalated: true,
222
+ needUserConfirm: true,
223
+ };
224
+ }
225
+ break;
226
+ }
227
+ round += 1;
228
+ const targetedIds = new Set(eligible.map((i) => i.atomId));
229
+ const prompt = buildRemediatePrompt({
230
+ runId: options.runId,
231
+ change: options.change,
232
+ round,
233
+ maxPerItem,
234
+ outputRoot: options.outputRoot,
235
+ failedItems: eligible,
236
+ remediateState: state,
237
+ });
238
+ await writeRemediateBrief(options.outputRoot, round, prompt, agent);
239
+ await (0, agentRemediateInvoke_1.invokeAgentOpsxRemediate)({
240
+ agent,
241
+ projectRoot: options.projectRoot,
242
+ outputRoot: options.outputRoot,
243
+ prompt,
244
+ attempt: round,
245
+ });
246
+ diff = await options.runDiff();
247
+ const afterFailed = collectRemediableItems(diff.semantic);
248
+ updateStateAfterRound(state, targetedIds, afterFailed, round);
249
+ await (0, prdRemediateState_1.saveRemediateState)(options.outputRoot, state);
250
+ if (diff.ok) {
251
+ await (0, prdRemediateState_1.clearRemediateState)(options.outputRoot);
252
+ return {
253
+ ok: true,
254
+ detail: `PRD 自动修复成功(${agent},${round} 轮后 prd-diff 通过)`,
255
+ attempts: round,
256
+ escalated: false,
257
+ needUserConfirm: false,
258
+ };
259
+ }
260
+ const newlyExhausted = (0, prdRemediateState_1.filterExhaustedItems)(afterFailed, state, maxPerItem);
261
+ if (newlyExhausted.length > 0 && (0, prdRemediateState_1.filterEligibleItems)(afterFailed, state, maxPerItem).length === 0) {
262
+ await escalateFailuresToHumanConfirm(options.outputRoot, newlyExhausted, maxPerItem);
263
+ return {
264
+ ok: false,
265
+ detail: `${newlyExhausted.length} 项已达单项 ${maxPerItem} 次上限仍未通过,已写入 08 待人工确认汇总`,
266
+ attempts: round,
267
+ escalated: true,
268
+ needUserConfirm: true,
269
+ };
270
+ }
271
+ }
272
+ const stillFailed = collectRemediableItems(diff.semantic);
273
+ const exhausted = (0, prdRemediateState_1.filterExhaustedItems)(stillFailed, state, maxPerItem);
274
+ if (exhausted.length > 0) {
275
+ await escalateFailuresToHumanConfirm(options.outputRoot, exhausted, maxPerItem);
276
+ return {
277
+ ok: false,
278
+ detail: `${exhausted.length} 项已达单项 ${maxPerItem} 次上限仍未通过,已写入 08 待人工确认汇总`,
279
+ attempts: round,
280
+ escalated: true,
281
+ needUserConfirm: true,
282
+ };
283
+ }
284
+ if (stillFailed.length > 0) {
285
+ return {
286
+ ok: false,
287
+ detail: `prd-diff 未通过,仍有 ${stillFailed.length} 项可自动修复但已达全局轮数上限`,
288
+ attempts: round,
289
+ escalated: false,
290
+ needUserConfirm: false,
291
+ };
292
+ }
293
+ return {
294
+ ok: false,
295
+ detail: "prd-diff 未通过但无可自动修复项",
296
+ attempts: round,
297
+ escalated: false,
298
+ needUserConfirm: false,
299
+ };
300
+ }
@@ -0,0 +1,135 @@
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.PRD_REMEDIATE_STATE_FILE = void 0;
40
+ exports.emptyRemediateState = emptyRemediateState;
41
+ exports.loadRemediateState = loadRemediateState;
42
+ exports.saveRemediateState = saveRemediateState;
43
+ exports.clearRemediateState = clearRemediateState;
44
+ exports.formatWrongCodeSnippet = formatWrongCodeSnippet;
45
+ exports.snapshotItemFailure = snapshotItemFailure;
46
+ exports.recordItemFailure = recordItemFailure;
47
+ exports.clearItemFromState = clearItemFromState;
48
+ exports.getItemAttempts = getItemAttempts;
49
+ exports.filterEligibleItems = filterEligibleItems;
50
+ exports.filterExhaustedItems = filterExhaustedItems;
51
+ const path_1 = __importDefault(require("path"));
52
+ const fs_1 = require("./fs");
53
+ exports.PRD_REMEDIATE_STATE_FILE = ".prd-remediate-state.json";
54
+ function emptyRemediateState() {
55
+ return { updatedAt: new Date().toISOString(), items: {} };
56
+ }
57
+ async function loadRemediateState(outputRoot) {
58
+ var _a, _b;
59
+ const raw = await (0, fs_1.readFileIfExists)(path_1.default.join(outputRoot, exports.PRD_REMEDIATE_STATE_FILE));
60
+ if (!raw)
61
+ return emptyRemediateState();
62
+ try {
63
+ const parsed = JSON.parse(raw);
64
+ return {
65
+ updatedAt: (_a = parsed.updatedAt) !== null && _a !== void 0 ? _a : new Date().toISOString(),
66
+ items: (_b = parsed.items) !== null && _b !== void 0 ? _b : {},
67
+ };
68
+ }
69
+ catch {
70
+ return emptyRemediateState();
71
+ }
72
+ }
73
+ async function saveRemediateState(outputRoot, state) {
74
+ state.updatedAt = new Date().toISOString();
75
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, exports.PRD_REMEDIATE_STATE_FILE), `${JSON.stringify(state, null, 2)}\n`);
76
+ }
77
+ async function clearRemediateState(outputRoot) {
78
+ const { unlink } = await Promise.resolve().then(() => __importStar(require("fs/promises")));
79
+ try {
80
+ await unlink(path_1.default.join(outputRoot, exports.PRD_REMEDIATE_STATE_FILE));
81
+ }
82
+ catch {
83
+ /* ignore */
84
+ }
85
+ }
86
+ /** 从语义 diff 项提取「错误代码/证据」摘要 */
87
+ function formatWrongCodeSnippet(item) {
88
+ if (item.codeHits.length > 0) {
89
+ return item.codeHits
90
+ .slice(0, 5)
91
+ .map((h) => `${h.file}:${h.line}\n ${h.snippet}`)
92
+ .join("\n");
93
+ }
94
+ if (item.status === "missing_in_code") {
95
+ return "(代码库中未找到 PRD 要求的实现或字面量)";
96
+ }
97
+ if (item.missingSignals.length > 0) {
98
+ return `(缺失 PRD 字面量: ${item.missingSignals.join(";")})`;
99
+ }
100
+ return "(无代码命中,见 detail)";
101
+ }
102
+ function snapshotItemFailure(item, round) {
103
+ return {
104
+ round,
105
+ at: new Date().toISOString(),
106
+ status: item.status,
107
+ detail: item.detail,
108
+ wrongCode: formatWrongCodeSnippet(item),
109
+ missingSignals: [...item.missingSignals],
110
+ };
111
+ }
112
+ function recordItemFailure(state, item, round) {
113
+ var _a;
114
+ const prev = (_a = state.items[item.atomId]) !== null && _a !== void 0 ? _a : { attempts: 0, history: [] };
115
+ const record = snapshotItemFailure(item, round);
116
+ state.items[item.atomId] = {
117
+ attempts: prev.attempts + 1,
118
+ history: [...prev.history, record].slice(-5),
119
+ };
120
+ }
121
+ function clearItemFromState(state, atomId) {
122
+ delete state.items[atomId];
123
+ }
124
+ function getItemAttempts(state, atomId) {
125
+ var _a, _b;
126
+ return (_b = (_a = state.items[atomId]) === null || _a === void 0 ? void 0 : _a.attempts) !== null && _b !== void 0 ? _b : 0;
127
+ }
128
+ /** 仍可自动修复的项(未达单项上限) */
129
+ function filterEligibleItems(failed, state, maxPerItem) {
130
+ return failed.filter((i) => getItemAttempts(state, i.atomId) < maxPerItem);
131
+ }
132
+ /** 已达单项上限、仍失败的项 */
133
+ function filterExhaustedItems(failed, state, maxPerItem) {
134
+ return failed.filter((i) => getItemAttempts(state, i.atomId) >= maxPerItem);
135
+ }