@zhushanwen/pi-subagent-workflow 5.0.0-dev.1 → 5.0.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.
- package/agents/doc-reviewer.md +2 -2
- package/agents/reviewer.md +13 -1
- package/package.json +5 -3
- package/src/execution/__tests__/run-spawn-edges.test.ts +210 -2
- package/src/execution/__tests__/session-pending.test.ts +138 -0
- package/src/execution/session-pending.ts +89 -0
- package/src/execution/session-runner.ts +69 -3
- package/src/index.ts +13 -0
- package/src/injectors/__tests__/subagent-list-injector.test.ts +94 -0
- package/src/injectors/__tests__/workflow-list-injector.test.ts +128 -0
- package/src/injectors/subagent-list-injector.ts +185 -0
- package/src/injectors/workflow-list-injector.ts +204 -0
- package/src/interface/subagent-tool.ts +2 -2
- package/src/interface/tool-workflow.ts +3 -3
- package/workflows/review-fix-loop-utils.cjs +47 -10
- package/workflows/review-fix-loop.js +22 -11
|
@@ -246,34 +246,69 @@ function normalizeFixResult(raw) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
/**
|
|
249
|
-
* ES3
|
|
250
|
-
*
|
|
251
|
-
* (放行,由 ES2 软校验记 warning)。wave 3 接入数字 ID 后可升级为对账级判定。
|
|
252
|
-
* @returns [{ issue_id, severity }] 违规列表;空数组 = 通过
|
|
249
|
+
* issue ID 归一化(ES3 校验与 fix 阶段对账共用键空间):小写 + 剥尾部 "(...)" 尾注。
|
|
250
|
+
* LLM 产出的 ID 漂移形态:大小写("mf-1"/"MF-1")、尾注("MF-1 (fixed)")。空串返回 ""。
|
|
253
251
|
*/
|
|
252
|
+
function normIssueId(s) {
|
|
253
|
+
return String(s ?? "").toLowerCase().replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 在 issues 键空间中查找 issue_id 的归一化匹配键(不存在返回 undefined)。
|
|
258
|
+
* fix 阶段(fix-attempted/deferred 标记)与 ES3 校验共用——精确键查表会把
|
|
259
|
+
* "mf-1"/"MF-1 (fixed)" 等漂移 ID 判为未追踪,导致 fix-attempted → fixed/regressed
|
|
260
|
+
* → needs-redesign 状态链静默失效;deferred 侧漂移则创建幽灵条目(原条目仍 open 阻塞收敛)。
|
|
261
|
+
*/
|
|
262
|
+
function findIssueKey(issues, issueId) {
|
|
263
|
+
if (!issues || typeof issueId !== "string" || !issueId) return undefined;
|
|
264
|
+
if (issues[issueId]) return issueId;
|
|
265
|
+
const norm = normIssueId(issueId);
|
|
266
|
+
if (!norm) return undefined;
|
|
267
|
+
for (const key of Object.keys(issues)) {
|
|
268
|
+
if (normIssueId(key) === norm) return key;
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
|
|
254
273
|
/**
|
|
255
274
|
* ES3 硬校验(5.3-P1 红线):(1) deferred 只允许 minor/trivial;(2) must-fix 必须全进
|
|
256
275
|
* fixes[]——mustFixIds 中未修复且未显式处理的 ID 判 violation(漏修)。mustFixIds
|
|
257
276
|
* 为 null/undefined 时仅做 (1)(无 aggregator 数据的降级路径,wave 2 限制)。
|
|
277
|
+
* trackedIssues(state.issues)可选:deferred 的 severity 与追踪表交叉核对(MF-4)——
|
|
278
|
+
* 追踪条目以追踪 severity 为准(must-fix 追踪皆 critical/major,defer 即违规),
|
|
279
|
+
* 仅追踪无此 ID(S-x minor)时采信 fix agent 自报。
|
|
258
280
|
*/
|
|
259
|
-
function validateFixResult(result, mustFixIds) {
|
|
281
|
+
function validateFixResult(result, mustFixIds, trackedIssues) {
|
|
260
282
|
const violations = [];
|
|
261
283
|
for (const d of result.deferred || []) {
|
|
262
284
|
if (!d) continue;
|
|
263
285
|
const sev = typeof d.severity === "string" ? d.severity.toLowerCase() : "";
|
|
264
|
-
|
|
265
|
-
|
|
286
|
+
// m9: 自报 severity 可被单边绕过(fix agent 与审核方同一 LLM,有少干活动机,
|
|
287
|
+
// 把 must-fix 标 minor 塞进 deferred 即过旧校验)——与追踪表交叉核对:
|
|
288
|
+
// trackedIssues 中能找到的 ID 以其追踪 severity 为准;追踪表无此 ID 采信自报。
|
|
289
|
+
let effectiveSev = sev;
|
|
290
|
+
if (trackedIssues && typeof d.issue_id === "string" && d.issue_id) {
|
|
291
|
+
const trackedKey = findIssueKey(trackedIssues, d.issue_id);
|
|
292
|
+
const trackedSev = trackedKey ? trackedIssues[trackedKey].severity : undefined;
|
|
293
|
+
const ts = typeof trackedSev === "string" ? trackedSev.toLowerCase() : "";
|
|
294
|
+
// 仅认真实 severity 等级(critical/major/minor/trivial);"unknown"(reconcile 新
|
|
295
|
+
// ID 默认)等非等级值不覆盖自报,避免误伤合法 minor deferral
|
|
296
|
+
if (ts === "critical" || ts === "major" || ts === "minor" || ts === "trivial") {
|
|
297
|
+
effectiveSev = ts;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (effectiveSev && effectiveSev !== "minor" && effectiveSev !== "trivial") {
|
|
301
|
+
violations.push({ issue_id: d.issue_id || "(unnamed)", severity: effectiveSev });
|
|
266
302
|
}
|
|
267
303
|
}
|
|
268
304
|
if (Array.isArray(mustFixIds) && mustFixIds.length > 0) {
|
|
269
305
|
// m3: ID 归一化比较——大小写 + 尾部括号尾注(如 "(fixed)")漂移不误杀:
|
|
270
306
|
// 严格 trim 比较会把 "mf-1"/"MF-1 (fixed)" 判漏修,整轮 fix-failure 误杀
|
|
271
|
-
const normId = (s) => String(s).toLowerCase().replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
272
307
|
const fixedIds = new Set((result.fixes || [])
|
|
273
|
-
.map((f) => (f && typeof f.issue_id === "string" ?
|
|
308
|
+
.map((f) => (f && typeof f.issue_id === "string" ? normIssueId(f.issue_id) : ""))
|
|
274
309
|
.filter(Boolean));
|
|
275
310
|
for (const id of mustFixIds) {
|
|
276
|
-
const norm = typeof id === "string" ?
|
|
311
|
+
const norm = typeof id === "string" ? normIssueId(id) : (id && typeof id.id === "string" ? normIssueId(id.id) : "");
|
|
277
312
|
if (norm && !fixedIds.has(norm)) {
|
|
278
313
|
violations.push({ issue_id: norm, severity: "must-fix-not-fixed" });
|
|
279
314
|
}
|
|
@@ -820,6 +855,8 @@ module.exports = {
|
|
|
820
855
|
resolveReviewReportPath,
|
|
821
856
|
normalizeFixResult,
|
|
822
857
|
validateFixResult,
|
|
858
|
+
normIssueId,
|
|
859
|
+
findIssueKey,
|
|
823
860
|
reconcileIssues,
|
|
824
861
|
normalizeReviewResult,
|
|
825
862
|
computeKnownRemaining,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
const meta = {
|
|
24
24
|
name: "review-fix-loop",
|
|
25
|
-
description: "审查-修复循环:多批串行(批内并行 review → aggregate → fix → 重审直到 clean)。必填 targetType(git-diff/file/dir/text)+ target
|
|
25
|
+
description: "审查-修复循环:多批串行(批内并行 review → aggregate → fix → 重审直到 clean)。必填 targetType(git-diff/file/dir/text)+ target。批次由必填参数 batch1..batchN 控制(无默认,至少传一个;agents 为单批简写;如 batch1=fallow-scan batch2=reviewer),用于前置检查先行的场景。注意:唯一带写操作/commit 副作用的内置 workflow,autoCommit 默认 false;skipCleanAgents 默认 true + recheckAfterFix 默认 false(clean agent 下轮跳过,与字面语义一致);传 recheckAfterFix=true 启用可选强回归模式(fix 后重派全批,clean agent 走限定 prompt 只审改动文件)。可选 fixAgent/maxFixAttempts/convergeNewIssues/convergeRounds 控制修复 agent 与收敛终止(详见 workflows/README.md)。",
|
|
26
26
|
phases: ["Review", "Fix"],
|
|
27
27
|
};
|
|
28
28
|
|
|
@@ -56,6 +56,7 @@ const {
|
|
|
56
56
|
resolveReviewReportPath,
|
|
57
57
|
normalizeFixResult,
|
|
58
58
|
validateFixResult,
|
|
59
|
+
findIssueKey,
|
|
59
60
|
reconcileIssues,
|
|
60
61
|
normalizeReviewResult,
|
|
61
62
|
computeKnownRemaining,
|
|
@@ -823,8 +824,10 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
823
824
|
|
|
824
825
|
// ES3 硬校验(5.3 红线,恢复 mustFixIds 交叉校验——wave 3 后 agg.must_fix_ids
|
|
825
826
|
// 已是标准字段):(1) deferred 只允许 minor;(2) must-fix 必须全进 fixes[](漏修
|
|
826
|
-
// 判 violation)。任一违规 → fix-failure
|
|
827
|
-
|
|
827
|
+
// 判 violation)。任一违规 → fix-failure(结构化终止)。trackedIssues 传入
|
|
828
|
+
// state.issues——deferred severity 与追踪表交叉核对(MF-4):must-fix 被标 minor
|
|
829
|
+
// 塞进 deferred 的逃逸路径在追踪表面前失效(追踪 severity 为准)。
|
|
830
|
+
const es3Violations = validateFixResult(fixResult, agg.must_fix_ids, state.issues);
|
|
828
831
|
if (es3Violations.length > 0) {
|
|
829
832
|
// m7: violation 分两类——deferred 非 minor / must-fix 漏修(must-fix-not-fixed),
|
|
830
833
|
// finalMessage 文案区分:统一文案会把漏修误报成 defer 违规,误导修复方向
|
|
@@ -867,22 +870,30 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
867
870
|
// 写入与 knownRemaining 同步链路生效(否则 knownRemaining 恒空,deferred 跨轮继承整链失效)
|
|
868
871
|
if (!state.issues) state.issues = {};
|
|
869
872
|
// 5.1:fix 结果标记 fix-attempted(ID 对账驱动)+ fixResults 落库(R2+ prompt 输入)
|
|
873
|
+
// 归一化查表(findIssueKey,与 ES3 同键空间):fix agent ID 漂移("mf-1"/
|
|
874
|
+
// "MF-1 (fixed)")不再丢匹配——精确键查表时 issue 停留 open,reconcile 无
|
|
875
|
+
// fix-attempted 可转 fixed/regressed,needs-redesign 出口对该类 ID 静默失效。
|
|
870
876
|
for (const f of fixResult.fixes) {
|
|
871
|
-
if (f && typeof f.issue_id === "string"
|
|
872
|
-
state.issues
|
|
873
|
-
|
|
877
|
+
if (f && typeof f.issue_id === "string") {
|
|
878
|
+
const trackedKey = findIssueKey(state.issues, f.issue_id);
|
|
879
|
+
if (trackedKey) {
|
|
880
|
+
state.issues[trackedKey].status = "fix-attempted";
|
|
881
|
+
state.issues[trackedKey].history.push({ round, status: "fix-attempted" });
|
|
882
|
+
}
|
|
874
883
|
}
|
|
875
884
|
}
|
|
876
885
|
// 5.3-4 deferred 写入 state.issues(known-remaining 跨轮继承链路):deferred 条目
|
|
877
886
|
// 以 status=deferred 入 issues,据此生成 knownRemaining 传给 R2+ prompt。
|
|
878
|
-
//
|
|
887
|
+
// ID 已存在(曾被修复/降级,含大小写/尾注漂移)→ 更新状态 + reason;
|
|
888
|
+
// 不存在(S-x minor)→ 新建。漂移 ID 归一化匹配防止幽灵条目(原条目仍 open 阻塞收敛)。
|
|
879
889
|
for (const d of fixResult.deferred) {
|
|
880
890
|
if (!d || typeof d.issue_id !== "string" || !d.issue_id) continue;
|
|
881
891
|
const reason = typeof d.reason === "string" ? d.reason : "";
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
state.issues[
|
|
885
|
-
state.issues[
|
|
892
|
+
const trackedKey = findIssueKey(state.issues, d.issue_id);
|
|
893
|
+
if (trackedKey) {
|
|
894
|
+
state.issues[trackedKey].status = "deferred";
|
|
895
|
+
state.issues[trackedKey].deferredReason = reason;
|
|
896
|
+
state.issues[trackedKey].history.push({ round, status: "deferred" });
|
|
886
897
|
} else {
|
|
887
898
|
state.issues[d.issue_id] = {
|
|
888
899
|
firstSeen: round, severity: "minor", status: "deferred",
|