@zhushanwen/pi-subagent-workflow 7.4.0 → 8.1.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 +15 -0
- package/package.json +7 -4
- package/scripts/rfl.mjs +308 -0
- package/skills/workflow-script-format/SKILL.md +1 -1
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +10 -4
- package/src/execution/__tests__/before-agent-start-injection.test.ts +132 -0
- package/src/execution/__tests__/bg-notify-render.test.ts +15 -15
- package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +365 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
- package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +215 -0
- package/src/execution/__tests__/conversation-wiring.test.ts +198 -0
- package/src/execution/__tests__/crash-recovery.test.ts +8 -2
- package/src/execution/__tests__/delivery-methods.test.ts +385 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +49 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -72
- package/src/execution/__tests__/execution-record.test.ts +199 -0
- package/src/execution/__tests__/finalize-record.test.ts +197 -15
- package/src/execution/__tests__/format.test.ts +131 -7
- package/src/execution/__tests__/gc-timer.test.ts +184 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +37 -10
- package/src/execution/__tests__/index-session-start-identity.test.ts +371 -0
- package/src/execution/__tests__/index-session-start.test.ts +257 -5
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +337 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
- package/src/execution/__tests__/list-component.test.ts +59 -5
- package/src/execution/__tests__/list-fields.test.ts +109 -0
- package/src/execution/__tests__/model-resolver.test.ts +38 -1
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
- package/src/execution/__tests__/nested-visibility.test.ts +325 -0
- package/src/execution/__tests__/notifier-flush.test.ts +209 -7
- package/src/execution/__tests__/one-shot-upgrade.test.ts +205 -0
- package/src/execution/__tests__/parent-child-matrix.test.ts +336 -0
- package/src/execution/__tests__/record-store.test.ts +442 -54
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +11 -12
- package/src/execution/__tests__/recursive-visibility-env.test.ts +18 -20
- package/src/execution/__tests__/resource-policy.test.ts +109 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +18 -25
- package/src/execution/__tests__/run-spawn-integration.test.ts +29 -25
- package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +14 -11
- package/src/execution/__tests__/session-pending.test.ts +61 -2
- package/src/execution/__tests__/session-reconstructor.test.ts +4 -4
- package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +15 -21
- package/src/execution/__tests__/session-start-reaper.test.ts +10 -8
- package/src/execution/__tests__/spawn-args.test.ts +127 -49
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +1 -0
- package/src/execution/__tests__/spawned-children.test.ts +92 -0
- package/src/execution/__tests__/status-refactor.test.ts +345 -0
- package/src/execution/__tests__/stdin-writer.test.ts +97 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
- package/src/execution/__tests__/subagent-service.test.ts +49 -11
- package/src/execution/__tests__/timeout-integration.test.ts +27 -13
- package/src/execution/__tests__/tool-action.test.ts +12 -10
- package/src/execution/__tests__/truncline-snapshot.test.ts +81 -0
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
- package/src/execution/__tests__/worktree-manager.test.ts +300 -90
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +17 -13
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
- package/src/execution/__tests__/worktree-registry.test.ts +72 -34
- package/src/execution/agent-result-mapper.ts +4 -1
- package/src/execution/argv-mirror.ts +21 -2
- package/src/execution/channel-registry-access.ts +3 -1
- package/src/execution/execution-record.ts +126 -9
- package/src/execution/finalize-record.ts +99 -13
- package/src/execution/idle-gc.ts +47 -0
- package/src/execution/lifecycle-manager.ts +491 -0
- package/src/execution/lifecycle-predicates.ts +65 -0
- package/src/execution/manifest-store.ts +61 -16
- package/src/execution/model-resolver.ts +26 -5
- package/src/execution/notifier.ts +69 -12
- package/src/execution/pi-invocation.ts +21 -1
- package/src/execution/record-entry.ts +118 -0
- package/src/execution/record-store.ts +844 -108
- package/src/execution/session-pending.ts +121 -49
- package/src/execution/session-reconstructor.ts +224 -7
- package/src/execution/session-runner.ts +713 -316
- package/src/execution/sessions-index.ts +304 -0
- package/src/execution/stdin-writer.ts +93 -7
- package/src/execution/stream-sink.ts +20 -3
- package/src/execution/subagent-service.ts +917 -138
- package/src/execution/temp-prompt.ts +8 -3
- package/src/execution/turn-limiter.ts +14 -0
- package/src/execution/types.ts +218 -19
- package/src/execution/worktree-manager.ts +449 -59
- package/src/execution/worktree-registry.ts +97 -29
- package/src/index.ts +318 -20
- package/src/injectors/subagent-list-injector.ts +26 -8
- package/src/injectors/workflow-list-injector.ts +25 -8
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +17 -4
- package/src/interface/__tests__/tool-render.test.ts +10 -8
- package/src/interface/__tests__/tool-workflow-script-generate.test.ts +103 -26
- package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +179 -0
- package/src/interface/bg-notify-render.ts +32 -8
- package/src/interface/command-actions.ts +26 -7
- package/src/interface/commands.ts +21 -22
- package/src/interface/format.ts +53 -20
- package/src/interface/gui-mappers.ts +6 -8
- package/src/interface/helpers.ts +170 -10
- package/src/interface/list-component.ts +53 -14
- package/src/interface/subagent-actions.ts +235 -17
- package/src/interface/subagent-tool.ts +81 -16
- package/src/interface/subagents.ts +2 -1
- package/src/interface/tool-render.ts +21 -27
- package/src/interface/tool-workflow-script.ts +29 -33
- package/src/interface/tool-workflow.ts +67 -100
- package/src/interface/views/WorkflowsView.ts +89 -32
- package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +264 -0
- package/src/interface/views/detail-content.ts +1 -1
- package/src/interface/views/format.ts +3 -3
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +333 -0
- package/src/orchestration/__tests__/args-validator.test.ts +1 -1
- package/src/orchestration/__tests__/config-loader.test.ts +38 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +394 -4
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +4 -4
- package/src/orchestration/__tests__/execute-agent-call.test.ts +144 -1
- package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +171 -0
- package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +820 -19
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -2
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +332 -149
- package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +1108 -19
- package/src/orchestration/__tests__/skill-discovery.test.ts +130 -61
- package/src/orchestration/__tests__/test-mocks.ts +197 -0
- package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +110 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -2
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
- package/src/orchestration/__tests__/workflows-e2e.test.ts +38 -40
- package/src/orchestration/agent-opts-resolver.ts +4 -1
- package/src/orchestration/args-validator.ts +2 -2
- package/src/orchestration/config-loader.ts +30 -1
- package/src/orchestration/error-recovery.ts +137 -30
- package/src/orchestration/execute-agent-call.ts +44 -10
- package/src/orchestration/jsonl-run-store.ts +397 -71
- package/src/orchestration/launcher.ts +7 -1
- package/src/orchestration/lifecycle.ts +145 -133
- package/src/orchestration/models/__tests__/trace.test.ts +408 -0
- package/src/orchestration/models/budget.ts +1 -1
- package/src/orchestration/models/run-runtime.ts +15 -17
- package/src/orchestration/models/run-spec.ts +2 -2
- package/src/orchestration/models/run-state.ts +3 -3
- package/src/orchestration/models/trace.ts +95 -15
- package/src/orchestration/models/types.ts +8 -9
- package/src/orchestration/models/workflow-run.ts +50 -71
- package/src/orchestration/models/workflow-script.ts +32 -1
- package/src/orchestration/skill-discovery.ts +30 -0
- package/src/orchestration/worker-handle.ts +1 -1
- package/src/orchestration/worker-host.ts +1 -1
- package/src/orchestration/worker-script-builder.ts +38 -11
- package/src/shared/__tests__/agent-ref.test.ts +34 -0
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +280 -0
- package/src/shared/__tests__/resource-discovery.test.ts +79 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
- package/src/shared/agent-ref.ts +22 -1
- package/src/shared/resource-discovery.ts +162 -59
- package/src/shared/schema-jsonify.ts +56 -0
- package/workflows/review-fix-loop-utils.cjs +542 -32
- package/workflows/review-fix-loop.js +462 -109
|
@@ -44,6 +44,7 @@ parameters:
|
|
|
44
44
|
maxFixAttempts: { type: integer, default: 2, minimum: 1 }
|
|
45
45
|
convergeNewIssues: { type: integer, default: 1, minimum: 1 }
|
|
46
46
|
convergeRounds: { type: integer, default: 2, minimum: 1 }
|
|
47
|
+
aggregatorModel: { type: string, description: cheaper model for the aggregate step (mechanical dedup/format work); route per global/project AGENTS.md, confirm with the owner before adding a new entry; defaults to the run model }
|
|
47
48
|
reviewPrompt: { type: string }
|
|
48
49
|
fixPrompt: { type: string }
|
|
49
50
|
fallowScan: { type: boolean, default: false }
|
|
@@ -84,6 +85,7 @@ const {
|
|
|
84
85
|
buildScopedRecheckPrompt,
|
|
85
86
|
wrapUntrusted,
|
|
86
87
|
buildFixPrompt,
|
|
88
|
+
buildR1ReviewPrompt,
|
|
87
89
|
buildR2ReviewPrompt,
|
|
88
90
|
buildAggregatorPrompt,
|
|
89
91
|
resolveReviewReportPath,
|
|
@@ -98,6 +100,16 @@ const {
|
|
|
98
100
|
parseResult,
|
|
99
101
|
normalizeAggregatorResult,
|
|
100
102
|
parseAggregatedMd,
|
|
103
|
+
resolveRunRoot,
|
|
104
|
+
computeOrigin,
|
|
105
|
+
recordDormant,
|
|
106
|
+
filterActiveIds,
|
|
107
|
+
filterDormantFromRecon,
|
|
108
|
+
landScores,
|
|
109
|
+
countMissingFields,
|
|
110
|
+
backfillFixRegression,
|
|
111
|
+
applyCleanRoundBackfill,
|
|
112
|
+
resolveAggregatorModel,
|
|
101
113
|
resolveAgentDefs,
|
|
102
114
|
recordAgentClean,
|
|
103
115
|
recordAgentDirty,
|
|
@@ -114,7 +126,7 @@ const {
|
|
|
114
126
|
for (const key of Object.keys($ARGS)) {
|
|
115
127
|
if (VALID_ARG_KEYS.has(key)) continue;
|
|
116
128
|
if (/^batch\d+$/.test(key)) continue;
|
|
117
|
-
fail("未知参数: " + key + "(合法参数: targetType/target/batch1..batchN/agents/batchNames/reviewPrompt/fixPrompt/autoCommit/maxRounds/stuckThreshold/skipCleanAgents/recheckAfterFix/fallowScan/fixAgent/maxFixAttempts/convergeNewIssues/convergeRounds)");
|
|
129
|
+
fail("未知参数: " + key + "(合法参数: targetType/target/batch1..batchN/agents/batchNames/reviewPrompt/fixPrompt/autoCommit/maxRounds/stuckThreshold/skipCleanAgents/recheckAfterFix/fallowScan/fixAgent/maxFixAttempts/convergeNewIssues/convergeRounds/aggregatorModel)");
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
const targetType = $ARGS.targetType;
|
|
@@ -162,6 +174,11 @@ const maxFixAttempts = coerceInt($ARGS.maxFixAttempts, 2);
|
|
|
162
174
|
const convergeNewIssues = coerceInt($ARGS.convergeNewIssues, 1);
|
|
163
175
|
const convergeRounds = coerceInt($ARGS.convergeRounds, 2);
|
|
164
176
|
const MODEL = $MODEL;
|
|
177
|
+
// rfl aggregator 降档(tier-1 6.4,T8):聚合是机械去重/格式化工作,可降档到便宜
|
|
178
|
+
// 模型。模型路由参考全局/项目 AGENTS.md(当前用户全局有条目:
|
|
179
|
+
// xiaomi-token-plan-cn/mimo-v2.5-pro,thinking 开非 max);无条目请先与主人确认
|
|
180
|
+
// 并写入 AGENTS.md。缺省回退主模型(行为与现状一致)。
|
|
181
|
+
const AGG_MODEL = resolveAggregatorModel($ARGS.aggregatorModel, MODEL);
|
|
165
182
|
|
|
166
183
|
// base 锁定(RC-6,5.6):git-diff 场景 run 启动时锁定 base commit,全程用锁定 hash 构造
|
|
167
184
|
// diff 指令,防止 run 期间 base ref 被更新导致各轮 diff 范围不一致。rev-parse 失败(非 git
|
|
@@ -214,22 +231,29 @@ const reviewerSchema = {
|
|
|
214
231
|
},
|
|
215
232
|
required: ["prev_id", "status"],
|
|
216
233
|
},
|
|
217
|
-
description: "
|
|
234
|
+
description: "Reconciliation table (structured): return [] on R1 (no previous round); on R2+ every previous issue_id must have a status entry",
|
|
218
235
|
},
|
|
219
236
|
},
|
|
220
|
-
required
|
|
237
|
+
// T9 前缀稳定化(tier-1 6.9):required 恒含 reconciliation——schema JSON 逐字嵌入
|
|
238
|
+
// appendSystemPrompt,R1↔R2+ 分叉会造成 system 段字节差异(消息级缓存前缀失效)。
|
|
239
|
+
// R1 的合规输出 = 空数组(prompt 动态段明示)。
|
|
240
|
+
required: ["report_file", "must_fix", "suggestion", "reconciliation"],
|
|
221
241
|
};
|
|
222
242
|
|
|
223
243
|
const aggregatorSchema = {
|
|
224
244
|
type: "object",
|
|
225
245
|
properties: {
|
|
226
246
|
report_file: { type: "string", description: "Absolute path to aggregated.md" },
|
|
227
|
-
must_fix: { type: "number", description: "Total must-fix after dedup across all dimensions" },
|
|
247
|
+
must_fix: { type: "number", description: "Total must-fix after dedup across all dimensions, counting adjudication=evidence entries only" },
|
|
228
248
|
suggestion: { type: "number", description: "Total suggestions after dedup across all dimensions" },
|
|
229
249
|
must_fix_ids: {
|
|
230
250
|
type: "array",
|
|
231
251
|
// M1: 支持 [{id, severity}] 对象(severity: critical/major/minor——converged 终止的
|
|
232
252
|
// 「无 critical」判定数据源)+ 旧格式 string[] 兼容。ajv 权威校验两者皆放行。
|
|
253
|
+
// rfl 数据链(tier-1 §7.2):对象分支增可选 files/evidence/guidance/note/adjudication——
|
|
254
|
+
// origin 归因(files)、修复指引(guidance)、裁决落盘(adjudication+note)的数据源。
|
|
255
|
+
// 降级条目(adjudication ∈ {downgraded, unverified})保留在数组中,由主循环
|
|
256
|
+
// filterActiveIds 过滤出修复队列(must_fix 计数由 aggregator 按非降级条目报)。
|
|
233
257
|
items: {
|
|
234
258
|
oneOf: [
|
|
235
259
|
{ type: "string" },
|
|
@@ -238,7 +262,14 @@ const aggregatorSchema = {
|
|
|
238
262
|
required: ["id"],
|
|
239
263
|
properties: {
|
|
240
264
|
id: { type: "string" },
|
|
241
|
-
|
|
265
|
+
// A5: severity 加 enum 约束生成侧(prompt 已列三值);normalize 侧另有
|
|
266
|
+
// 枚举回退兜底(畸形值 → major),双层防御。
|
|
267
|
+
severity: { type: "string", enum: ["critical", "major", "minor"] },
|
|
268
|
+
files: { type: "array", items: { type: "string" }, description: "File paths cited by the issue (regression attribution)" },
|
|
269
|
+
evidence: { type: "string", description: "Cited evidence (files/lines/test results) from the sub-review" },
|
|
270
|
+
guidance: { type: "string", description: "One-line fix direction for the fixer (code wins on conflict)" },
|
|
271
|
+
adjudication: { type: "string", enum: ["evidence", "unverified", "downgraded"], description: "Evidence verdict; downgraded/unverified entries stay in this array but are filtered out of the fix queue" },
|
|
272
|
+
note: { type: "string", description: "Adjudication reason (required when adjudication is unverified/downgraded)" },
|
|
242
273
|
},
|
|
243
274
|
},
|
|
244
275
|
],
|
|
@@ -250,6 +281,23 @@ const aggregatorSchema = {
|
|
|
250
281
|
items: { type: "string" },
|
|
251
282
|
description: "Caution entries for weak-evidence or high-risk claims (passed to fix stage)",
|
|
252
283
|
},
|
|
284
|
+
// rfl scores(tier-1 §7.2,M2 打分 rubric 消费):可选——M1 只透传 schema 与
|
|
285
|
+
// normalize,打分 prompt 段属 M2(T7)。提前落 schema 避免二次 schema 破坏性变更。
|
|
286
|
+
scores: {
|
|
287
|
+
type: "array",
|
|
288
|
+
items: {
|
|
289
|
+
type: "object",
|
|
290
|
+
properties: {
|
|
291
|
+
round: { type: "number", description: "Round of the scored target (R2 aggregation scoring R1 fix -> round=1)" },
|
|
292
|
+
targetKind: { type: "string", description: "reviewer | fix" },
|
|
293
|
+
targetName: { type: "string" },
|
|
294
|
+
dimensions: { type: "object", description: "Dimension name -> 0-10 score" },
|
|
295
|
+
total: { type: ["number", "null"], description: "Weighted total; null = not computable (e.g. clean-round LLM dimensions missing)" },
|
|
296
|
+
note: { type: "string" },
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
description: "Optional per-round quality scores (may be omitted until the scoring rubric lands)",
|
|
300
|
+
},
|
|
253
301
|
},
|
|
254
302
|
required: ["report_file", "must_fix", "suggestion"],
|
|
255
303
|
};
|
|
@@ -296,10 +344,15 @@ const path = require("path");
|
|
|
296
344
|
const os = require("os");
|
|
297
345
|
|
|
298
346
|
const RUN_ID = ($ARGS._runId && typeof $ARGS._runId === "string") ? $ARGS._runId : "run-" + Date.now();
|
|
299
|
-
|
|
347
|
+
// rfl 仪表(tier-1 §6.8/§7.5):state 持久化迁出 $TMPDIR(系统清理即丢失)——
|
|
348
|
+
// ~/.review-fix-loop/<repo-slug>/<runId>/;$TMPDIR 仅作 home 不可写时的降级。
|
|
349
|
+
// 旧 $TMPDIR run 不迁移(易失数据不抢救),loadState 只从新位置读。
|
|
350
|
+
const { root: RUN_ROOT, degraded: RUN_ROOT_DEGRADED } = resolveRunRoot({ runId: RUN_ID, cwd: process.cwd() });
|
|
351
|
+
if (RUN_ROOT_DEGRADED) {
|
|
352
|
+
log("WARN: ~/.review-fix-loop not writable, falling back to $TMPDIR: " + RUN_ROOT);
|
|
353
|
+
}
|
|
300
354
|
const STATE_FILE = RUN_ROOT + "/state.json";
|
|
301
355
|
|
|
302
|
-
fs.mkdirSync(RUN_ROOT, { recursive: true });
|
|
303
356
|
log("Run directory: " + RUN_ROOT);
|
|
304
357
|
|
|
305
358
|
// ── Startup fail-fast: validate agent ref paths exist (ADR-0003 D6) ─
|
|
@@ -330,36 +383,124 @@ validateAgentPaths(startupDefs);
|
|
|
330
383
|
|
|
331
384
|
// ── State management (persistent, atomic writes) ────────────────────
|
|
332
385
|
|
|
386
|
+
// 全新初始 state 模板:catch 分支(无残留)与「读到残留 state」分支共用(A1)。
|
|
387
|
+
// 易变累积字段(calls/fixResults/fixCount/issues/dormant...)全部归零——
|
|
388
|
+
// attempt-2 重放会重新 recordCall/push,复用旧 state 必然双记。
|
|
389
|
+
function freshState() {
|
|
390
|
+
return {
|
|
391
|
+
meta: {
|
|
392
|
+
runId: RUN_ID, workspace: $WORKSPACE || "", model: MODEL || "(default)",
|
|
393
|
+
targetType, target, batches: BATCHES, startedAt: new Date().toISOString(),
|
|
394
|
+
},
|
|
395
|
+
agentStatus: {},
|
|
396
|
+
fixCount: 0,
|
|
397
|
+
batches: [],
|
|
398
|
+
calls: [],
|
|
399
|
+
dormant: [],
|
|
400
|
+
fixResults: [],
|
|
401
|
+
issues: undefined,
|
|
402
|
+
knownRemaining: [],
|
|
403
|
+
convergeStreak: 0,
|
|
404
|
+
lastModifiedFiles: [],
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
333
408
|
function loadState() {
|
|
334
409
|
try {
|
|
335
|
-
|
|
410
|
+
const parsed = JSON.parse(fs.readFileSync(STATE_FILE, "utf-8"));
|
|
411
|
+
// A1(rebuild 双记修复):读到既有 state = 同 _runId 上一 attempt 的残留——
|
|
412
|
+
// engine 因 script-error rebuild 后同 RUN_ROOT 重跑本脚本,原样返回旧 state 会让
|
|
413
|
+
// attempt-2 重放的 recordCall/fixResults/fixCount/issues 全部双记。state.json 在
|
|
414
|
+
// RUN_ROOT(按 runId 隔离)内,存在即属本 run 前次 attempt,无跨 run 误判。
|
|
415
|
+
// 只保留 previousAttempts 计数(观测 rebuild 次数),其余全部重置。
|
|
416
|
+
const prev = (parsed && parsed.meta && parsed.meta.previousAttempts) || 0;
|
|
417
|
+
log("INFO: found leftover state.json for this run (previous attempt residue after engine rebuild?) — resetting volatile accumulators (previousAttempts=" + (prev + 1) + ")");
|
|
418
|
+
const s = freshState();
|
|
419
|
+
s.meta.previousAttempts = prev + 1;
|
|
420
|
+
return s;
|
|
336
421
|
} catch {
|
|
337
|
-
return
|
|
338
|
-
meta: {
|
|
339
|
-
runId: RUN_ID, workspace: $WORKSPACE || "", model: MODEL || "(default)",
|
|
340
|
-
targetType, target, batches: BATCHES, startedAt: new Date().toISOString(),
|
|
341
|
-
},
|
|
342
|
-
agentStatus: {},
|
|
343
|
-
fixCount: 0,
|
|
344
|
-
batches: [],
|
|
345
|
-
fixResults: [],
|
|
346
|
-
issues: undefined,
|
|
347
|
-
knownRemaining: [],
|
|
348
|
-
convergeStreak: 0,
|
|
349
|
-
lastModifiedFiles: [],
|
|
350
|
-
};
|
|
422
|
+
return freshState();
|
|
351
423
|
}
|
|
352
424
|
}
|
|
353
425
|
|
|
426
|
+
// rfl 仪表:终止原因快照声明上移(saveState 引用;所有实际调用发生在
|
|
427
|
+
// 主循环初始化后,TDZ 不触发)。CLI list/stats 的终止原因数据源。
|
|
428
|
+
let terminated = "clean";
|
|
429
|
+
|
|
354
430
|
function saveState(state) {
|
|
431
|
+
// rfl 仪表:terminated 随每次落盘快照(结构化终止路径设值后即 saveState)
|
|
432
|
+
if (state.meta) state.meta.terminated = terminated;
|
|
355
433
|
const tmp = STATE_FILE + ".tmp";
|
|
356
434
|
fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
357
435
|
fs.renameSync(tmp, STATE_FILE);
|
|
358
436
|
}
|
|
359
437
|
|
|
438
|
+
// ── rfl 仪表(tier-1 §7.3):calls[] 采集 + phaseTimings ───────────
|
|
439
|
+
// 每次 agent() 调用记录资源与耗时;数据源 = returnMeta 透传的 usage/durationMs/
|
|
440
|
+
// sessionId(引擎 T1 透传;旧引擎缺字段时 usage 键省略(undefined)、durationMs
|
|
441
|
+
// 落 null——phase 级耗时由 phaseTimings 覆盖)。
|
|
442
|
+
function recordCall(entry) {
|
|
443
|
+
if (!Array.isArray(state.calls)) state.calls = [];
|
|
444
|
+
state.calls.push(entry);
|
|
445
|
+
}
|
|
446
|
+
function normUsage(meta) {
|
|
447
|
+
const u = meta && typeof meta === "object" ? meta.usage : undefined;
|
|
448
|
+
if (!u || typeof u !== "object") return undefined;
|
|
449
|
+
return {
|
|
450
|
+
input: u.input ?? 0, output: u.output ?? 0,
|
|
451
|
+
cacheRead: u.cacheRead ?? 0, cacheWrite: u.cacheWrite ?? 0, cost: u.cost ?? 0,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
// A10:引擎 T1 透传缺位(returnMeta 在但 usage 缺失 / durationMs 非数)只 WARN 一次
|
|
455
|
+
//(逐调用都打会淹没日志)——提示引擎版本或透传链路问题,calls[] usage 已降级。
|
|
456
|
+
let warnedTelemetryMissing = false;
|
|
457
|
+
function warnTelemetryMissingOnce() {
|
|
458
|
+
if (warnedTelemetryMissing) return;
|
|
459
|
+
warnedTelemetryMissing = true;
|
|
460
|
+
log("WARN: returnMeta usage/durationMs missing — engine telemetry passthrough (T1) not active? calls[] usage degraded");
|
|
461
|
+
}
|
|
462
|
+
// returnMeta 对象 → calls[] 条目(十字段,设计 §7.3)。
|
|
463
|
+
// A11(model 字段语义):model = 请求时参数。agent-ref 调用的实际模型由主线程按
|
|
464
|
+
// .md frontmatter 解析(resolveAgentOpts),此处可能记 "(default)" 而实际跑 frontmatter
|
|
465
|
+
// 模型——引擎侧透传实际 model 是后续工作,消费侧(§6.4 降档归因)需知此局限。
|
|
466
|
+
// A12(promptMode 语义):该字段 = review prompt 模式("full" | "scoped"),对非
|
|
467
|
+
// reviewer 角色(aggregator/fixer)无意义、显式传 null;仅缺省(undefined)回退
|
|
468
|
+
// "full"——空串/null 不再被 || 误转 "full"。
|
|
469
|
+
function buildCallRecord({ batch, round, role, name, model, prompt, promptMode, meta }) {
|
|
470
|
+
const promptStr = typeof prompt === "string" ? prompt : "";
|
|
471
|
+
const metaObj = meta && typeof meta === "object" ? meta : undefined;
|
|
472
|
+
// W1:失败调用(returnMeta = {value, error})天然无 usage/durationMs——排除
|
|
473
|
+
// error 分支,否则 agent 调用失败被误诊为「引擎透传未上线」并烧掉 once 名额。
|
|
474
|
+
// 三个 recordCall 调用点(review/aggregator/fixer)共用本函数,一处修复全覆盖。
|
|
475
|
+
if (metaObj && !metaObj.error && (normUsage(metaObj) === undefined || typeof metaObj.durationMs !== "number")) {
|
|
476
|
+
warnTelemetryMissingOnce();
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
batch, round, role, name: name || role,
|
|
480
|
+
model: model || "(default)",
|
|
481
|
+
durationMs: (metaObj && typeof metaObj.durationMs === "number")
|
|
482
|
+
? metaObj.durationMs
|
|
483
|
+
: null,
|
|
484
|
+
usage: normUsage(metaObj),
|
|
485
|
+
promptMode: promptMode === undefined ? "full" : promptMode,
|
|
486
|
+
promptBytes: Buffer.byteLength(promptStr, "utf8"),
|
|
487
|
+
sessionId: (metaObj && typeof metaObj.sessionId === "string") ? metaObj.sessionId : undefined,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
360
491
|
// clean 记录(recordAgentClean/recordAgentDirty 与跨批跳过判定 shouldSkipAgent
|
|
361
492
|
// 在 review-fix-loop-utils.cjs,vitest 单测见 src/__tests__/review-fix-loop-utils.test.ts)
|
|
362
493
|
|
|
494
|
+
// A8(guidance/evidence 缺失观测):活跃条目缺 guidance/evidence 的单行 WARN——
|
|
495
|
+
// 数据链断点(aggregator 未提取 / 归一化丢失)的可观测信号;fixer 免侦查降级提示。
|
|
496
|
+
// 仅 N+M>0 时打,不逐条刷屏。
|
|
497
|
+
function warnMissingFields(mustFixIds) {
|
|
498
|
+
const miss = countMissingFields(mustFixIds);
|
|
499
|
+
if (miss.missingGuidance > 0 || miss.missingEvidence > 0) {
|
|
500
|
+
log("WARN: " + miss.missingGuidance + " active must-fix entries lack guidance (" + miss.missingEvidence + " lack evidence) — fixer self-investigation expected");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
363
504
|
// ── Agent defs(loadAgentMd/resolveAgentDefs 在 review-fix-loop-utils.cjs) ──
|
|
364
505
|
|
|
365
506
|
// ── Build review calls ──────────────────────────────────────────────
|
|
@@ -379,6 +520,71 @@ function lastModifiedFiles() {
|
|
|
379
520
|
return (r && Array.isArray(r.modifiedFiles)) ? r.modifiedFiles : [];
|
|
380
521
|
}
|
|
381
522
|
|
|
523
|
+
// buildReviewCall 分支构造:fallow 内置工具型(无 .md,静态分析 prompt)。
|
|
524
|
+
function buildFallowReviewCall(base, def, header, roundDir) {
|
|
525
|
+
return {
|
|
526
|
+
...base,
|
|
527
|
+
prompt: [
|
|
528
|
+
header,
|
|
529
|
+
"",
|
|
530
|
+
"Fallow static-analysis pre-scan (tool-based, NOT a git-diff review).",
|
|
531
|
+
"",
|
|
532
|
+
"Steps:",
|
|
533
|
+
"1. Check if fallow is installed: `which fallow`",
|
|
534
|
+
"2. If NOT installed: write the report with a one-line note, must_fix=0, suggestion=0.",
|
|
535
|
+
"3. If installed, run: `fallow audit --base " + lockedBase.base + " --format json --quiet`",
|
|
536
|
+
"4. Extract: complexity hotspots, dead code, unused exports, circular deps",
|
|
537
|
+
"5. Classify findings: critical/major count into must_fix; minor into suggestion.",
|
|
538
|
+
"",
|
|
539
|
+
"output 路径:" + roundDir + "/" + def.report + ".md",
|
|
540
|
+
"Write report to: " + roundDir + "/" + def.report + ".md",
|
|
541
|
+
].join("\n"),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// buildReviewCall 分支构造:R2+ 三段式(5.2)——verify-first 对账 + known-remaining + 收敛 hunt。
|
|
546
|
+
function buildR2ReviewCall(base, def, header, round, max, roundDir, batchIndex) {
|
|
547
|
+
const prevRoundDir = RUN_ROOT + "/batch-" + batchIndex + "/round-" + (round - 1);
|
|
548
|
+
return {
|
|
549
|
+
...base,
|
|
550
|
+
// T9:无 per-round spread——reviewerSchema 跨轮统一(缓存前缀稳定前提)
|
|
551
|
+
prompt: buildR2ReviewPrompt({
|
|
552
|
+
header, round, max, roundDir,
|
|
553
|
+
reportFile: def.report,
|
|
554
|
+
aggPath: prevRoundDir + "/aggregated.md",
|
|
555
|
+
fixResult: state.fixResults && state.fixResults.length
|
|
556
|
+
? state.fixResults[state.fixResults.length - 1]
|
|
557
|
+
: null,
|
|
558
|
+
knownRemaining: (state.knownRemaining && Array.isArray(state.knownRemaining)) ? state.knownRemaining : [],
|
|
559
|
+
// rfl dormant 复活通道(tier-1 6.3 delta ③):降级条目注入 R2+ prompt(动态段内容)
|
|
560
|
+
dormant: (state.dormant && Array.isArray(state.dormant)) ? state.dormant : [],
|
|
561
|
+
reviewPrompt, reviewInstruction,
|
|
562
|
+
}),
|
|
563
|
+
agent: def.path,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// buildReviewCall 分支构造:recheck 限定(5.5)——clean agent 重派时只审 fix 改动
|
|
568
|
+
// 文件 + 自检关联点(scope = modifiedFiles ∪ affected_files,后者来自
|
|
569
|
+
// state.fixImpactFiles),不诱导全量重扫。
|
|
570
|
+
function buildScopedReviewCall(base, def, header, round, max, roundDir, batchIndex) {
|
|
571
|
+
return {
|
|
572
|
+
...base,
|
|
573
|
+
prompt: buildScopedRecheckPrompt({
|
|
574
|
+
header, round, max, roundDir,
|
|
575
|
+
reportFile: def.report,
|
|
576
|
+
modifiedFiles: lastModifiedFiles(),
|
|
577
|
+
affectedFiles: (state.fixImpactFiles && Array.isArray(state.fixImpactFiles)) ? state.fixImpactFiles : [],
|
|
578
|
+
aggPath: RUN_ROOT + "/batch-" + batchIndex + "/round-" + (round - 1) + "/aggregated.md",
|
|
579
|
+
fixResult: state.fixResults && state.fixResults.length
|
|
580
|
+
? state.fixResults[state.fixResults.length - 1]
|
|
581
|
+
: null,
|
|
582
|
+
reviewPrompt, reviewInstruction,
|
|
583
|
+
}),
|
|
584
|
+
agent: def.path,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
382
588
|
function buildReviewCall(def, round, max, batchIndex, roundDir, scoped) {
|
|
383
589
|
const header = "Batch " + batchIndex + " Round " + round + "/" + max + " — " + BATCH_NAMES[batchIndex - 1];
|
|
384
590
|
const prevBatchesHint = batchIndex > 1
|
|
@@ -398,80 +604,27 @@ function buildReviewCall(def, round, max, batchIndex, roundDir, scoped) {
|
|
|
398
604
|
// S4:agentRef = 路径(非 fallow 的 def 必有 path)——systemPrompt/model 由主线程
|
|
399
605
|
// resolveAgentOpts 按 path 加载注入,脚本不再拼 md 内容。
|
|
400
606
|
if (def.isFallow) {
|
|
401
|
-
return
|
|
402
|
-
...base,
|
|
403
|
-
prompt: [
|
|
404
|
-
header,
|
|
405
|
-
"",
|
|
406
|
-
"Fallow static-analysis pre-scan (tool-based, NOT a git-diff review).",
|
|
407
|
-
"",
|
|
408
|
-
"Steps:",
|
|
409
|
-
"1. Check if fallow is installed: `which fallow`",
|
|
410
|
-
"2. If NOT installed: write the report with a one-line note, must_fix=0, suggestion=0.",
|
|
411
|
-
"3. If installed, run: `fallow audit --base " + lockedBase.base + " --format json --quiet`",
|
|
412
|
-
"4. Extract: complexity hotspots, dead code, unused exports, circular deps",
|
|
413
|
-
"5. Classify findings: critical/major count into must_fix; minor into suggestion.",
|
|
414
|
-
"",
|
|
415
|
-
"output 路径:" + roundDir + "/" + def.report + ".md",
|
|
416
|
-
"Write report to: " + roundDir + "/" + def.report + ".md",
|
|
417
|
-
].join("\n"),
|
|
418
|
-
};
|
|
607
|
+
return buildFallowReviewCall(base, def, header, roundDir);
|
|
419
608
|
}
|
|
420
609
|
|
|
421
610
|
// R2+ 三段式分支(5.2):round>1 且非 scoped → verify-first 对账 + known-remaining + 收敛 hunt。
|
|
422
611
|
// scoped 分支(recheck 限定)在下方单独处理(含对账段)。
|
|
423
612
|
if (round > 1 && !scoped) {
|
|
424
|
-
|
|
425
|
-
return {
|
|
426
|
-
...base,
|
|
427
|
-
schema: { ...reviewerSchema, required: [...reviewerSchema.required, "reconciliation"] },
|
|
428
|
-
prompt: buildR2ReviewPrompt({
|
|
429
|
-
header, round, max, roundDir,
|
|
430
|
-
reportFile: def.report,
|
|
431
|
-
aggPath: prevRoundDir + "/aggregated.md",
|
|
432
|
-
fixResult: state.fixResults && state.fixResults.length
|
|
433
|
-
? state.fixResults[state.fixResults.length - 1]
|
|
434
|
-
: null,
|
|
435
|
-
knownRemaining: (state.knownRemaining && Array.isArray(state.knownRemaining)) ? state.knownRemaining : [],
|
|
436
|
-
}),
|
|
437
|
-
agent: def.path,
|
|
438
|
-
};
|
|
613
|
+
return buildR2ReviewCall(base, def, header, round, max, roundDir, batchIndex);
|
|
439
614
|
}
|
|
440
615
|
|
|
441
|
-
// recheck 限定分支(5.5
|
|
442
|
-
// (scope = modifiedFiles ∪ affected_files,后者来自 state.fixImpactFiles),不诱导全量重扫。
|
|
616
|
+
// recheck 限定分支(5.5)
|
|
443
617
|
if (scoped) {
|
|
444
|
-
return
|
|
445
|
-
...base,
|
|
446
|
-
// m2: scoped 分支与 R2+ 分支一致——reconciliation 必填(recheck 也须对账前轮 fix)
|
|
447
|
-
schema: { ...reviewerSchema, required: [...reviewerSchema.required, "reconciliation"] },
|
|
448
|
-
prompt: buildScopedRecheckPrompt({
|
|
449
|
-
header, round, max, roundDir,
|
|
450
|
-
reportFile: def.report,
|
|
451
|
-
modifiedFiles: lastModifiedFiles(),
|
|
452
|
-
affectedFiles: (state.fixImpactFiles && Array.isArray(state.fixImpactFiles)) ? state.fixImpactFiles : [],
|
|
453
|
-
aggPath: RUN_ROOT + "/batch-" + batchIndex + "/round-" + (round - 1) + "/aggregated.md",
|
|
454
|
-
fixResult: state.fixResults && state.fixResults.length
|
|
455
|
-
? state.fixResults[state.fixResults.length - 1]
|
|
456
|
-
: null,
|
|
457
|
-
}),
|
|
458
|
-
agent: def.path,
|
|
459
|
-
};
|
|
618
|
+
return buildScopedReviewCall(base, def, header, round, max, roundDir, batchIndex);
|
|
460
619
|
}
|
|
461
620
|
|
|
621
|
+
// T9:R1 函数化(三模板共享静态段,动态后置)
|
|
462
622
|
return {
|
|
463
623
|
...base,
|
|
464
|
-
prompt:
|
|
465
|
-
header,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
"",
|
|
469
|
-
"Review requirements:",
|
|
470
|
-
reviewPrompt,
|
|
471
|
-
"",
|
|
472
|
-
"output 路径:" + roundDir + "/" + def.report + ".md",
|
|
473
|
-
"Write report to: " + roundDir + "/" + def.report + ".md",
|
|
474
|
-
].join("\n"),
|
|
624
|
+
prompt: buildR1ReviewPrompt({
|
|
625
|
+
header, roundDir, reportFile: def.report, prevBatchesHint,
|
|
626
|
+
reviewPrompt, reviewInstruction,
|
|
627
|
+
}),
|
|
475
628
|
agent: def.path,
|
|
476
629
|
};
|
|
477
630
|
}
|
|
@@ -488,7 +641,6 @@ const state = loadState();
|
|
|
488
641
|
state.meta = state.meta || {};
|
|
489
642
|
state.meta.baseHash = lockedBase.hash || "";
|
|
490
643
|
let totalFixed = 0;
|
|
491
|
-
let terminated = "clean";
|
|
492
644
|
let finalMessage = "";
|
|
493
645
|
|
|
494
646
|
for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
@@ -507,6 +659,11 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
507
659
|
state.issues = undefined;
|
|
508
660
|
state.convergeStreak = 0;
|
|
509
661
|
state.knownRemaining = [];
|
|
662
|
+
// A2(dormant 跨批污染修复):aggregator id 空间每批从 MF-1 重新编号,批 1 降级的
|
|
663
|
+
// dormant MF-3 会与批 2 活跃 MF-3 冲突——filterDormantFromRecon 把它从 reconSeen
|
|
664
|
+
// 剥离后,批 2 的 fix-attempted 被误反转 fixed;且批 1 dormant 持续注入批 2+ prompt。
|
|
665
|
+
// dormant 必须与 issues 同点做批作用域重置。
|
|
666
|
+
state.dormant = [];
|
|
510
667
|
|
|
511
668
|
// 跨批跳过:agent 在更早批 clean 且此后无 fix → 本批不派发
|
|
512
669
|
if (batchIndex > 1) {
|
|
@@ -543,7 +700,20 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
543
700
|
|
|
544
701
|
log("Review: " + active.map((d) => d.name).join(", ") + " (" + active.length + " agent(s) in parallel)...");
|
|
545
702
|
const calls = active.map((def) => buildReviewCall(def, round, maxRounds, batchIndex, roundDir, scopedClean.has(def.name)));
|
|
703
|
+
const phaseTimings = { review: null, aggregate: null, fix: null };
|
|
704
|
+
const reviewT0 = Date.now();
|
|
546
705
|
const allRaw = await parallel(calls.map(runReviewAgent));
|
|
706
|
+
phaseTimings.review = [reviewT0, Date.now()];
|
|
707
|
+
// rfl 仪表:本轮全部 reviewer 调用落 calls[](allRaw 与 calls 一一对应,parallel 语义)
|
|
708
|
+
for (let i = 0; i < allRaw.length; i++) {
|
|
709
|
+
recordCall(buildCallRecord({
|
|
710
|
+
batch: batchIndex, round, role: "reviewer",
|
|
711
|
+
name: active[i].name, model: calls[i].model,
|
|
712
|
+
prompt: calls[i].prompt,
|
|
713
|
+
promptMode: scopedClean.has(active[i].name) ? "scoped" : "full",
|
|
714
|
+
meta: allRaw[i],
|
|
715
|
+
}));
|
|
716
|
+
}
|
|
547
717
|
|
|
548
718
|
// per-agent 结果区分:parallel 结果与 calls 一一对应
|
|
549
719
|
const reviewResults = [];
|
|
@@ -557,6 +727,12 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
557
727
|
// 审查 agent 调用失败(含 AgentRegistry not found / 超时)。不裸 throw——与
|
|
558
728
|
// aggregator-failure/stuck/fix-failure 路径一致:saveState + terminated 结构化终止,
|
|
559
729
|
// 保证 state.json 有记录、调用方拿到结构化结果而非裸异常(MF-3)。
|
|
730
|
+
// W8:失败轮的当前轮条目也落 batchRounds(该轮 phase 时长不因结构化终止
|
|
731
|
+
// 从时间线消失);F3:聚合未发生/失败,mustFix/suggestion 是未知而非 0——
|
|
732
|
+
// 落 0 会被时间线误读为 clean 轮(rfl.mjs 消费侧 `mustFix ?? "-"` 对 null
|
|
733
|
+
// 显示 "-",suggestion 无消费点,null 安全);agents 为已收集的部分结果,
|
|
734
|
+
// aggregate/fix 相位 null 是如实采集(未到达)。
|
|
735
|
+
batchRounds.push({ round, mustFix: null, suggestion: null, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
560
736
|
state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
|
|
561
737
|
saveState(state);
|
|
562
738
|
terminated = "review-failure";
|
|
@@ -577,7 +753,14 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
577
753
|
log("WARN: failed to write report_content to " + reportPath + " — " + e.message);
|
|
578
754
|
}
|
|
579
755
|
}
|
|
580
|
-
|
|
756
|
+
// W6:report_content 已落盘(上方 resolveReviewReportPath 写盘 + report_file
|
|
757
|
+
// 回填),aggregator prompt 指示其 read report_file——整份正文再随 reviewResults
|
|
758
|
+
// JSON 内嵌即同一内容双份付费(实测 ~48% 重复)。push 前剥离;下游
|
|
759
|
+
// (buildAggregatorPrompt 路径清单 / must_fix 计数 / all-clean 判定)只消费
|
|
760
|
+
// report_file 与计数字段,无 report_content 消费。
|
|
761
|
+
const parsedWithoutContent = { ...parsed };
|
|
762
|
+
delete parsedWithoutContent.report_content;
|
|
763
|
+
reviewResults.push(parsedWithoutContent);
|
|
581
764
|
for (const r of parsed.reconciliation) {
|
|
582
765
|
if (r.status === "escalate") reconEscalate.add(r.prev_id);
|
|
583
766
|
else if (r.status !== "fixed") reconSeen.add(r.prev_id);
|
|
@@ -594,6 +777,9 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
594
777
|
} else {
|
|
595
778
|
// tools 受限的 agent(如 tools: read)会过滤掉 structured-output → schema 失效,
|
|
596
779
|
// 结果缺 must_fix。结构化终止(MF-3),raw 完整 dump 便于定位。
|
|
780
|
+
// W8:同 review-failure——当前轮条目落 batchRounds(phase 时长保留在时间线);
|
|
781
|
+
// F3:聚合未发生,mustFix/suggestion 未知非 0(null,消费侧 ?? "-" 兜底)。
|
|
782
|
+
batchRounds.push({ round, mustFix: null, suggestion: null, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
597
783
|
state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
|
|
598
784
|
saveState(state);
|
|
599
785
|
terminated = "review-failure";
|
|
@@ -607,25 +793,47 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
607
793
|
|
|
608
794
|
if (reviewResults.every((r) => r.must_fix === 0)) {
|
|
609
795
|
log("Batch " + batchIndex + " round " + round + ": all agents clean.");
|
|
610
|
-
|
|
796
|
+
// rfl clean 轮黑洞修复(tier-1 6.6 v5,T7):all-clean 现状在聚合/reconcile 前
|
|
797
|
+
// break——末轮 fix 的对账与回归回填永不发生,eval 数据在最 canonical 的成功
|
|
798
|
+
// 路径上失真。break 前用本轮已解析的 reconciliation 数据做确定性回填(不调
|
|
799
|
+
// LLM):fix-attempted 未再现 → fixed + 上轮 fix 的 regression 维度回填。
|
|
800
|
+
if (round > 1) {
|
|
801
|
+
applyCleanRoundBackfill(state, {
|
|
802
|
+
reconSeen, reconEscalate, round, stuckThreshold, batch: batchIndex,
|
|
803
|
+
});
|
|
804
|
+
log("Clean-round backfill applied (reconcile + regression backfill for the previous fix).");
|
|
805
|
+
}
|
|
806
|
+
batchRounds.push({ round, mustFix: 0, suggestion: reviewResults.reduce((a, r) => a + (r.suggestion ?? 0), 0), agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
611
807
|
saveState(state);
|
|
612
808
|
batchClean = true;
|
|
613
809
|
break;
|
|
614
810
|
}
|
|
615
811
|
|
|
616
812
|
// ── Aggregate(内置 prompt,不依赖任何 agent.md) ─────────
|
|
813
|
+
// rfl T7/T8:prevFixResult 作打分材料(R2+ 聚合给上轮 fix 打 LLM 三维度分);
|
|
814
|
+
// model 用 AGG_MODEL(可降档参数,缺省 = 主模型,行为与现状一致)。
|
|
815
|
+
const aggT0 = Date.now();
|
|
816
|
+
const aggPrompt = buildAggregatorPrompt({
|
|
817
|
+
header: "Batch " + batchIndex + "/" + BATCHES.length + " Round " + round + "/" + maxRounds + " — AGGREGATE REVIEWS",
|
|
818
|
+
round, max: maxRounds, roundDir,
|
|
819
|
+
reviewResults,
|
|
820
|
+
prevFixResult: round > 1 && state.fixResults && state.fixResults.length
|
|
821
|
+
? state.fixResults[state.fixResults.length - 1]
|
|
822
|
+
: null,
|
|
823
|
+
});
|
|
617
824
|
const aggRaw = await agent({
|
|
618
|
-
prompt:
|
|
619
|
-
|
|
620
|
-
round, max: maxRounds, roundDir,
|
|
621
|
-
reviewResults,
|
|
622
|
-
}),
|
|
623
|
-
model: MODEL,
|
|
825
|
+
prompt: aggPrompt,
|
|
826
|
+
model: AGG_MODEL,
|
|
624
827
|
schema: aggregatorSchema,
|
|
625
828
|
description: "aggregate",
|
|
626
829
|
timeoutMs: 3_600_000, // 1h
|
|
627
830
|
returnMeta: true,
|
|
628
831
|
});
|
|
832
|
+
phaseTimings.aggregate = [aggT0, Date.now()];
|
|
833
|
+
recordCall(buildCallRecord({
|
|
834
|
+
batch: batchIndex, round, role: "aggregator", name: "aggregate",
|
|
835
|
+
model: AGG_MODEL, prompt: aggPrompt, promptMode: null, meta: aggRaw,
|
|
836
|
+
}));
|
|
629
837
|
|
|
630
838
|
// returnMeta 下 aggRaw = {value, error};失败时 value 为空串、error 在 finalMessage 透出(MF-1)
|
|
631
839
|
const aggValue = aggRaw?.value ?? aggRaw;
|
|
@@ -646,6 +854,10 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
646
854
|
|
|
647
855
|
if (!agg || typeof agg.must_fix !== "number") {
|
|
648
856
|
log("Aggregator failed and fallback failed, stopping.");
|
|
857
|
+
// W8:aggregator-failure 轮的当前轮条目落 batchRounds——review/aggregate 相位
|
|
858
|
+
// 时长已实测(fix 相位 null 如实采集);F3:聚合失败,mustFix/suggestion 未知
|
|
859
|
+
// 非 0(null,消费侧 ?? "-" 兜底,不误读为 clean 轮)。
|
|
860
|
+
batchRounds.push({ round, mustFix: null, suggestion: null, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
649
861
|
state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
|
|
650
862
|
saveState(state);
|
|
651
863
|
terminated = "aggregator-failure";
|
|
@@ -660,12 +872,31 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
660
872
|
const suggestion = agg.suggestion ?? 0;
|
|
661
873
|
log("Aggregated: " + mustFix + " must-fix + " + suggestion + " suggestion(s).");
|
|
662
874
|
|
|
875
|
+
// rfl 打分落盘(tier-1 6.6,T7):aggregator 顺手输出的弱信号 scores(reviewer
|
|
876
|
+
// 分每轮 / fix LLM 三维度 R2+)。A6:逐条形状校验(landScores 纯函数)——畸形
|
|
877
|
+
// (targetKind 缺失/round 非数/dimensions 非 object)不再静默落盘,计数进 WARN。
|
|
878
|
+
// 打分失败不影响循环(权威层是状态机客观回填,见 reconcileIssues + backfillFixRegression)。
|
|
879
|
+
{
|
|
880
|
+
const landed0 = landScores(state.scores, Array.isArray(agg.scores) ? agg.scores : [], batchIndex);
|
|
881
|
+
state.scores = landed0.scores;
|
|
882
|
+
if (landed0.landed === 0) {
|
|
883
|
+
log("WARN: aggregator scores unusable this round (landed=0, malformed=" + landed0.malformed + ") — quality scoring degraded");
|
|
884
|
+
} else if (landed0.malformed > 0) {
|
|
885
|
+
log("WARN: aggregator scores partially malformed (landed=" + landed0.landed + ", malformed=" + landed0.malformed + ") — malformed entries dropped");
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
663
889
|
// 5.1:R1 的 aggregator must_fix_ids 初始化 state.issues(数字 ID 起点)
|
|
890
|
+
// rfl 数据链(tier-1 6.1/6.3):降级条目(adjudication downgraded/unverified)
|
|
891
|
+
// 不建 issue——消费侧过滤(filterActiveIds),修复队列/ES3 校验只含活跃条目;
|
|
892
|
+
// guidance/evidence 随条目落 issues(fixer 免侦查 + 裁决可追踪)。
|
|
664
893
|
if (round === 1 && agg.must_fix_ids && agg.must_fix_ids.length > 0) {
|
|
665
894
|
if (!state.issues) state.issues = {};
|
|
895
|
+
const activeIds = new Set(filterActiveIds(agg.must_fix_ids));
|
|
666
896
|
for (const entry of agg.must_fix_ids) {
|
|
667
897
|
const id = typeof entry === "string" ? entry : entry && entry.id;
|
|
668
898
|
if (!id || state.issues[id]) continue;
|
|
899
|
+
if (!activeIds.has(id)) continue;
|
|
669
900
|
state.issues[id] = {
|
|
670
901
|
// severity 结构化(5.7):aggregator 标注 critical/major/minor,converged 终止的
|
|
671
902
|
// 「无 critical」判定依赖它;旧格式(string)默认 major(must-fix 语义)。
|
|
@@ -673,8 +904,23 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
673
904
|
severity: typeof entry === "string" ? "major" : (entry.severity || "major"),
|
|
674
905
|
status: "open",
|
|
675
906
|
history: [{ round: 1, status: "open" }], fixAttempts: 0,
|
|
907
|
+
...(entry && typeof entry.guidance === "string" && entry.guidance ? { guidance: entry.guidance } : {}),
|
|
908
|
+
...(entry && typeof entry.evidence === "string" && entry.evidence ? { evidence: entry.evidence } : {}),
|
|
676
909
|
};
|
|
677
910
|
}
|
|
911
|
+
// A8:R1 落盘后观测 guidance/evidence 缺失(数据链断点信号)
|
|
912
|
+
warnMissingFields(agg.must_fix_ids);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// rfl dormant(tier-1 6.3):adjudication 降级条目落盘(含裁决理由),R2+ prompt
|
|
916
|
+
// 注入复活通道——「降级即消失」的修复。每轮聚合后统一记录(同 id 幂等)。
|
|
917
|
+
if (agg.must_fix_ids && agg.must_fix_ids.length > 0) {
|
|
918
|
+
const before = (state.dormant || []).length;
|
|
919
|
+
state.dormant = recordDormant(state.dormant, agg.must_fix_ids, round,
|
|
920
|
+
new Set(Object.keys(state.issues || {})));
|
|
921
|
+
if (state.dormant.length > before) {
|
|
922
|
+
log("Dormant recorded: " + (state.dormant.length - before) + " adjudication-downgraded issue(s) (total " + state.dormant.length + ")");
|
|
923
|
+
}
|
|
678
924
|
}
|
|
679
925
|
|
|
680
926
|
// ── Stuck detection ─────────────────────────────────────
|
|
@@ -701,11 +947,20 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
701
947
|
// 再次被报告同样转 regressed——否则 fixed 停留 + 收敛终止组合会在默认配置下
|
|
702
948
|
// R3 即以 converged 提前终止而 must-fix 仍活跃。
|
|
703
949
|
// newFindings 统计与 needs-redesign/fixAttempts 追踪对 R2+ 新发现生效。
|
|
950
|
+
// rfl 数据链(tier-1 6.1/6.3):新发现带 origin(computeOrigin:files 与上轮 fix
|
|
951
|
+
// 触碰文件相交 = regression,否则 new;无 files 不可归因 WARN);重新上报的
|
|
952
|
+
// dormant 条目 → revived=true 回修复队列;本轮降级条目不建 issue(同 R1 过滤)。
|
|
704
953
|
if (round > 1 && state.issues && agg.must_fix_ids && agg.must_fix_ids.length > 0) {
|
|
705
954
|
let added = 0;
|
|
955
|
+
const activeIds = new Set(filterActiveIds(agg.must_fix_ids));
|
|
706
956
|
for (const entry of agg.must_fix_ids) {
|
|
707
957
|
const id = typeof entry === "string" ? entry : entry && entry.id;
|
|
708
958
|
if (!id) continue;
|
|
959
|
+
// exec-review 修复(major-2a):本轮降级条目(不在 activeIds)对新建与已追踪
|
|
960
|
+
// 转换都不生效——已追踪条目被聚合降级重报 = 聚合撤回本轮判定,不翻 regressed
|
|
961
|
+
//(对账通道 reconcileIssues 才是 fix-attempted → regressed 的权威转换点),
|
|
962
|
+
// 也不落 dormant(在 issues 活跃追踪,recordDormant 的 excludeIds 排除)。
|
|
963
|
+
if (!activeIds.has(id)) continue;
|
|
709
964
|
if (state.issues[id]) {
|
|
710
965
|
if (reconCount === 0 && (state.issues[id].status === "fix-attempted" || state.issues[id].status === "fixed")) {
|
|
711
966
|
state.issues[id].status = "regressed";
|
|
@@ -717,25 +972,65 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
717
972
|
}
|
|
718
973
|
continue;
|
|
719
974
|
}
|
|
975
|
+
// 复活置位(6.3 delta ③ 的闭环另一半):dormant 条目以活跃身份重新上报 →
|
|
976
|
+
// 回修复队列(下方正常建 issue),后续轮 prompt 不再注入它。
|
|
977
|
+
const dormantHit = (state.dormant || []).find((d) => d.id === id && d.revived !== true);
|
|
978
|
+
if (dormantHit) {
|
|
979
|
+
dormantHit.revived = true;
|
|
980
|
+
log("Dormant revived: " + id + " re-reported in round " + round);
|
|
981
|
+
}
|
|
982
|
+
const origin = computeOrigin(entry, {
|
|
983
|
+
lastModifiedFiles: state.lastModifiedFiles || [],
|
|
984
|
+
fixImpactFiles: state.fixImpactFiles || [],
|
|
985
|
+
});
|
|
986
|
+
if (!origin) log("WARN: issue " + id + " carries no files — origin not attributable");
|
|
720
987
|
state.issues[id] = {
|
|
721
988
|
firstSeen: round,
|
|
722
989
|
severity: typeof entry === "string" ? "major" : (entry.severity || "major"),
|
|
723
990
|
status: "open", openStreak: 1,
|
|
724
991
|
history: [{ round, status: "open" }], fixAttempts: 0,
|
|
992
|
+
...(origin ? { origin } : {}),
|
|
993
|
+
...(typeof entry === "object" && typeof entry.guidance === "string" && entry.guidance ? { guidance: entry.guidance } : {}),
|
|
994
|
+
...(typeof entry === "object" && typeof entry.evidence === "string" && entry.evidence ? { evidence: entry.evidence } : {}),
|
|
725
995
|
};
|
|
726
996
|
added++;
|
|
727
997
|
}
|
|
998
|
+
// A8:R2+ merge 落盘后观测 guidance/evidence 缺失(与 R1 init 同一观测口径)
|
|
999
|
+
warnMissingFields(agg.must_fix_ids);
|
|
728
1000
|
if (added > 0) log("New findings tracked: " + added + " new or re-reported issue(s) in round " + round);
|
|
729
1001
|
}
|
|
730
1002
|
|
|
731
1003
|
let stuck = { stuck: false };
|
|
732
1004
|
if (round > 1 && (reconCount > 0 || hasFixAttempted)) {
|
|
733
|
-
|
|
1005
|
+
// 对账通道的 dormant 分区(exec-review major-1 修复):reviewer 对 dormant id
|
|
1006
|
+
// 声明 not-fixed 不经此通道建 issue(复活唯一入口 = 聚合活跃重报)
|
|
1007
|
+
const reconFiltered = filterDormantFromRecon(reconSeen, reconEscalate, state.dormant);
|
|
1008
|
+
const rec = reconcileIssues(state.issues || {}, { seenIds: reconFiltered.seen, escalateIds: reconFiltered.escalate, round, stuckThreshold });
|
|
734
1009
|
state.issues = rec.issues;
|
|
735
1010
|
state.knownRemaining = rec.knownRemaining;
|
|
736
1011
|
stuck = { stuck: rec.stuck, stuckIds: rec.stuckIds };
|
|
737
1012
|
log("Reconcile: " + Object.keys(rec.issues).length + " tracked issue(s), known-remaining: " + rec.knownRemaining.length);
|
|
1013
|
+
// rfl regression 维度确定性回填(tier-1 6.6,T7):reconcile 后 regressed 数已
|
|
1014
|
+
// 定,为上轮 fix 的 score entry 填 regression(无 entry 时创建确定性 entry)
|
|
1015
|
+
if (state.fixResults && state.fixResults.length > 0) {
|
|
1016
|
+
state.scores = backfillFixRegression({
|
|
1017
|
+
scores: state.scores, fixResult: state.fixResults[state.fixResults.length - 1],
|
|
1018
|
+
issues: state.issues || {}, round, batch: batchIndex, cleanRound: false,
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
738
1021
|
} else {
|
|
1022
|
+
// A9(regression 回填边缘缺口):else 分支 = reconCount===0 且无 fix-attempted
|
|
1023
|
+
//(aggregator numeric-only fallback 等无对账数据场景)——上轮 fix 的 regressed
|
|
1024
|
+
// 数本轮不可判定,此前该场景的 regression entry 永缺。以 unverifiable 模式补
|
|
1025
|
+
// 确定性 entry(regression=null,不诚实造 10 分)。unverifiable 为终态(W3):
|
|
1026
|
+
// 回填只匹配最近一次 fix 的 entry、永不重访旧轮,该轮 regression 维度永久
|
|
1027
|
+
// 缺失(CLI 显示 n/a),同轮后续回填经终态 guard 不覆盖。
|
|
1028
|
+
if (round > 1 && state.fixResults && state.fixResults.length > 0) {
|
|
1029
|
+
state.scores = backfillFixRegression({
|
|
1030
|
+
scores: state.scores, fixResult: state.fixResults[state.fixResults.length - 1],
|
|
1031
|
+
issues: state.issues || {}, round, batch: batchIndex, mode: "unverifiable",
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
739
1034
|
const s = updateStuckState(prevMustFix, stuckCount, mustFix, stuckThreshold);
|
|
740
1035
|
stuckCount = s.stuckCount;
|
|
741
1036
|
prevMustFix = s.prevMustFix;
|
|
@@ -744,7 +1039,7 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
744
1039
|
if (stuck.stuck) {
|
|
745
1040
|
const stuckIds = (stuck.stuckIds || []).join(", ");
|
|
746
1041
|
log("Stuck: issue(s) not converging for " + stuckThreshold + " rounds: " + stuckIds + ". Stopping.");
|
|
747
|
-
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [] });
|
|
1042
|
+
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
748
1043
|
state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
|
|
749
1044
|
saveState(state);
|
|
750
1045
|
terminated = "stuck";
|
|
@@ -766,7 +1061,7 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
766
1061
|
return r.issue_id + " [" + (hist || "no history") + "]";
|
|
767
1062
|
}).join("; ");
|
|
768
1063
|
log("Needs redesign: " + ids + " not converging after " + maxFixAttempts + " fix attempts. Stopping.");
|
|
769
|
-
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [] });
|
|
1064
|
+
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
770
1065
|
state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
|
|
771
1066
|
saveState(state);
|
|
772
1067
|
terminated = "needs-redesign";
|
|
@@ -802,7 +1097,7 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
802
1097
|
.filter(([, i]) => i.status !== "fixed" && i.status !== "deferred")
|
|
803
1098
|
.map(([id]) => id);
|
|
804
1099
|
log("Converged: new findings <= " + convergeNewIssues + " for " + convergeRounds + " rounds. Batch " + batchIndex + " done, proceeding to next batch.");
|
|
805
|
-
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [] });
|
|
1100
|
+
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
806
1101
|
saveState(state);
|
|
807
1102
|
terminated = "converged";
|
|
808
1103
|
finalMessage = "Batch " + batchIndex + " round " + round + ": 新发现率收敛(连续 " + convergeRounds
|
|
@@ -814,6 +1109,41 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
814
1109
|
}
|
|
815
1110
|
}
|
|
816
1111
|
|
|
1112
|
+
// A4(全降级轮不驱动 fix,设计 §6.3 省轮次的兑现):reviewer 原始计数有 must-fix
|
|
1113
|
+
// 但 aggregator 裁决后全部降级(mustFix===0 且活跃条目为 0)时,all-clean break
|
|
1114
|
+
//(reviewer 原始计数口径,见上方 reviewResults.every 判定)拦不住本路径——不守卫
|
|
1115
|
+
// 会空转派发 fixer(fixCount++ 且无问题可修)。suggestions-only 轮不受影响
|
|
1116
|
+
//(reviewer 全 0 已在 all-clean break 提前终止)。
|
|
1117
|
+
if (mustFix === 0
|
|
1118
|
+
&& reviewResults.some((r) => r.must_fix > 0)
|
|
1119
|
+
&& (agg.must_fix_ids ? filterActiveIds(agg.must_fix_ids).length : 0) === 0) {
|
|
1120
|
+
log("All reviewer must-fix entries adjudicated down this round — no active fix queue, skipping fix stage.");
|
|
1121
|
+
// F4:此处不需要 backfillFixRegression 调用(原死防御已删)。第 3 轮探针实证
|
|
1122
|
+
// 其恒为逐字节 no-op:能到达 A4 的 R2+ 轮(round>1 且 fixResults 非空),上方
|
|
1123
|
+
// stuck 检测的 if/else 两分支已在相同门控与相同匹配键(round-1/batch)下分别
|
|
1124
|
+
// 回填过——reconcile 路径(reconCount>0 或有 fix-attempted)mode=normal、无对账
|
|
1125
|
+
// 数据路径 mode=unverifiable——regression 键必已存在(number 或 null),W3 终态
|
|
1126
|
+
// guard(键存在即不再处理)使任何后续同键调用直接短路。round=1 时 A4 的调用
|
|
1127
|
+
// 门控本就不满足。保留调用会误导维护者以为此处承担回填职责。
|
|
1128
|
+
// W5:A4 场景 reviewer 原始 must_fix>0 走了 recordAgentDirty,但裁决后全部降级
|
|
1129
|
+
// = 无真实问题(语义等价 clean)——对 active 中本轮 must_fix>0 的 reviewer 补记
|
|
1130
|
+
// recordAgentClean(快照语义对齐常规调用点:lastCleanBatch + 当时 fixCount),
|
|
1131
|
+
// 让「裁决降级=噪声」也推进跨批 skip(否则同 agent 后续批每批全价重扫)。
|
|
1132
|
+
// 本轮无 fix(fixCount 不变),批后快照比较成立。
|
|
1133
|
+
// F2:agg.must_fix_ids 缺失(aggregator JSON 无效走 parseAggregatedMd numeric
|
|
1134
|
+
// fallback 的 agg 无此键)时无条目级裁决证据——「must_fix=0」只是 md 里的一行
|
|
1135
|
+
// 数字,不能证明 reviewer 的 must-fix 是被裁决降级的噪声。此时弱证据不授予
|
|
1136
|
+
// 跨批 clean-skip(否则「跳一轮」被 shouldSkipAgent 放大为「跳到底」且该 agent
|
|
1137
|
+
// 永不再重扫),留待后续批全价复核。
|
|
1138
|
+
for (const a of agentRoundResults) {
|
|
1139
|
+
if (agg.must_fix_ids && a.must_fix > 0) recordAgentClean(state, a.name, batchIndex);
|
|
1140
|
+
}
|
|
1141
|
+
batchRounds.push({ round, mustFix: 0, suggestion: reviewResults.reduce((a, r) => a + (r.suggestion ?? 0), 0), agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
1142
|
+
saveState(state);
|
|
1143
|
+
batchClean = true;
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
817
1147
|
// ── Fix ─────────────────────────────────────────────────
|
|
818
1148
|
phase("Fix");
|
|
819
1149
|
let reportContent;
|
|
@@ -838,14 +1168,30 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
838
1168
|
"- Commit with message: `fix: review batch " + batchIndex + " round " + round + " — " + mustFix + " must-fix`"
|
|
839
1169
|
: "- Do NOT commit. Leave the fixes in the working tree (autoCommit=false).";
|
|
840
1170
|
|
|
1171
|
+
// A3(guidance 链最后一跳):活跃条目中带非空 guidance 的清单——构造确定性通道
|
|
1172
|
+
// 传给 fixer(reportContent 正文之外,per-issue 直达)。W2:先 filterActiveIds 过滤
|
|
1173
|
+
//(与修复队列/ES3 校验同口径)——降级条目(adjudication downgraded/unverified)的
|
|
1174
|
+
// guidance 不再以 "MUST-FIX GUIDANCE" 标题混给 fixer(口径不一致会诱导修复已裁决
|
|
1175
|
+
// 噪声)。normalize 后条目均为对象;string 旧格式(经 fallback 等路径)无 guidance
|
|
1176
|
+
// 自然跳过;must_fix_ids 为 undefined(fallback 路径)时 activeGuidanceIds 为空集
|
|
1177
|
+
// → fixGuidance 仍为空清单。
|
|
1178
|
+
const activeGuidanceIds = new Set(filterActiveIds(agg.must_fix_ids || []));
|
|
1179
|
+
const fixGuidance = (agg.must_fix_ids || [])
|
|
1180
|
+
.filter((e) => e && typeof e === "object" && typeof e.guidance === "string" && e.guidance.trim()
|
|
1181
|
+
&& activeGuidanceIds.has(e.id))
|
|
1182
|
+
.map((e) => ({ id: e.id, guidance: e.guidance }));
|
|
1183
|
+
|
|
1184
|
+
const fixT0 = Date.now();
|
|
1185
|
+
const fixPromptBuilt = buildFixPrompt({
|
|
1186
|
+
header: "Fix round " + round + " (batch " + batchIndex + ")",
|
|
1187
|
+
reportContent,
|
|
1188
|
+
fixPrompt,
|
|
1189
|
+
commitInstr,
|
|
1190
|
+
caution: agg.fixes_caution && agg.fixes_caution.length ? agg.fixes_caution : [],
|
|
1191
|
+
guidance: fixGuidance,
|
|
1192
|
+
});
|
|
841
1193
|
const fxRaw = await agent({
|
|
842
|
-
prompt:
|
|
843
|
-
header: "Fix round " + round + " (batch " + batchIndex + ")",
|
|
844
|
-
reportContent,
|
|
845
|
-
fixPrompt,
|
|
846
|
-
commitInstr,
|
|
847
|
-
caution: agg.fixes_caution && agg.fixes_caution.length ? agg.fixes_caution : [],
|
|
848
|
-
}),
|
|
1194
|
+
prompt: fixPromptBuilt,
|
|
849
1195
|
schema: fixSchema,
|
|
850
1196
|
// S4:fixAgent = agentRef 路径(主线程按路径加载 + frontmatter model 传播);
|
|
851
1197
|
// 未传保持现状(通用 subagent + 内联 fixPrompt)。
|
|
@@ -856,6 +1202,11 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
856
1202
|
returnMeta: true,
|
|
857
1203
|
...(FIX_DEF && FIX_DEF.path ? { agent: FIX_DEF.path } : {}),
|
|
858
1204
|
});
|
|
1205
|
+
phaseTimings.fix = [fixT0, Date.now()];
|
|
1206
|
+
recordCall(buildCallRecord({
|
|
1207
|
+
batch: batchIndex, round, role: "fixer",
|
|
1208
|
+
name: (FIX_DEF && FIX_DEF.name) || "fix", model: MODEL, prompt: fixPromptBuilt, promptMode: null, meta: fxRaw,
|
|
1209
|
+
}));
|
|
859
1210
|
|
|
860
1211
|
// returnMeta 下 fxRaw = {value, error}:先查 error(失败分支可达,MF-1),再对 value 做 parseResult
|
|
861
1212
|
if (fxRaw && typeof fxRaw === "object" && fxRaw.error) {
|
|
@@ -863,7 +1214,7 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
863
1214
|
// 与 review 路径(raw.error → review-failure)对齐:结构化终止而非静默当成功——
|
|
864
1215
|
// 否则 fixed_count 缺失被 `?? mustFix` 回退,totalFixed 虚增且 must_fix 不降白跑轮次(MF-1)。
|
|
865
1216
|
log("Fix agent failed, stopping.");
|
|
866
|
-
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [] });
|
|
1217
|
+
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
867
1218
|
state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
|
|
868
1219
|
saveState(state);
|
|
869
1220
|
terminated = "fix-failure";
|
|
@@ -875,7 +1226,7 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
875
1226
|
const fixResult = fx ? normalizeFixResult(fx) : null;
|
|
876
1227
|
if (!fixResult) {
|
|
877
1228
|
log("Fix agent failed, stopping.");
|
|
878
|
-
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [] });
|
|
1229
|
+
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
879
1230
|
state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
|
|
880
1231
|
saveState(state);
|
|
881
1232
|
terminated = "fix-failure";
|
|
@@ -889,7 +1240,9 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
889
1240
|
// 判 violation)。任一违规 → fix-failure(结构化终止)。trackedIssues 传入
|
|
890
1241
|
// state.issues——deferred severity 与追踪表交叉核对(MF-4):must-fix 被标 minor
|
|
891
1242
|
// 塞进 deferred 的逃逸路径在追踪表面前失效(追踪 severity 为准)。
|
|
892
|
-
|
|
1243
|
+
// rfl(tier-1 6.3):mustFixIds 传 filterActiveIds 结果——降级条目不占修复队列
|
|
1244
|
+
//(must_fix 计数由 aggregator 按非降级条目报,两侧口径一致)。
|
|
1245
|
+
const es3Violations = validateFixResult(fixResult, filterActiveIds(agg.must_fix_ids), state.issues);
|
|
893
1246
|
if (es3Violations.length > 0) {
|
|
894
1247
|
// m7: violation 分两类——deferred 非 minor / must-fix 漏修(must-fix-not-fixed),
|
|
895
1248
|
// finalMessage 文案区分:统一文案会把漏修误报成 defer 违规,误导修复方向
|
|
@@ -899,7 +1252,7 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
899
1252
|
: "deferred 含非 minor 条目(must-fix 不得 defer)— " + v.issue_id + "(" + v.severity + ")"
|
|
900
1253
|
);
|
|
901
1254
|
log("ES3 violation: " + JSON.stringify(es3Violations));
|
|
902
|
-
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [] });
|
|
1255
|
+
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
|
|
903
1256
|
state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
|
|
904
1257
|
saveState(state);
|
|
905
1258
|
terminated = "fix-failure";
|
|
@@ -984,7 +1337,7 @@ for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
|
|
|
984
1337
|
modifiedFiles = out ? out.split("\n") : [];
|
|
985
1338
|
} catch { /* empty */ }
|
|
986
1339
|
}
|
|
987
|
-
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles });
|
|
1340
|
+
batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles, phaseTimings });
|
|
988
1341
|
// M4: 批内即时字段——下轮 scoped 分支(recheckAfterFix=true)从
|
|
989
1342
|
// state.lastModifiedFiles 读本批 fix 的真实改动文件(git 实测兜底)
|
|
990
1343
|
state.lastModifiedFiles = modifiedFiles;
|