@zhushanwen/pi-subagent-workflow 8.5.0 → 8.7.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.
Files changed (336) hide show
  1. package/package.json +13 -16
  2. package/relay/relay.mjs +1 -1
  3. package/src/host/__tests__/pi-host.test.ts +214 -0
  4. package/src/host/pi-host.ts +109 -0
  5. package/src/index.ts +142 -32
  6. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  7. package/src/injectors/__tests__/engine-section-stability.test.ts +341 -0
  8. package/src/injectors/__tests__/subagent-list-injector.test.ts +5 -5
  9. package/src/injectors/__tests__/workflow-list-injector.test.ts +5 -5
  10. package/src/injectors/engine-awareness.ts +123 -0
  11. package/src/injectors/model-list-injector.ts +1 -1
  12. package/src/injectors/subagent-list-injector.ts +8 -8
  13. package/src/injectors/workflow-list-injector.ts +8 -8
  14. package/src/interface/__tests__/detectors.test.ts +3 -3
  15. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +157 -0
  16. package/src/interface/__tests__/subagent-tool-prompt.test.ts +12 -0
  17. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +2 -2
  18. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
  19. package/src/interface/bg-notify-render.ts +34 -13
  20. package/src/interface/commands.ts +3 -3
  21. package/src/interface/format.ts +2 -2
  22. package/src/interface/helpers.ts +3 -3
  23. package/src/interface/list-component.ts +4 -4
  24. package/src/interface/list-shared.ts +2 -2
  25. package/src/interface/list-view.ts +2 -2
  26. package/src/interface/subagent-actions.ts +288 -19
  27. package/src/interface/subagent-tool-schema.ts +163 -0
  28. package/src/interface/subagent-tool.ts +65 -129
  29. package/src/interface/subagents.ts +5 -5
  30. package/src/interface/tool-render.ts +34 -2
  31. package/src/interface/tool-workflow-script.ts +4 -4
  32. package/src/interface/tool-workflow.ts +6 -6
  33. package/src/interface/views/WorkflowsView.ts +5 -5
  34. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  35. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  36. package/src/interface/views/detail-content.ts +4 -4
  37. package/src/interface/views/format.ts +4 -4
  38. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +132 -18
  39. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  40. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  41. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  42. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  43. package/src/execution/__tests__/alive-store.test.ts +0 -147
  44. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  45. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  46. package/src/execution/__tests__/bg-notify-render.test.ts +0 -256
  47. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  48. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -597
  49. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  50. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  51. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  52. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  53. package/src/execution/__tests__/config.test.ts +0 -110
  54. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  55. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  56. package/src/execution/__tests__/delivery-methods.test.ts +0 -385
  57. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  58. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  59. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  60. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  61. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -189
  62. package/src/execution/__tests__/execution-record.test.ts +0 -1284
  63. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  64. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  65. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -138
  66. package/src/execution/__tests__/format.test.ts +0 -458
  67. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  68. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  69. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  70. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  71. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -238
  72. package/src/execution/__tests__/host-mode.test.ts +0 -87
  73. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  74. package/src/execution/__tests__/index-session-start.test.ts +0 -593
  75. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  76. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -337
  77. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  78. package/src/execution/__tests__/list-component.test.ts +0 -401
  79. package/src/execution/__tests__/list-fields.test.ts +0 -109
  80. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  81. package/src/execution/__tests__/model-resolver.test.ts +0 -413
  82. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  83. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  84. package/src/execution/__tests__/notifier-flush.test.ts +0 -574
  85. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  86. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  87. package/src/execution/__tests__/output-collector.test.ts +0 -61
  88. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  89. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  90. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  91. package/src/execution/__tests__/record-store.test.ts +0 -1057
  92. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  93. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  94. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  95. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  96. package/src/execution/__tests__/relay-env.test.ts +0 -42
  97. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  98. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -644
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  105. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  106. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  107. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  108. package/src/execution/__tests__/session-pending.test.ts +0 -197
  109. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  110. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -311
  112. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  113. package/src/execution/__tests__/spawn-args.test.ts +0 -434
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  117. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  118. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  119. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  120. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  122. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  124. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  125. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  126. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  127. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -506
  128. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  129. package/src/execution/__tests__/timeout-integration.test.ts +0 -397
  130. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  131. package/src/execution/__tests__/tool-action.test.ts +0 -385
  132. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  133. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  134. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  135. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  136. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  137. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  138. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  139. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  140. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  141. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  142. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  143. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  144. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  145. package/src/execution/agent-registry.ts +0 -193
  146. package/src/execution/agent-result-mapper.ts +0 -90
  147. package/src/execution/alive-store.ts +0 -92
  148. package/src/execution/argv-mirror.ts +0 -109
  149. package/src/execution/best-effort.ts +0 -38
  150. package/src/execution/channel-registry-access.ts +0 -144
  151. package/src/execution/concurrency-pool.ts +0 -116
  152. package/src/execution/config.ts +0 -96
  153. package/src/execution/dialog-queue.ts +0 -330
  154. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  155. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  156. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  158. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  159. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  160. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  161. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  162. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  163. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  164. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  165. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  166. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  167. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  168. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  169. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  170. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  171. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  172. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  173. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  174. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  175. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  176. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  177. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  178. package/src/execution/engine/common/data-dir.ts +0 -62
  179. package/src/execution/engine/common/errors.ts +0 -183
  180. package/src/execution/engine/common/event-journal.ts +0 -254
  181. package/src/execution/engine/common/journal-replay.ts +0 -62
  182. package/src/execution/engine/common/kill-chain.ts +0 -221
  183. package/src/execution/engine/common/nesting-guard.ts +0 -50
  184. package/src/execution/engine/common/persona-router.ts +0 -108
  185. package/src/execution/engine/common/pool-manager.ts +0 -226
  186. package/src/execution/engine/common/schema-emulation.ts +0 -189
  187. package/src/execution/engine/common/session-view-projection.ts +0 -51
  188. package/src/execution/engine/engine-discovery.ts +0 -65
  189. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  190. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  191. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  192. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  193. package/src/execution/engine/engines/pi/reader.ts +0 -48
  194. package/src/execution/engine/engines/pi/registration.ts +0 -35
  195. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  197. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  198. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  199. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  200. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  201. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -567
  204. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  205. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  206. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  207. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  208. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  209. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  210. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  211. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -648
  212. package/src/execution/engine/host-task-spec.ts +0 -47
  213. package/src/execution/engine/model-prompt.ts +0 -59
  214. package/src/execution/engine/paths.ts +0 -42
  215. package/src/execution/engine/port.ts +0 -153
  216. package/src/execution/engine/registry.ts +0 -123
  217. package/src/execution/engine/routing.ts +0 -218
  218. package/src/execution/engine/types.ts +0 -304
  219. package/src/execution/execute-options-mapper.ts +0 -111
  220. package/src/execution/execution-record.ts +0 -906
  221. package/src/execution/finalize-record.ts +0 -251
  222. package/src/execution/finalized-marker.ts +0 -51
  223. package/src/execution/get-state-handshake.ts +0 -104
  224. package/src/execution/host-mode.ts +0 -56
  225. package/src/execution/idle-gc.ts +0 -47
  226. package/src/execution/lifecycle-manager.ts +0 -491
  227. package/src/execution/lifecycle-predicates.ts +0 -65
  228. package/src/execution/manifest-store.ts +0 -256
  229. package/src/execution/model-config-service.ts +0 -204
  230. package/src/execution/model-resolver.ts +0 -276
  231. package/src/execution/notifier.ts +0 -260
  232. package/src/execution/output-collector.ts +0 -88
  233. package/src/execution/path-encoding.ts +0 -60
  234. package/src/execution/pi-invocation.ts +0 -120
  235. package/src/execution/record-entry.ts +0 -132
  236. package/src/execution/record-store.ts +0 -1234
  237. package/src/execution/relay-env.ts +0 -37
  238. package/src/execution/session-context-resolver.ts +0 -64
  239. package/src/execution/session-file-gc.ts +0 -120
  240. package/src/execution/session-pending.ts +0 -164
  241. package/src/execution/session-reconstructor.ts +0 -678
  242. package/src/execution/session-runner.ts +0 -1548
  243. package/src/execution/sessions-index.ts +0 -304
  244. package/src/execution/spawn-event-adapter.ts +0 -363
  245. package/src/execution/stdin-writer.ts +0 -198
  246. package/src/execution/stream-sink.ts +0 -126
  247. package/src/execution/subagent-service.ts +0 -2119
  248. package/src/execution/subprocess-agent-runner.ts +0 -303
  249. package/src/execution/temp-prompt.ts +0 -62
  250. package/src/execution/tombstone-store.ts +0 -72
  251. package/src/execution/turn-limiter.ts +0 -102
  252. package/src/execution/types.ts +0 -910
  253. package/src/execution/ui-channels.ts +0 -216
  254. package/src/execution/ui-interaction-model.ts +0 -48
  255. package/src/execution/ui-request-handler-factory.ts +0 -212
  256. package/src/execution/ui-request-observability.ts +0 -81
  257. package/src/execution/ui-request-queue.ts +0 -174
  258. package/src/execution/worktree-manager.ts +0 -688
  259. package/src/execution/worktree-registry.ts +0 -222
  260. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -336
  261. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -208
  262. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -157
  263. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  264. package/src/orchestration/__tests__/concurrency-gate.test.ts +0 -125
  265. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  266. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -724
  267. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  268. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  269. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  270. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -314
  271. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  272. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  273. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -277
  274. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  275. package/src/orchestration/__tests__/lifecycle.test.ts +0 -625
  276. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -1971
  277. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  278. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -157
  279. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  280. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  281. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  282. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -520
  283. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  284. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -110
  285. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  286. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  287. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  288. package/src/orchestration/agent-opts-resolver.ts +0 -93
  289. package/src/orchestration/args-validator.ts +0 -127
  290. package/src/orchestration/concurrency-gate.ts +0 -69
  291. package/src/orchestration/config-loader.ts +0 -315
  292. package/src/orchestration/error-recovery.ts +0 -862
  293. package/src/orchestration/execute-agent-call.ts +0 -217
  294. package/src/orchestration/launcher.ts +0 -410
  295. package/src/orchestration/lifecycle.ts +0 -396
  296. package/src/orchestration/models/__tests__/budget.test.ts +0 -367
  297. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  298. package/src/orchestration/models/agent-call.ts +0 -83
  299. package/src/orchestration/models/budget.ts +0 -148
  300. package/src/orchestration/models/ports.ts +0 -164
  301. package/src/orchestration/models/run-runtime.ts +0 -89
  302. package/src/orchestration/models/run-spec.ts +0 -83
  303. package/src/orchestration/models/run-state.ts +0 -44
  304. package/src/orchestration/models/trace.ts +0 -183
  305. package/src/orchestration/models/types.ts +0 -292
  306. package/src/orchestration/models/workflow-run.ts +0 -254
  307. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  308. package/src/orchestration/models/workflow-script.ts +0 -117
  309. package/src/orchestration/script-lint.ts +0 -766
  310. package/src/orchestration/skill-discovery.ts +0 -92
  311. package/src/orchestration/worker-handle.ts +0 -115
  312. package/src/orchestration/worker-host.ts +0 -98
  313. package/src/orchestration/worker-script-builder.ts +0 -408
  314. package/src/orchestration/workflow-files.ts +0 -85
  315. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  316. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  317. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  318. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  319. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  320. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  321. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  322. package/src/shared/agent-event.ts +0 -13
  323. package/src/shared/agent-ref.ts +0 -57
  324. package/src/shared/meta-parser.ts +0 -261
  325. package/src/shared/resource-discovery.ts +0 -811
  326. package/src/shared/resource-meta.ts +0 -65
  327. package/src/shared/schema-jsonify.ts +0 -56
  328. package/src/shared/xml-injection.ts +0 -35
  329. package/workflows/README.md +0 -81
  330. package/workflows/_shared/agent-refs.cjs +0 -40
  331. package/workflows/chain.js +0 -131
  332. package/workflows/map-reduce.js +0 -174
  333. package/workflows/parallel.js +0 -148
  334. package/workflows/review-fix-loop-utils.cjs +0 -1307
  335. package/workflows/review-fix-loop.js +0 -1396
  336. package/workflows/scatter-gather.js +0 -178
@@ -1,1396 +0,0 @@
1
- // review-fix-loop.js — 通用多批审查-修复循环(内置 workflow)
2
- //
3
- // 模式:多批(batch)串行,批内循环(round):并行 review → aggregate → fix → 重审。
4
- // 批次用于表达前置依赖(fallow 静态分析等前置检查必须先完成,后续审查才有意义)。
5
- // 批内某 agent 已无 must-fix(critical/major)则后续轮跳过,优化 token 效率。
6
- //
7
- // 用法:
8
- // workflow run review-fix-loop --args targetType=git-diff target=main \
9
- // batch1="/path/reviewer-a.md,/path/reviewer-b.md" autoCommit=true
10
- // workflow run review-fix-loop --args targetType=file target=/path/to/doc.md \
11
- // batch1="/path/doc-reviewer.md" autoCommit=false
12
- //
13
- // S4 路径统一:batch1..batchN/fixAgent 值 = agentRef(.md 绝对路径,<available_subagents> 的
14
- // <location>);fallowScan=true 独立参数前置插入静态分析批次(不占 batchN)。
15
- // ⚠️ 唯一带写操作的内置 workflow:fix 阶段会修改文件(autoCommit=true 时 commit)。
16
- // ⚠️ lintScript 约束(本脚本已遵守):含 parallel() 入口,禁止 bare IIFE;
17
- // agent() 调用顺序确定(批次按配置顺序稳定排序,callId 重放安全)。
18
- //
19
- // ⚠️ 与 main 的 4.0.0 版分叉(merge 时 add/add 冲突,刻意决策记录):
20
- // 本版(feat-recursive-optimize)保留——纯函数拆到 review-fix-loop-utils.cjs(vitest 覆盖)、
21
- // 支持 fallow-scan 前置批次、无默认批次(batch1..batchN/agents 必传)、recheckAfterFix 默认 false。
22
- // main 4.0.0 版自包含 677 行、缺批次参数时默认单批 ["reviewer"]、recheckAfterFix 默认 false。
23
- // merge 时保留本版(功能更全),详见 .changeset/tidy-waves-description-phase-lint.md。
24
-
25
- /* @pi-meta
26
- name: review-fix-loop
27
- description: >-
28
- 多批串行审查-修复循环:批内并行 review 聚合 must-fix 后迭代修复直到 clean
29
- (唯一带写操作与 commit 副作用的内置 workflow,autoCommit 默认 false)
30
- when: 用户要 review 并迭代修复至 clean
31
- notFor: 单纯审查不改代码
32
- phases: [Review, Fix]
33
- parameters:
34
- type: object
35
- properties:
36
- targetType: { type: string, enum: [git-diff, file, dir, text] }
37
- target: { type: string, minLength: 1, pattern: '\S' }
38
- autoCommit: { type: boolean, default: false }
39
- maxRounds: { type: integer, default: 10, minimum: 1 }
40
- stuckThreshold: { type: integer, default: 3, minimum: 1 }
41
- skipCleanAgents: { type: boolean, default: true }
42
- recheckAfterFix: { type: boolean, default: false }
43
- fixAgent: { type: string }
44
- maxFixAttempts: { type: integer, default: 2, minimum: 1 }
45
- convergeNewIssues: { type: integer, default: 1, minimum: 1 }
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 }
48
- reviewPrompt: { type: string }
49
- fixPrompt: { type: string }
50
- fallowScan: { type: boolean, default: false }
51
- agents: { type: string }
52
- batchNames: { type: string }
53
- patternProperties:
54
- "^batch\\d+$":
55
- type: string
56
- description: 任意 batchN 编号,至少一个;值为 agent .md 绝对路径(逗号分隔多 agent)
57
- required: [targetType, target]
58
- usage: |
59
- ## 使用说明
60
- - batch1..batchN 与 agents 互斥(至少传一个 batchN);值 = agentRef(.md 绝对路径,<available_subagents> 的 <location>)
61
- - fixAgent:fix 阶段执行者(agentRef),缺省用通用 subagent + 内联 fixPrompt
62
- - fallowScan=true 仅 targetType=git-diff 合法(前置静态分析批次,不占 batchN)
63
- - 示例:workflow run review-fix-loop --args targetType=git-diff target=main batch1="/path/fallow-agent.md,/path/reviewer.md" autoCommit=true
64
- */
65
-
66
- // ── 参数解析 + 白名单校验(fail-fast) ────────────────────────────
67
-
68
- function fail(msg) {
69
- throw new Error("review-fix-loop: " + msg);
70
- }
71
-
72
- // ── 可测纯函数模块 ────────────────────────────────────────────────
73
- // 参数校验(白名单,类型校验由 m3 args-validator schema 接管)/批次解析/聚合结果解析/审查指令构建
74
- // 的纯函数在 review-fix-loop-utils.cjs,
75
- // vitest 单测见 src/__tests__/review-fix-loop-utils.test.ts。
76
- // worker 运行时经 workerData.scriptPath 定位自身目录——内置 workflow 在 npm 包内,
77
- // process.cwd() 是用户项目目录,不能作为锚点;其他引擎无 workerData 时回退 cwd。
78
- const {
79
- TARGET_TYPES,
80
- VALID_ARG_KEYS,
81
- parseBatches,
82
- resolveBatchNames,
83
- buildReviewInstruction,
84
- lockReviewBase,
85
- buildScopedRecheckPrompt,
86
- wrapUntrusted,
87
- buildFixPrompt,
88
- buildR1ReviewPrompt,
89
- buildR2ReviewPrompt,
90
- buildAggregatorPrompt,
91
- resolveReviewReportPath,
92
- normalizeFixResult,
93
- validateFixResult,
94
- findIssueKey,
95
- reconcileIssues,
96
- normalizeReviewResult,
97
- computeKnownRemaining,
98
- checkConvergence,
99
- findNeedsRedesign,
100
- parseResult,
101
- normalizeAggregatorResult,
102
- parseAggregatedMd,
103
- resolveRunRoot,
104
- computeOrigin,
105
- recordDormant,
106
- filterActiveIds,
107
- filterDormantFromRecon,
108
- landScores,
109
- countMissingFields,
110
- backfillFixRegression,
111
- applyCleanRoundBackfill,
112
- resolveAggregatorModel,
113
- resolveAgentDefs,
114
- recordAgentClean,
115
- recordAgentDirty,
116
- shouldSkipAgent,
117
- updateStuckState,
118
- resolveBatchTerminated,
119
- } = require(
120
- (typeof workerData !== "undefined" && workerData && typeof workerData.scriptPath === "string"
121
- ? require("path").dirname(workerData.scriptPath)
122
- : process.cwd()) + "/review-fix-loop-utils.cjs"
123
- );
124
-
125
- // 白名单校验:未知参数名(防 batchX 拼错如 batchl)→ 报错
126
- for (const key of Object.keys($ARGS)) {
127
- if (VALID_ARG_KEYS.has(key)) continue;
128
- if (/^batch\d+$/.test(key)) continue;
129
- fail("未知参数: " + key + "(合法参数: targetType/target/batch1..batchN/agents/batchNames/reviewPrompt/fixPrompt/autoCommit/maxRounds/stuckThreshold/skipCleanAgents/recheckAfterFix/fallowScan/fixAgent/maxFixAttempts/convergeNewIssues/convergeRounds/aggregatorModel)");
130
- }
131
-
132
- const targetType = $ARGS.targetType;
133
- if (!TARGET_TYPES.includes(targetType)) {
134
- fail("targetType 必填且必须是枚举之一: " + TARGET_TYPES.join("/") + "(实际: " + JSON.stringify(targetType) + ")");
135
- }
136
- const target = typeof $ARGS.target === "string" ? $ARGS.target.trim() : "";
137
- if (!target) fail("target 必填(git-diff 时传 base ref 如 main;file 传路径;dir 传目录;text 传描述)");
138
-
139
- const reviewPrompt = typeof $ARGS.reviewPrompt === "string" && $ARGS.reviewPrompt.trim()
140
- ? $ARGS.reviewPrompt.trim()
141
- : "审查变更/目标是否存在:逻辑错误、边界条件、类型不安全、遗漏、回归风险、代码规范问题。发现问题分三级:critical(严重,必须修)/ major(重要,应当修)/ minor(轻微,建议修)。critical+major 计入 must_fix。";
142
- const fixPrompt = typeof $ARGS.fixPrompt === "string" && $ARGS.fixPrompt.trim()
143
- ? $ARGS.fixPrompt.trim()
144
- : "修复全部 must-fix 问题(critical/major)。最小正确修复,不做重构、不做风格改动。";
145
- // 字符串强制转换(m2 exec-review MAJOR-1):LLM 可能以字符串传布尔/整数(旧 normalizeBool
146
- // 正是为此防御——"false" ?? false 是 truthy 会误触发 commit)。m3 args-validator
147
- // (chokepoint + coerceTypes)上线后此处为双保险,直接执行路径仍受保护。
148
- const coerceBool = (v, fallback) =>
149
- typeof v === "boolean" ? v : v === "true" ? true : v === "false" ? false : fallback;
150
- const coerceInt = (v, fallback) =>
151
- typeof v === "number" && Number.isInteger(v)
152
- ? v
153
- : typeof v === "string" && /^\d+$/.test(v.trim())
154
- ? parseInt(v, 10)
155
- : fallback;
156
-
157
- const autoCommit = coerceBool($ARGS.autoCommit, false);
158
- const maxRounds = coerceInt($ARGS.maxRounds, 10);
159
- const stuckThreshold = coerceInt($ARGS.stuckThreshold, 3);
160
- const skipCleanAgents = coerceBool($ARGS.skipCleanAgents, true);
161
- // 默认 recheckAfterFix=false:clean agent 下轮跳过(与 skipCleanAgents=true 字面语义一致),
162
- // RC-5(fix 后全批全量重审放大 token)在默认场景消失。传 true 启用可选强回归模式:fix 后重派
163
- // 全批,clean agent 走限定 prompt(buildScopedRecheckPrompt,只审 modifiedFiles,5.5)。
164
- const recheckAfterFix = coerceBool($ARGS.recheckAfterFix, false);
165
- // fixAgent(5.3):值语义同 batchN 的 agent 项(内置名 / agent.md 路径),解析复用
166
- // resolveAgentDefs 白名单与加载逻辑。传入时 fix 阶段用 agent({agent: ...}) 派发(代码场景
167
- // 的 verify 命令写在该 agent.md 内);未传保持现状(通用 subagent + 内联 prompt)。
168
- const FIX_AGENT_RAW = typeof $ARGS.fixAgent === "string" && $ARGS.fixAgent.trim()
169
- ? $ARGS.fixAgent.trim() : undefined;
170
- const FIX_DEF = FIX_AGENT_RAW ? resolveAgentDefs([FIX_AGENT_RAW])[0] : null;
171
- // 5.7 收敛终止参数:maxFixAttempts(needs-redesign 阈值,RC-7)/ convergeNewIssues +
172
- // convergeRounds(新发现率收敛阈值)
173
- const maxFixAttempts = coerceInt($ARGS.maxFixAttempts, 2);
174
- const convergeNewIssues = coerceInt($ARGS.convergeNewIssues, 1);
175
- const convergeRounds = coerceInt($ARGS.convergeRounds, 2);
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);
182
-
183
- // base 锁定(RC-6,5.6):git-diff 场景 run 启动时锁定 base commit,全程用锁定 hash 构造
184
- // diff 指令,防止 run 期间 base ref 被更新导致各轮 diff 范围不一致。rev-parse 失败(非 git
185
- // 目录 / ref 不存在)降级用原 ref 并 warn,锁定结果随 state.meta.baseHash 记录。
186
- const lockedBase = lockReviewBase(targetType, target);
187
- if (targetType === "git-diff") {
188
- if (lockedBase.hash) {
189
- log("Locked review base: " + target + " -> " + lockedBase.hash);
190
- } else {
191
- log("WARN: git rev-parse " + target + " failed, falling back to ref for diff base: " + target);
192
- }
193
- }
194
- const reviewInstruction = buildReviewInstruction(targetType, lockedBase.base);
195
-
196
- // 批次解析:batch1..batchN(缺号报错)/ agents 简写;无默认批次——缺批次参数时
197
- // parseBatches 直接 fail-fast(与头注释「batch1..batchN/agents 必传」一致)
198
- const rawBatches = parseBatches($ARGS, fail);
199
-
200
- // S4:fallowScan 独立参数(不由 batchN 触发——batchN 值域 = agentRef 路径)。
201
- // fallow 作为内置首批前置插入(fallow-scan 保留字在 resolveAgentDefs 内解析)。
202
- const fallowScan = coerceBool($ARGS.fallowScan, false);
203
- if (fallowScan && targetType !== "git-diff") {
204
- fail("fallowScan 只支持 targetType=git-diff(它审查 git 变更的静态分析),实际 targetType=" + targetType);
205
- }
206
- const BATCHES = fallowScan ? [["fallow-scan"], ...rawBatches] : rawBatches;
207
-
208
- // batchNames(数量校验)
209
- const rawBatchNames = typeof $ARGS.batchNames === "string" && $ARGS.batchNames.trim()
210
- ? $ARGS.batchNames.split(",").map((s) => s.trim()).filter(Boolean)
211
- : [];
212
- const BATCH_NAMES = resolveBatchNames(rawBatchNames, BATCHES, fail);
213
-
214
- // ── Schemas ─────────────────────────────────────────────────────────
215
-
216
- const reviewerSchema = {
217
- type: "object",
218
- properties: {
219
- report_file: { type: "string", description: "Absolute path to the written review report (.md)" },
220
- report_content: { type: "string", description: "Full markdown report body (for schema-only agents without write tool; workflow writes it to <roundDir>/<report>.md)" },
221
- must_fix: { type: "number", description: "Number of must-fix (critical+major) issues found" },
222
- suggestion: { type: "number", description: "Number of suggestion-level (minor) issues found" },
223
- reconciliation: {
224
- type: "array",
225
- items: {
226
- type: "object",
227
- properties: {
228
- prev_id: { type: "string", description: "Issue id from the previous round (reconciliation continuation)" },
229
- status: { type: "string", description: "fixed / not-fixed / regressed / escalate — only fixed counts as resolved; fix result claiming fixed is NOT evidence; escalate = a DEFERRED issue whose context was changed by this round's fix (workflow re-opens it for fixing)" },
230
- evidence: { type: "string", description: "What was read/confirmed (file + what changed)" },
231
- },
232
- required: ["prev_id", "status"],
233
- },
234
- description: "Reconciliation table (structured): return [] on R1 (no previous round); on R2+ every previous issue_id must have a status entry",
235
- },
236
- },
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"],
241
- };
242
-
243
- const aggregatorSchema = {
244
- type: "object",
245
- properties: {
246
- report_file: { type: "string", description: "Absolute path to aggregated.md" },
247
- must_fix: { type: "number", description: "Total must-fix after dedup across all dimensions, counting adjudication=evidence entries only" },
248
- suggestion: { type: "number", description: "Total suggestions after dedup across all dimensions" },
249
- must_fix_ids: {
250
- type: "array",
251
- // M1: 支持 [{id, severity}] 对象(severity: critical/major/minor——converged 终止的
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 按非降级条目报)。
257
- items: {
258
- oneOf: [
259
- { type: "string" },
260
- {
261
- type: "object",
262
- required: ["id"],
263
- properties: {
264
- id: { type: "string" },
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)" },
273
- },
274
- },
275
- ],
276
- },
277
- description: "Issue ids of the deduplicated must-fix list (MF-1..N), matching the first column of the markdown table",
278
- },
279
- fixes_caution: {
280
- type: "array",
281
- items: { type: "string" },
282
- description: "Caution entries for weak-evidence or high-risk claims (passed to fix stage)",
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
- },
301
- },
302
- required: ["report_file", "must_fix", "suggestion"],
303
- };
304
-
305
- // fix schema(5.3):object[](issue_id/description/self_check/affected_files)+ deferred。
306
- // 兼容性:normalizeFixResult 对旧格式(fixes string[]、无 deferred)兜底解析。
307
- const fixSchema = {
308
- type: "object",
309
- properties: {
310
- fixed_count: { type: "number", description: "Number of issues fixed" },
311
- fixes: {
312
- type: "array",
313
- items: {
314
- type: "object",
315
- properties: {
316
- issue_id: { type: "string", description: "Issue identifier from the aggregated report" },
317
- description: { type: "string", description: "One-line description of the fix" },
318
- self_check: { type: "string", description: "grep command + hit count + sync action proving the fix is complete" },
319
- affected_files: { type: "array", items: { type: "string" }, description: "Files touched by this fix + files checked/synced as reference points" },
320
- },
321
- // m3: issue_id 是 ES3 交叉校验(must-fix 必须全进 fixes[])的匹配键,必填
322
- required: ["issue_id"],
323
- },
324
- },
325
- deferred: {
326
- type: "array",
327
- items: {
328
- type: "object",
329
- properties: {
330
- issue_id: { type: "string" },
331
- severity: { type: "string", description: "Must be minor — critical/major must not be deferred" },
332
- reason: { type: "string", description: "Concrete cost description: which files/mechanisms involved, why high cost, suggested follow-up" },
333
- },
334
- },
335
- },
336
- },
337
- required: ["fixed_count"],
338
- };
339
-
340
- // ── Per-run isolation: runId-scoped directories ─────────────────────
341
-
342
- const fs = require("fs");
343
- const path = require("path");
344
- const os = require("os");
345
-
346
- const RUN_ID = ($ARGS._runId && typeof $ARGS._runId === "string") ? $ARGS._runId : "run-" + Date.now();
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
- }
354
- const STATE_FILE = RUN_ROOT + "/state.json";
355
-
356
- log("Run directory: " + RUN_ROOT);
357
-
358
- // ── Startup fail-fast: validate agent ref paths exist (ADR-0003 D6) ─
359
- // 启动期校验所有 batchN/fixAgent 路径存在,不存在立即报错(带 location 恢复指引),
360
- // 避免跑到 round 中段 agent-call 时 loadByPath 失败才暴露。FALLOW_DEF(isFallow,
361
- // 无 path)跳过——它是内置工具标记非文件路径。
362
- function validateAgentPaths(defs) {
363
- for (const def of defs) {
364
- if (def.isFallow || !def.path) continue;
365
- // MF-2:与 normalizeRef(src/shared/agent-ref.ts)对齐——~/ 前缀展开为 homedir 后再 statSync。
366
- // resolveAgentDefs 接受 ~/ 前缀、normalizeRef 运行时也展开,此处不展开会「先接受后拒绝」误报 ENOENT。
367
- const expanded = def.path.startsWith("~/")
368
- ? path.join(os.homedir(), def.path.slice(2))
369
- : def.path;
370
- try {
371
- fs.statSync(expanded);
372
- } catch {
373
- fail("Agent file not found: " + def.path + ". Check <available_subagents> <location> for valid agent refs (absolute .md path).");
374
- }
375
- }
376
- }
377
- const startupDefs = [];
378
- for (const batch of BATCHES) {
379
- startupDefs.push(...resolveAgentDefs(batch));
380
- }
381
- if (FIX_DEF) startupDefs.push(FIX_DEF);
382
- validateAgentPaths(startupDefs);
383
-
384
- // ── State management (persistent, atomic writes) ────────────────────
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
-
408
- function loadState() {
409
- try {
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;
421
- } catch {
422
- return freshState();
423
- }
424
- }
425
-
426
- // rfl 仪表:终止原因快照声明上移(saveState 引用;所有实际调用发生在
427
- // 主循环初始化后,TDZ 不触发)。CLI list/stats 的终止原因数据源。
428
- let terminated = "clean";
429
-
430
- function saveState(state) {
431
- // rfl 仪表:terminated 随每次落盘快照(结构化终止路径设值后即 saveState)
432
- if (state.meta) state.meta.terminated = terminated;
433
- const tmp = STATE_FILE + ".tmp";
434
- fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
435
- fs.renameSync(tmp, STATE_FILE);
436
- }
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
-
491
- // clean 记录(recordAgentClean/recordAgentDirty 与跨批跳过判定 shouldSkipAgent
492
- // 在 review-fix-loop-utils.cjs,vitest 单测见 src/__tests__/review-fix-loop-utils.test.ts)
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
-
504
- // ── Agent defs(loadAgentMd/resolveAgentDefs 在 review-fix-loop-utils.cjs) ──
505
-
506
- // ── Build review calls ──────────────────────────────────────────────
507
-
508
- // 上一轮 fix 的 modifiedFiles(git diff 实测,fix 阶段写入 batchRounds)。
509
- // recheck 限定 prompt(scoped)的 scope 来源(初版 = modifiedFiles)。
510
- function lastModifiedFiles() {
511
- // M4: 批内轮次循环中 state.batches 尚未 push(仅在终止/批结束 push)——scoped 分支
512
- // (recheckAfterFix=true 的下轮 review)执行时读不到本批数据。fix 阶段把
513
- // modifiedFiles 同步写入 state.lastModifiedFiles(即时字段,批内立即可用),
514
- // 此处优先读它;fallback 旧路径(跨批场景)。
515
- if (state.lastModifiedFiles && Array.isArray(state.lastModifiedFiles)) {
516
- return state.lastModifiedFiles;
517
- }
518
- const b = state.batches[state.batches.length - 1];
519
- const r = b && b.rounds && b.rounds[b.rounds.length - 1];
520
- return (r && Array.isArray(r.modifiedFiles)) ? r.modifiedFiles : [];
521
- }
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
-
588
- function buildReviewCall(def, round, max, batchIndex, roundDir, scoped) {
589
- const header = "Batch " + batchIndex + " Round " + round + "/" + max + " — " + BATCH_NAMES[batchIndex - 1];
590
- const prevBatchesHint = batchIndex > 1
591
- ? "\nPrior batch reports (optional context): " + RUN_ROOT + "/batch-*/ (use read)"
592
- : "";
593
- const base = {
594
- model: MODEL,
595
- schema: reviewerSchema,
596
- description: def.name,
597
- timeoutMs: 3_600_000, // 1h(只读审查 + retry 退避余量)
598
- // returnMeta: true — 失败时 resolve
599
- // {value, error},raw.error 可检测(结构化终止可达);成功时
600
- // value = parsedOutput ?? content,parseResult 作用于 raw.value(MF-1)。
601
- returnMeta: true,
602
- };
603
-
604
- // S4:agentRef = 路径(非 fallow 的 def 必有 path)——systemPrompt/model 由主线程
605
- // resolveAgentOpts 按 path 加载注入,脚本不再拼 md 内容。
606
- if (def.isFallow) {
607
- return buildFallowReviewCall(base, def, header, roundDir);
608
- }
609
-
610
- // R2+ 三段式分支(5.2):round>1 且非 scoped → verify-first 对账 + known-remaining + 收敛 hunt。
611
- // scoped 分支(recheck 限定)在下方单独处理(含对账段)。
612
- if (round > 1 && !scoped) {
613
- return buildR2ReviewCall(base, def, header, round, max, roundDir, batchIndex);
614
- }
615
-
616
- // recheck 限定分支(5.5)
617
- if (scoped) {
618
- return buildScopedReviewCall(base, def, header, round, max, roundDir, batchIndex);
619
- }
620
-
621
- // T9:R1 函数化(三模板共享静态段,动态后置)
622
- return {
623
- ...base,
624
- prompt: buildR1ReviewPrompt({
625
- header, roundDir, reportFile: def.report, prevBatchesHint,
626
- reviewPrompt, reviewInstruction,
627
- }),
628
- agent: def.path,
629
- };
630
- }
631
-
632
- // S4:agentRef = 路径,主线程按路径加载(systemPrompt 注入);无名字查找,无需前缀兜底
633
- async function runReviewAgent(call) {
634
- return agent(call);
635
- }
636
-
637
- // ── Main loop: batches (serial) × rounds (per-batch) ────────────────
638
-
639
- const state = loadState();
640
- // 5.6 锁定结果落 state.meta.baseHash(commit 1 声称与实现一致化,run 后可从 state.json 追溯审查基线)
641
- state.meta = state.meta || {};
642
- state.meta.baseHash = lockedBase.hash || "";
643
- let totalFixed = 0;
644
- let finalMessage = "";
645
-
646
- for (let batchIndex = 1; batchIndex <= BATCHES.length; batchIndex++) {
647
- const defs = resolveAgentDefs(BATCHES[batchIndex - 1]);
648
- const cleanNames = new Set();
649
- let round = 0;
650
- let prevMustFix = -1;
651
- let stuckCount = 0;
652
- let batchClean = false;
653
- let roundHasFix = false; // recheckAfterFix 用:上轮是否有 fix
654
- const batchRounds = [];
655
-
656
- // MF-1 批级 review 状态隔离:每批开始重置 issue 追踪 / 收敛计数 / known-remaining,
657
- // 防止跨批 newFindings 语义污染(firstSeen 用批内 round 号写入、convergeStreak 跨批
658
- // 累加会让前批收敛状态泄漏到后批,复合导致 converged 错误跳过后续批次)。
659
- state.issues = undefined;
660
- state.convergeStreak = 0;
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 = [];
667
-
668
- // 跨批跳过:agent 在更早批 clean 且此后无 fix → 本批不派发
669
- if (batchIndex > 1) {
670
- for (const def of defs) {
671
- const s = state.agentStatus[def.name];
672
- if (shouldSkipAgent(s, state.fixCount, batchIndex)) {
673
- cleanNames.add(def.name);
674
- log("Cross-batch skip: " + def.name + " (clean in batch " + s.lastCleanBatch + ", no fix since)");
675
- }
676
- }
677
- }
678
-
679
- while (round < maxRounds) {
680
- round++;
681
- log("--- Batch " + batchIndex + "/" + BATCHES.length + " (" + BATCH_NAMES[batchIndex - 1] + ") Round " + round + "/" + maxRounds + " ---");
682
-
683
- phase("Review");
684
- const roundDir = RUN_ROOT + "/batch-" + batchIndex + "/round-" + round;
685
- fs.mkdirSync(roundDir, { recursive: true });
686
-
687
- let active = defs.filter((def) => !(skipCleanAgents && cleanNames.has(def.name)));
688
- let scopedClean = new Set(); // recheckAfterFix 重派时:上一轮 clean 的 agent 本轮走限定 prompt
689
- if (recheckAfterFix && round > 1 && roundHasFix) {
690
- scopedClean = new Set(cleanNames); // 重派前快照上一轮 clean 集合
691
- active = defs; // fix 后重派全批(强回归模式)
692
- cleanNames.clear();
693
- }
694
-
695
- if (active.length === 0) {
696
- log("All agents clean/skipped — batch " + batchIndex + " done.");
697
- batchClean = true;
698
- break;
699
- }
700
-
701
- log("Review: " + active.map((d) => d.name).join(", ") + " (" + active.length + " agent(s) in parallel)...");
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();
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
- }
717
-
718
- // per-agent 结果区分:parallel 结果与 calls 一一对应
719
- const reviewResults = [];
720
- const agentRoundResults = [];
721
- const reconSeen = new Set(); // R2+ reconciliation 声明的上轮 ID(status !== fixed)——stuck ID 驱动数据源(5.1)
722
- const reconEscalate = new Set(); // 5.1-5 escalate 声明:deferred 条目上下文改变 → 重新 open
723
- const reconAll = new Set(); // M2: 所有 status 条目(含 fixed)的 prev_id 去重——reconcile 门控数据源
724
- for (let i = 0; i < allRaw.length; i++) {
725
- const raw = allRaw[i];
726
- if (raw && typeof raw === "object" && raw.error) {
727
- // 审查 agent 调用失败(含 AgentRegistry not found / 超时)。不裸 throw——与
728
- // aggregator-failure/stuck/fix-failure 路径一致:saveState + terminated 结构化终止,
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 });
736
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
737
- saveState(state);
738
- terminated = "review-failure";
739
- finalMessage = "Batch " + batchIndex + " round " + round + ": 审查 agent 调用失败 " + active[i].name + " — " + raw.error;
740
- batchIndex = BATCHES.length + 1; // 终止外层循环
741
- break;
742
- }
743
- const parsed = normalizeReviewResult(raw.value);
744
- if (parsed) {
745
- // 5.8 通用落盘:schema-only agent(report_content 无 report_file,如 doc-reviewer)→
746
- // workflow 写盘到 <roundDir>/<def.report>.md 并填入 report_file(aggregator 读取路径不变)。
747
- const reportPath = resolveReviewReportPath(parsed, roundDir, active[i].report);
748
- if (reportPath && !(parsed.report_file && parsed.report_file.trim())) {
749
- try {
750
- fs.writeFileSync(reportPath, parsed.report_content || "", "utf-8");
751
- parsed.report_file = reportPath;
752
- } catch (e) {
753
- log("WARN: failed to write report_content to " + reportPath + " — " + e.message);
754
- }
755
- }
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);
764
- for (const r of parsed.reconciliation) {
765
- if (r.status === "escalate") reconEscalate.add(r.prev_id);
766
- else if (r.status !== "fixed") reconSeen.add(r.prev_id);
767
- reconAll.add(r.prev_id); // M2: 含 fixed——全 fixed 时 reconSeen 空但 reconcile 仍需执行
768
- }
769
- const def = active[i];
770
- if (parsed.must_fix === 0) {
771
- recordAgentClean(state, def.name, batchIndex);
772
- cleanNames.add(def.name);
773
- } else {
774
- recordAgentDirty(state, def.name, parsed.must_fix, batchIndex);
775
- }
776
- agentRoundResults.push({ name: def.name, must_fix: parsed.must_fix, suggestion: parsed.suggestion ?? 0, clean: parsed.must_fix === 0 });
777
- } else {
778
- // tools 受限的 agent(如 tools: read)会过滤掉 structured-output → schema 失效,
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 });
783
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
784
- saveState(state);
785
- terminated = "review-failure";
786
- finalMessage = "Batch " + batchIndex + " round " + round + ": 审查 agent 结果无效(缺 must_fix) " + active[i].name + " raw=" + JSON.stringify(raw.value).slice(0, 400);
787
- batchIndex = BATCHES.length + 1; // 终止外层循环
788
- break;
789
- }
790
- }
791
-
792
- if (terminated === "review-failure") break; // 已结构化终止,退出 round 循环(MF-3)
793
-
794
- if (reviewResults.every((r) => r.must_fix === 0)) {
795
- log("Batch " + batchIndex + " round " + round + ": all agents clean.");
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 });
807
- saveState(state);
808
- batchClean = true;
809
- break;
810
- }
811
-
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
- });
824
- const aggRaw = await agent({
825
- prompt: aggPrompt,
826
- model: AGG_MODEL,
827
- schema: aggregatorSchema,
828
- description: "aggregate",
829
- timeoutMs: 3_600_000, // 1h
830
- returnMeta: true,
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
- }));
837
-
838
- // returnMeta 下 aggRaw = {value, error};失败时 value 为空串、error 在 finalMessage 透出(MF-1)
839
- const aggValue = aggRaw?.value ?? aggRaw;
840
- let agg = normalizeAggregatorResult(aggValue);
841
-
842
- if (!agg || typeof agg.must_fix !== "number") {
843
- const rawPreview = (aggValue === undefined ? "undefined" : typeof aggValue === "string" ? aggValue : JSON.stringify(aggValue)).slice(0, 200);
844
- log("Aggregator JSON invalid (len=" + (typeof aggValue === "string" ? aggValue.length : 0) + "): " + rawPreview);
845
- const fallbackPath = (agg && agg.report_file) || (roundDir + "/aggregated.md");
846
- try {
847
- const content = fs.readFileSync(fallbackPath, "utf-8");
848
- const parsed = parseAggregatedMd(content);
849
- if (parsed && typeof parsed.must_fix === "number") {
850
- agg = { report_file: fallbackPath, must_fix: parsed.must_fix, suggestion: parsed.suggestion ?? 0 };
851
- log("Fallback parsed from " + fallbackPath + ": must_fix=" + agg.must_fix);
852
- }
853
- } catch { /* fallback read failed */ }
854
-
855
- if (!agg || typeof agg.must_fix !== "number") {
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 });
861
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
862
- saveState(state);
863
- terminated = "aggregator-failure";
864
- finalMessage = "Batch " + batchIndex + " round " + round + ": aggregator 失败且 fallback 解析失败"
865
- + (aggRaw && typeof aggRaw === "object" && aggRaw.error ? " — " + aggRaw.error : "");
866
- batchIndex = BATCHES.length + 1; // 终止外层循环
867
- break;
868
- }
869
- }
870
-
871
- const mustFix = agg.must_fix;
872
- const suggestion = agg.suggestion ?? 0;
873
- log("Aggregated: " + mustFix + " must-fix + " + suggestion + " suggestion(s).");
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
-
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 免侦查 + 裁决可追踪)。
893
- if (round === 1 && agg.must_fix_ids && agg.must_fix_ids.length > 0) {
894
- if (!state.issues) state.issues = {};
895
- const activeIds = new Set(filterActiveIds(agg.must_fix_ids));
896
- for (const entry of agg.must_fix_ids) {
897
- const id = typeof entry === "string" ? entry : entry && entry.id;
898
- if (!id || state.issues[id]) continue;
899
- if (!activeIds.has(id)) continue;
900
- state.issues[id] = {
901
- // severity 结构化(5.7):aggregator 标注 critical/major/minor,converged 终止的
902
- // 「无 critical」判定依赖它;旧格式(string)默认 major(must-fix 语义)。
903
- firstSeen: 1,
904
- severity: typeof entry === "string" ? "major" : (entry.severity || "major"),
905
- status: "open",
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 } : {}),
909
- };
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
- }
924
- }
925
-
926
- // ── Stuck detection ─────────────────────────────────────
927
- // 5.1 ID 驱动:R2+ 用 reconciliation 声明的 seenIds + reconcileIssues(同一 ID 连续 N 轮
928
- // open/regressed);无 reconciliation 数据(R1 或 reviewer 未填)降级计数式 updateStuckState
929
- // (现状语义,MF-2 注释保留)。needs-redesign(fixAttempts>=2)在 wave 5 接入。
930
- // M2: reconCount = 所有 status 条目的 prev_id 去重计数(含 fixed)——全 fixed(最常见
931
- // 收敛路径)时 reconSeen 为空,但 reconciliation 有数据仍须调 reconcileIssues:
932
- // fix-attempted → fixed 的唯一转换点(否则 fix-attempted 永不转 fixed)
933
- const reconCount = reconAll.size;
934
- // F1: 无对账数据(doc-reviewer-only 批 reconciliation 恒空)时,fix-attempted 条目
935
- // 存在仍须执行 reconcile——空 seenIds 语义 = 未被重新报告 = 已修复(5.1「未出现→fixed」)
936
- const hasFixAttempted = state.issues
937
- ? Object.values(state.issues).some((i) => i.status === "fix-attempted")
938
- : false;
939
-
940
- // 5.1-2 R2+ 新发现 ID 契约(M2 移出 reconcile 分支,独立执行;F1 扩展为
941
- // 「重新报告 = 未修复」转换):aggregator 的 must_fix_ids 中
942
- // a) 不在 issues → 创建为新条目(firstSeen=round,severity 从 aggregator 标注)
943
- // b) 已存在且(fix-attempted 或 fixed)且本轮无对账数据(reconCount===0,
944
- // doc-reviewer 场景)→ 重新报告 = 修复失败:转 regressed + fixAttempts+1 + openStreak+1
945
- // (RC-7 needs-redesign 出口在无对账配置下可达;reconciliation 场景由
946
- // reconcileIssues 处理,避免双计)。fixed 重报分支(MF-2):已确认修复的问题
947
- // 再次被报告同样转 regressed——否则 fixed 停留 + 收敛终止组合会在默认配置下
948
- // R3 即以 converged 提前终止而 must-fix 仍活跃。
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 过滤)。
953
- if (round > 1 && state.issues && agg.must_fix_ids && agg.must_fix_ids.length > 0) {
954
- let added = 0;
955
- const activeIds = new Set(filterActiveIds(agg.must_fix_ids));
956
- for (const entry of agg.must_fix_ids) {
957
- const id = typeof entry === "string" ? entry : entry && entry.id;
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;
964
- if (state.issues[id]) {
965
- if (reconCount === 0 && (state.issues[id].status === "fix-attempted" || state.issues[id].status === "fixed")) {
966
- state.issues[id].status = "regressed";
967
- state.issues[id].fixAttempts = (state.issues[id].fixAttempts || 0) + 1;
968
- state.issues[id].openStreak = (state.issues[id].openStreak || 0) + 1;
969
- state.issues[id].severity = typeof entry === "string" ? "major" : (entry.severity || "major");
970
- state.issues[id].history.push({ round, status: "regressed" });
971
- added++;
972
- }
973
- continue;
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");
987
- state.issues[id] = {
988
- firstSeen: round,
989
- severity: typeof entry === "string" ? "major" : (entry.severity || "major"),
990
- status: "open", openStreak: 1,
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 } : {}),
995
- };
996
- added++;
997
- }
998
- // A8:R2+ merge 落盘后观测 guidance/evidence 缺失(与 R1 init 同一观测口径)
999
- warnMissingFields(agg.must_fix_ids);
1000
- if (added > 0) log("New findings tracked: " + added + " new or re-reported issue(s) in round " + round);
1001
- }
1002
-
1003
- let stuck = { stuck: false };
1004
- if (round > 1 && (reconCount > 0 || hasFixAttempted)) {
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 });
1009
- state.issues = rec.issues;
1010
- state.knownRemaining = rec.knownRemaining;
1011
- stuck = { stuck: rec.stuck, stuckIds: rec.stuckIds };
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
- }
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
- }
1034
- const s = updateStuckState(prevMustFix, stuckCount, mustFix, stuckThreshold);
1035
- stuckCount = s.stuckCount;
1036
- prevMustFix = s.prevMustFix;
1037
- stuck = { stuck: s.stuck };
1038
- }
1039
- if (stuck.stuck) {
1040
- const stuckIds = (stuck.stuckIds || []).join(", ");
1041
- log("Stuck: issue(s) not converging for " + stuckThreshold + " rounds: " + stuckIds + ". Stopping.");
1042
- batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
1043
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
1044
- saveState(state);
1045
- terminated = "stuck";
1046
- finalMessage = "Batch " + batchIndex + " round " + round + ": 问题 " + stuckIds + " 连续 " + stuckThreshold + " 轮未收敛。残留: "
1047
- + (state.knownRemaining && state.knownRemaining.length ? state.knownRemaining.join("; ") : "无 deferred");
1048
- batchIndex = BATCHES.length + 1;
1049
- break;
1050
- }
1051
-
1052
- // 5.7 needs-redesign(RC-7):fixAttempts >= maxFixAttempts 且 regressed → 终止。
1053
- // 顺序在 stuck 之后:stuck(一直在)信息更宏观,needs-redesign(修不好)更具体,先 stuck 后 redesign。
1054
- if (round > 1 && state.issues) {
1055
- const redesign = findNeedsRedesign(state.issues, maxFixAttempts);
1056
- if (redesign.length > 0) {
1057
- const ids = redesign.map((r) => r.issue_id).join(", ");
1058
- // 5.7 message 三要素:ID + 修复历史摘要 + 残留清单(history 全文随 state.json 落盘)
1059
- const historySummary = redesign.map((r) => {
1060
- const hist = (r.history || []).map((h) => "R" + h.round + ":" + h.status).join(" -> ");
1061
- return r.issue_id + " [" + (hist || "no history") + "]";
1062
- }).join("; ");
1063
- log("Needs redesign: " + ids + " not converging after " + maxFixAttempts + " fix attempts. Stopping.");
1064
- batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
1065
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
1066
- saveState(state);
1067
- terminated = "needs-redesign";
1068
- finalMessage = "Batch " + batchIndex + " round " + round + ": 问题 " + historySummary + " 经 " + maxFixAttempts
1069
- + " 次修复仍未收敛,属于需要重新设计而非继续补丁的结构性问题,请人工介入。残留: "
1070
- + (state.knownRemaining && state.knownRemaining.length ? state.knownRemaining.join("; ") : "无 deferred");
1071
- batchIndex = BATCHES.length + 1;
1072
- break;
1073
- }
1074
-
1075
- // 5.7 新发现率收敛:连续 convergeRounds 轮新发现 <= convergeNewIssues → converged
1076
- const newIssues = Object.values(state.issues).filter((i) => i.firstSeen === round);
1077
- const newFindings = newIssues.length;
1078
- const newFindingsCritical = newIssues.filter((i) => i.severity === "critical").length;
1079
- const conv = checkConvergence({
1080
- prevStreak: state.convergeStreak || 0, newFindings, newFindingsCritical,
1081
- convergeNewIssues, convergeRounds,
1082
- });
1083
- state.convergeStreak = conv.streak;
1084
- // MF-2/S-21 收敛门槛:新发现率收敛 ≠ 问题已解决——必须同时满足「无 open/regressed
1085
- // 活跃条目」才允许 converged 终止。fixed 条目复发(reconcile/merge 已转 regressed)
1086
- // 后活跃条目存在 → 不收敛,继续修复循环(默认配置下 R3 复发不再提前终止)。
1087
- // issues 无追踪(aggregator 缺 must_fix_ids)时回退 mustFix===0 数字级判定,
1088
- // 避免 must_fix>0 照常收敛掩盖未处理问题(S-21)。
1089
- const trackedCount = Object.keys(state.issues || {}).length;
1090
- const activeIssues = Object.values(state.issues || {})
1091
- .filter((i) => i.status === "open" || i.status === "regressed");
1092
- const noActiveIssues = trackedCount === 0 ? mustFix === 0 : activeIssues.length === 0;
1093
- if (conv.converged && noActiveIssues) {
1094
- // MF-2 ④:converged 消息列出 open issue ID(对齐 max-rounds 的 remainingIds 逻辑)。
1095
- // 门槛保证正常路径此处为空;状态漂移时调用方仍能看到残留而非「无 deferred」误报。
1096
- const remainingIds = Object.entries(state.issues || {})
1097
- .filter(([, i]) => i.status !== "fixed" && i.status !== "deferred")
1098
- .map(([id]) => id);
1099
- log("Converged: new findings <= " + convergeNewIssues + " for " + convergeRounds + " rounds. Batch " + batchIndex + " done, proceeding to next batch.");
1100
- batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
1101
- saveState(state);
1102
- terminated = "converged";
1103
- finalMessage = "Batch " + batchIndex + " round " + round + ": 新发现率收敛(连续 " + convergeRounds
1104
- + " 轮新问题 ≤" + convergeNewIssues + ")。残留: "
1105
- + (remainingIds.length ? remainingIds.join(", ") : "无")
1106
- + (state.knownRemaining && state.knownRemaining.length ? ";deferred: " + state.knownRemaining.join("; ") : "");
1107
- batchClean = true; // MF-1: 与 clean 一致,让外层 for 推进下一批(不跳过后续批次)
1108
- break;
1109
- }
1110
- }
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
-
1147
- // ── Fix ─────────────────────────────────────────────────
1148
- phase("Fix");
1149
- let reportContent;
1150
- try {
1151
- reportContent = fs.readFileSync(agg.report_file, "utf-8");
1152
- } catch {
1153
- reportContent = "(could not read aggregated report)";
1154
- }
1155
-
1156
- let prevHead = "";
1157
- try {
1158
- prevHead = require("child_process").execSync(
1159
- "git rev-parse HEAD", { encoding: "utf-8", timeout: 10_000 }
1160
- ).trim();
1161
- } catch {
1162
- // 非 git 项目(如纯文档目录):prevHead 为空,跳过 modifiedFiles 统计
1163
- }
1164
-
1165
- const commitInstr = autoCommit
1166
- ? "- After all fixes, stage ONLY the files you modified: `git add <file1> <file2> ...` (explicit paths).\n" +
1167
- "- NEVER use `git add -A` or `git add .` — the workspace may contain unrelated untracked files.\n" +
1168
- "- Commit with message: `fix: review batch " + batchIndex + " round " + round + " — " + mustFix + " must-fix`"
1169
- : "- Do NOT commit. Leave the fixes in the working tree (autoCommit=false).";
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
- });
1193
- const fxRaw = await agent({
1194
- prompt: fixPromptBuilt,
1195
- schema: fixSchema,
1196
- // S4:fixAgent = agentRef 路径(主线程按路径加载 + frontmatter model 传播);
1197
- // 未传保持现状(通用 subagent + 内联 fixPrompt)。
1198
- model: MODEL,
1199
- description: (FIX_DEF && FIX_DEF.name) || "fix",
1200
- // fix 不设 timeoutMs = 不限时(execute-options-mapper: undefined/<=0 → 不设超时)。
1201
- // 带写操作(改项目代码)可能很久(大重构/多文件),不应被墙钟超时打断。
1202
- returnMeta: true,
1203
- ...(FIX_DEF && FIX_DEF.path ? { agent: FIX_DEF.path } : {}),
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
- }));
1210
-
1211
- // returnMeta 下 fxRaw = {value, error}:先查 error(失败分支可达,MF-1),再对 value 做 parseResult
1212
- if (fxRaw && typeof fxRaw === "object" && fxRaw.error) {
1213
- // fix agent 调用失败(AgentRegistry not found / 超时等)。
1214
- // 与 review 路径(raw.error → review-failure)对齐:结构化终止而非静默当成功——
1215
- // 否则 fixed_count 缺失被 `?? mustFix` 回退,totalFixed 虚增且 must_fix 不降白跑轮次(MF-1)。
1216
- log("Fix agent failed, stopping.");
1217
- batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
1218
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
1219
- saveState(state);
1220
- terminated = "fix-failure";
1221
- finalMessage = "Batch " + batchIndex + " round " + round + ": fix agent 调用失败 — " + fxRaw.error;
1222
- batchIndex = BATCHES.length + 1;
1223
- break;
1224
- }
1225
- const fx = parseResult(fxRaw.value);
1226
- const fixResult = fx ? normalizeFixResult(fx) : null;
1227
- if (!fixResult) {
1228
- log("Fix agent failed, stopping.");
1229
- batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
1230
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
1231
- saveState(state);
1232
- terminated = "fix-failure";
1233
- finalMessage = "Batch " + batchIndex + " round " + round + ": fix agent 结果无效";
1234
- batchIndex = BATCHES.length + 1;
1235
- break;
1236
- }
1237
-
1238
- // ES3 硬校验(5.3 红线,恢复 mustFixIds 交叉校验——wave 3 后 agg.must_fix_ids
1239
- // 已是标准字段):(1) deferred 只允许 minor;(2) must-fix 必须全进 fixes[](漏修
1240
- // 判 violation)。任一违规 → fix-failure(结构化终止)。trackedIssues 传入
1241
- // state.issues——deferred severity 与追踪表交叉核对(MF-4):must-fix 被标 minor
1242
- // 塞进 deferred 的逃逸路径在追踪表面前失效(追踪 severity 为准)。
1243
- // rfl(tier-1 6.3):mustFixIds 传 filterActiveIds 结果——降级条目不占修复队列
1244
- //(must_fix 计数由 aggregator 按非降级条目报,两侧口径一致)。
1245
- const es3Violations = validateFixResult(fixResult, filterActiveIds(agg.must_fix_ids), state.issues);
1246
- if (es3Violations.length > 0) {
1247
- // m7: violation 分两类——deferred 非 minor / must-fix 漏修(must-fix-not-fixed),
1248
- // finalMessage 文案区分:统一文案会把漏修误报成 defer 违规,误导修复方向
1249
- const parts = es3Violations.map((v) =>
1250
- v.severity === "must-fix-not-fixed"
1251
- ? "must-fix 未在 fixes[] 中修复(漏修)— " + v.issue_id
1252
- : "deferred 含非 minor 条目(must-fix 不得 defer)— " + v.issue_id + "(" + v.severity + ")"
1253
- );
1254
- log("ES3 violation: " + JSON.stringify(es3Violations));
1255
- batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles: [], phaseTimings });
1256
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
1257
- saveState(state);
1258
- terminated = "fix-failure";
1259
- finalMessage = "Batch " + batchIndex + " round " + round + ": " + parts.join("; ");
1260
- batchIndex = BATCHES.length + 1;
1261
- break;
1262
- }
1263
-
1264
- // ES2 软校验(5.3 证据标准):defer 理由过短/无实质 → warning 日志(不终止)
1265
- for (const d of fixResult.deferred) {
1266
- const reason = typeof d.reason === "string" ? d.reason : "";
1267
- if (reason.trim().length < 20) {
1268
- log("WARN: deferred reason too short / no concrete cost description: " + JSON.stringify(d));
1269
- }
1270
- }
1271
-
1272
- // affected_files 并入 state.fixImpactFiles(5.3/5.5):recheck scope = modifiedFiles ∪ fixImpactFiles
1273
- const impactFiles = [];
1274
- for (const f of fixResult.fixes) {
1275
- if (Array.isArray(f.affected_files)) {
1276
- for (const af of f.affected_files) {
1277
- if (typeof af === "string" && af.trim() && !impactFiles.includes(af.trim())) impactFiles.push(af.trim());
1278
- }
1279
- }
1280
- }
1281
- state.fixImpactFiles = impactFiles;
1282
-
1283
- // m4: 先初始化容器——aggregator JSON 无效走 parseAggregatedMd 回退时 agg 无
1284
- // must_fix_ids → R1 初始化跳过 → state.issues undefined;此处初始化保证 deferred
1285
- // 写入与 knownRemaining 同步链路生效(否则 knownRemaining 恒空,deferred 跨轮继承整链失效)
1286
- if (!state.issues) state.issues = {};
1287
- // 5.1:fix 结果标记 fix-attempted(ID 对账驱动)+ fixResults 落库(R2+ prompt 输入)
1288
- // 归一化查表(findIssueKey,与 ES3 同键空间):fix agent ID 漂移("mf-1"/
1289
- // "MF-1 (fixed)")不再丢匹配——精确键查表时 issue 停留 open,reconcile 无
1290
- // fix-attempted 可转 fixed/regressed,needs-redesign 出口对该类 ID 静默失效。
1291
- for (const f of fixResult.fixes) {
1292
- if (f && typeof f.issue_id === "string") {
1293
- const trackedKey = findIssueKey(state.issues, f.issue_id);
1294
- if (trackedKey) {
1295
- state.issues[trackedKey].status = "fix-attempted";
1296
- state.issues[trackedKey].history.push({ round, status: "fix-attempted" });
1297
- }
1298
- }
1299
- }
1300
- // 5.3-4 deferred 写入 state.issues(known-remaining 跨轮继承链路):deferred 条目
1301
- // 以 status=deferred 入 issues,据此生成 knownRemaining 传给 R2+ prompt。
1302
- // ID 已存在(曾被修复/降级,含大小写/尾注漂移)→ 更新状态 + reason;
1303
- // 不存在(S-x minor)→ 新建。漂移 ID 归一化匹配防止幽灵条目(原条目仍 open 阻塞收敛)。
1304
- for (const d of fixResult.deferred) {
1305
- if (!d || typeof d.issue_id !== "string" || !d.issue_id) continue;
1306
- const reason = typeof d.reason === "string" ? d.reason : "";
1307
- const trackedKey = findIssueKey(state.issues, d.issue_id);
1308
- if (trackedKey) {
1309
- state.issues[trackedKey].status = "deferred";
1310
- state.issues[trackedKey].deferredReason = reason;
1311
- state.issues[trackedKey].history.push({ round, status: "deferred" });
1312
- } else {
1313
- state.issues[d.issue_id] = {
1314
- firstSeen: round, severity: "minor", status: "deferred",
1315
- deferredReason: reason,
1316
- history: [{ round, status: "deferred" }], fixAttempts: 0,
1317
- };
1318
- }
1319
- }
1320
- // known-remaining 同步更新:deferred 在本轮 fix 后即生效,R2+ prompt 立即消费
1321
- // (不依赖下轮 reconcile 才生成——否则滞后一轮,reviewer 本轮看不到 deferred 清单)
1322
- state.knownRemaining = computeKnownRemaining(state.issues);
1323
- if (!state.fixResults) state.fixResults = [];
1324
- state.fixResults.push(fixResult);
1325
-
1326
- const fixedCount = fixResult.fixed_count ?? mustFix;
1327
- totalFixed += fixedCount;
1328
- state.fixCount++;
1329
- roundHasFix = true;
1330
-
1331
- let modifiedFiles = [];
1332
- if (prevHead) {
1333
- try {
1334
- const out = require("child_process").execSync(
1335
- "git diff --name-only " + prevHead, { encoding: "utf-8", timeout: 10_000 }
1336
- ).trim();
1337
- modifiedFiles = out ? out.split("\n") : [];
1338
- } catch { /* empty */ }
1339
- }
1340
- batchRounds.push({ round, mustFix, suggestion, agents: agentRoundResults, modifiedFiles, phaseTimings });
1341
- // M4: 批内即时字段——下轮 scoped 分支(recheckAfterFix=true)从
1342
- // state.lastModifiedFiles 读本批 fix 的真实改动文件(git 实测兜底)
1343
- state.lastModifiedFiles = modifiedFiles;
1344
- saveState(state);
1345
-
1346
- log("Fixed " + fixedCount + " issue(s). Total: " + totalFixed + ". Modified " + modifiedFiles.length + " file(s). Continuing...");
1347
- }
1348
-
1349
- if (batchIndex > BATCHES.length) break; // 已终止
1350
-
1351
- state.batches.push({ index: batchIndex, name: BATCH_NAMES[batchIndex - 1], rounds: batchRounds });
1352
-
1353
- terminated = resolveBatchTerminated(batchClean, terminated);
1354
- if (terminated === "max-rounds") {
1355
- // 该批达到 maxRounds 仍残留 must-fix → fail-fast,不进入后续批。
1356
- // 5.9 残留清单:未 fixed 的 issues(status != fixed/deferred)+ known-remaining。
1357
- const remainingIds = state.issues
1358
- ? Object.entries(state.issues)
1359
- .filter(([, i]) => i.status !== "fixed" && i.status !== "deferred")
1360
- .map(([id]) => id)
1361
- : [];
1362
- finalMessage = "Batch " + batchIndex + " (" + BATCH_NAMES[batchIndex - 1] + ") 达到 maxRounds=" + maxRounds
1363
- + " 仍有 must-fix,终止整个 workflow。残留: "
1364
- + (remainingIds.length ? remainingIds.join(", ") : "(issues 未追踪)")
1365
- + (state.knownRemaining && state.knownRemaining.length ? ";deferred: " + state.knownRemaining.join("; ") : "");
1366
- log(finalMessage);
1367
- saveState(state);
1368
- batchIndex = BATCHES.length + 1;
1369
- break;
1370
- }
1371
- saveState(state);
1372
- log("=== Batch " + batchIndex + " (" + BATCH_NAMES[batchIndex - 1] + ") CLEAN ===");
1373
- }
1374
-
1375
- log("\n=== Loop Complete ===");
1376
- saveState(state);
1377
-
1378
- return {
1379
- batches: BATCHES.length,
1380
- totalFixed,
1381
- terminated,
1382
- targetType,
1383
- target,
1384
- runDir: RUN_ROOT,
1385
- // 5.9 terminated 透出:非 clean 时 message 含终止原因 + 残留 ID 清单 + deferred 理由
1386
- // (stuck/needs-redesign/converged/max-rounds/*-failure 均由 finalMessage 承载)。
1387
- // 渲染层特判(launcher 对 terminated 非 clean 的视觉区分)留 TODO:当前 tool 结果
1388
- // 已含完整 message,主 agent 可直接感知差异。
1389
- // 5.9 视觉区分:非 clean 终止加 [UNRESOLVED] 前缀(tool 结果即主 agent 可见层,
1390
- // launcher 透传 message——无需跨模块渲染特判,W5C3 决策更新)
1391
- message: terminated === "clean"
1392
- ? "All batches clean. " + totalFixed + " issue(s) fixed total. State: " + STATE_FILE
1393
- : terminated === "converged"
1394
- ? finalMessage + " " + totalFixed + " issue(s) fixed total. State: " + STATE_FILE
1395
- : "[UNRESOLVED] " + finalMessage + ". State: " + STATE_FILE,
1396
- };