soloforge 1.1.47 → 1.1.49

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 (266) hide show
  1. package/README.md +11 -7
  2. package/dist/cli/adapter_writers.d.ts +5 -0
  3. package/dist/cli/adapter_writers.d.ts.map +1 -1
  4. package/dist/cli/adapter_writers.js +25 -0
  5. package/dist/cli/adapter_writers.js.map +1 -1
  6. package/dist/cli/init.d.ts.map +1 -1
  7. package/dist/cli/init.js +3 -1
  8. package/dist/cli/init.js.map +1 -1
  9. package/dist/cli/scope_check.d.ts +4 -0
  10. package/dist/cli/scope_check.d.ts.map +1 -0
  11. package/dist/cli/scope_check.js +51 -0
  12. package/dist/cli/scope_check.js.map +1 -0
  13. package/dist/context/adapters/claude_code/hooks.d.ts +5 -7
  14. package/dist/context/adapters/claude_code/hooks.d.ts.map +1 -1
  15. package/dist/context/adapters/claude_code/hooks.js +11 -9
  16. package/dist/context/adapters/claude_code/hooks.js.map +1 -1
  17. package/dist/context/adapters/shared/integration_guide.d.ts +3 -3
  18. package/dist/context/adapters/shared/integration_guide.js +18 -8
  19. package/dist/context/adapters/shared/integration_guide.js.map +1 -1
  20. package/dist/context/adapters/shared/workflow_template.js +1 -1
  21. package/dist/context/config/intent_schema.d.ts +793 -6
  22. package/dist/context/config/intent_schema.d.ts.map +1 -1
  23. package/dist/context/config/intent_schema.js +9 -0
  24. package/dist/context/config/intent_schema.js.map +1 -1
  25. package/dist/context/config/resolver.d.ts +9 -0
  26. package/dist/context/config/resolver.d.ts.map +1 -1
  27. package/dist/context/config/resolver.js +5 -0
  28. package/dist/context/config/resolver.js.map +1 -1
  29. package/dist/core/adversarial_review_store.d.ts +133 -0
  30. package/dist/core/adversarial_review_store.d.ts.map +1 -0
  31. package/dist/core/adversarial_review_store.js +161 -0
  32. package/dist/core/adversarial_review_store.js.map +1 -0
  33. package/dist/core/domain_transition.d.ts.map +1 -1
  34. package/dist/core/domain_transition.js +3 -0
  35. package/dist/core/domain_transition.js.map +1 -1
  36. package/dist/core/gate_record_store.d.ts +2 -0
  37. package/dist/core/gate_record_store.d.ts.map +1 -1
  38. package/dist/core/gate_record_store.js +17 -4
  39. package/dist/core/gate_record_store.js.map +1 -1
  40. package/dist/core/git_utils.d.ts +1 -1
  41. package/dist/core/git_utils.d.ts.map +1 -1
  42. package/dist/core/git_utils.js +8 -4
  43. package/dist/core/git_utils.js.map +1 -1
  44. package/dist/core/observer.d.ts.map +1 -1
  45. package/dist/core/observer.js +26 -2
  46. package/dist/core/observer.js.map +1 -1
  47. package/dist/core/task_context/constants.d.ts.map +1 -1
  48. package/dist/core/task_context/constants.js +4 -2
  49. package/dist/core/task_context/constants.js.map +1 -1
  50. package/dist/core/task_context/manager.d.ts +4 -4
  51. package/dist/core/task_context/manager.d.ts.map +1 -1
  52. package/dist/core/task_context/manager.js +69 -62
  53. package/dist/core/task_context/manager.js.map +1 -1
  54. package/dist/core/task_context/manager_setters.d.ts +2 -0
  55. package/dist/core/task_context/manager_setters.d.ts.map +1 -1
  56. package/dist/core/task_context/manager_setters.js +10 -0
  57. package/dist/core/task_context/manager_setters.js.map +1 -1
  58. package/dist/core/task_context/stage_fact_ownership.js +1 -1
  59. package/dist/core/task_context/stage_fact_ownership.js.map +1 -1
  60. package/dist/core/task_context/status_transitions.js +5 -5
  61. package/dist/core/task_context/status_transitions.js.map +1 -1
  62. package/dist/core/types.d.ts +29 -0
  63. package/dist/core/types.d.ts.map +1 -1
  64. package/dist/core/waiver_store.d.ts +100 -0
  65. package/dist/core/waiver_store.d.ts.map +1 -0
  66. package/dist/core/waiver_store.js +185 -0
  67. package/dist/core/waiver_store.js.map +1 -0
  68. package/dist/domain/asset_registry/derived_registry.d.ts +10 -0
  69. package/dist/domain/asset_registry/derived_registry.d.ts.map +1 -1
  70. package/dist/domain/asset_registry/derived_registry.js +10 -0
  71. package/dist/domain/asset_registry/derived_registry.js.map +1 -1
  72. package/dist/domain/asset_registry/derived_types.d.ts +27 -0
  73. package/dist/domain/asset_registry/derived_types.d.ts.map +1 -1
  74. package/dist/domain/asset_registry/derived_types.js +10 -0
  75. package/dist/domain/asset_registry/derived_types.js.map +1 -1
  76. package/dist/domain/build/engine.d.ts +1 -0
  77. package/dist/domain/build/engine.d.ts.map +1 -1
  78. package/dist/domain/build/engine.js +34 -4
  79. package/dist/domain/build/engine.js.map +1 -1
  80. package/dist/domain/contracts/design_lifecycle_contract.d.ts.map +1 -1
  81. package/dist/domain/contracts/design_lifecycle_contract.js +11 -4
  82. package/dist/domain/contracts/design_lifecycle_contract.js.map +1 -1
  83. package/dist/domain/design/contract.d.ts.map +1 -1
  84. package/dist/domain/design/contract.js +11 -0
  85. package/dist/domain/design/contract.js.map +1 -1
  86. package/dist/domain/design/engine.d.ts +1 -0
  87. package/dist/domain/design/engine.d.ts.map +1 -1
  88. package/dist/domain/design/engine.js +35 -3
  89. package/dist/domain/design/engine.js.map +1 -1
  90. package/dist/domain/engine_helpers.d.ts +37 -0
  91. package/dist/domain/engine_helpers.d.ts.map +1 -1
  92. package/dist/domain/engine_helpers.js +86 -0
  93. package/dist/domain/engine_helpers.js.map +1 -1
  94. package/dist/domain/operate/engine.d.ts +1 -0
  95. package/dist/domain/operate/engine.d.ts.map +1 -1
  96. package/dist/domain/operate/engine.js +19 -2
  97. package/dist/domain/operate/engine.js.map +1 -1
  98. package/dist/domain/types.d.ts +6 -0
  99. package/dist/domain/types.d.ts.map +1 -1
  100. package/dist/domain/types.js.map +1 -1
  101. package/dist/domain/verify/engine.d.ts +1 -0
  102. package/dist/domain/verify/engine.d.ts.map +1 -1
  103. package/dist/domain/verify/engine.js +18 -1
  104. package/dist/domain/verify/engine.js.map +1 -1
  105. package/dist/gate/certainty_gate.d.ts +10 -0
  106. package/dist/gate/certainty_gate.d.ts.map +1 -1
  107. package/dist/gate/certainty_gate.js.map +1 -1
  108. package/dist/gate/contracts/tool_actions.d.ts +11 -2
  109. package/dist/gate/contracts/tool_actions.d.ts.map +1 -1
  110. package/dist/gate/contracts/tool_actions.js +12 -2
  111. package/dist/gate/contracts/tool_actions.js.map +1 -1
  112. package/dist/gate/contracts/tool_invocation_contract_registry.d.ts.map +1 -1
  113. package/dist/gate/contracts/tool_invocation_contract_registry.js +11 -1
  114. package/dist/gate/contracts/tool_invocation_contract_registry.js.map +1 -1
  115. package/dist/gate/executors/executors_annotation.d.ts +33 -10
  116. package/dist/gate/executors/executors_annotation.d.ts.map +1 -1
  117. package/dist/gate/executors/executors_annotation.js +119 -64
  118. package/dist/gate/executors/executors_annotation.js.map +1 -1
  119. package/dist/gate/executors/executors_artifact.d.ts +20 -1
  120. package/dist/gate/executors/executors_artifact.d.ts.map +1 -1
  121. package/dist/gate/executors/executors_artifact.js +83 -123
  122. package/dist/gate/executors/executors_artifact.js.map +1 -1
  123. package/dist/gate/executors/executors_build.d.ts +2 -2
  124. package/dist/gate/executors/executors_build.d.ts.map +1 -1
  125. package/dist/gate/executors/executors_build.js +28 -126
  126. package/dist/gate/executors/executors_build.js.map +1 -1
  127. package/dist/gate/executors/executors_deploy.d.ts.map +1 -1
  128. package/dist/gate/executors/executors_deploy.js +35 -7
  129. package/dist/gate/executors/executors_deploy.js.map +1 -1
  130. package/dist/gate/executors/executors_external_command.d.ts.map +1 -1
  131. package/dist/gate/executors/executors_external_command.js +54 -2
  132. package/dist/gate/executors/executors_external_command.js.map +1 -1
  133. package/dist/gate/executors/executors_field_mapping.d.ts +7 -0
  134. package/dist/gate/executors/executors_field_mapping.d.ts.map +1 -0
  135. package/dist/gate/executors/executors_field_mapping.js +191 -0
  136. package/dist/gate/executors/executors_field_mapping.js.map +1 -0
  137. package/dist/gate/executors/executors_jacoco.d.ts +3 -0
  138. package/dist/gate/executors/executors_jacoco.d.ts.map +1 -0
  139. package/dist/gate/executors/executors_jacoco.js +70 -0
  140. package/dist/gate/executors/executors_jacoco.js.map +1 -0
  141. package/dist/gate/executors/executors_prerequisite.d.ts +1 -1
  142. package/dist/gate/executors/executors_prerequisite.d.ts.map +1 -1
  143. package/dist/gate/executors/executors_prerequisite.js +2 -62
  144. package/dist/gate/executors/executors_prerequisite.js.map +1 -1
  145. package/dist/gate/executors/executors_probe.d.ts +1 -0
  146. package/dist/gate/executors/executors_probe.d.ts.map +1 -1
  147. package/dist/gate/executors/executors_probe.js +1 -0
  148. package/dist/gate/executors/executors_probe.js.map +1 -1
  149. package/dist/gate/executors/executors_regex_scan.d.ts.map +1 -1
  150. package/dist/gate/executors/executors_regex_scan.js +34 -13
  151. package/dist/gate/executors/executors_regex_scan.js.map +1 -1
  152. package/dist/gate/executors/executors_scope.d.ts +7 -3
  153. package/dist/gate/executors/executors_scope.d.ts.map +1 -1
  154. package/dist/gate/executors/executors_scope.js +20 -173
  155. package/dist/gate/executors/executors_scope.js.map +1 -1
  156. package/dist/gate/executors/executors_trace.d.ts +5 -0
  157. package/dist/gate/executors/executors_trace.d.ts.map +1 -1
  158. package/dist/gate/executors/executors_trace.js +295 -4
  159. package/dist/gate/executors/executors_trace.js.map +1 -1
  160. package/dist/gate/executors/index.d.ts.map +1 -1
  161. package/dist/gate/executors/index.js +4 -2
  162. package/dist/gate/executors/index.js.map +1 -1
  163. package/dist/gate/gate_engine.d.ts +20 -0
  164. package/dist/gate/gate_engine.d.ts.map +1 -1
  165. package/dist/gate/gate_engine.js +58 -6
  166. package/dist/gate/gate_engine.js.map +1 -1
  167. package/dist/gate/gate_registry_bridge.d.ts +12 -2
  168. package/dist/gate/gate_registry_bridge.d.ts.map +1 -1
  169. package/dist/gate/gate_registry_bridge.js +7 -5
  170. package/dist/gate/gate_registry_bridge.js.map +1 -1
  171. package/dist/gate/middleware_gates.js +1 -1
  172. package/dist/gate/middleware_gates.js.map +1 -1
  173. package/dist/gate/release/gate_checks/checkAssetAntiBloat.d.ts.map +1 -1
  174. package/dist/gate/release/gate_checks/checkAssetAntiBloat.js +3 -0
  175. package/dist/gate/release/gate_checks/checkAssetAntiBloat.js.map +1 -1
  176. package/dist/gate/scope_resolver.d.ts +7 -0
  177. package/dist/gate/scope_resolver.d.ts.map +1 -1
  178. package/dist/gate/scope_resolver.js +1 -1
  179. package/dist/gate/scope_resolver.js.map +1 -1
  180. package/dist/index.js +5 -0
  181. package/dist/index.js.map +1 -1
  182. package/dist/server/tools/index.d.ts.map +1 -1
  183. package/dist/server/tools/index.js +4 -2
  184. package/dist/server/tools/index.js.map +1 -1
  185. package/dist/server/tools/middleware.d.ts.map +1 -1
  186. package/dist/server/tools/middleware.js +1 -0
  187. package/dist/server/tools/middleware.js.map +1 -1
  188. package/dist/server/tools/schemas.d.ts +10 -0
  189. package/dist/server/tools/schemas.d.ts.map +1 -1
  190. package/dist/server/tools/schemas.js +10 -0
  191. package/dist/server/tools/schemas.js.map +1 -1
  192. package/dist/server/tools/sf_doctor.d.ts +8 -0
  193. package/dist/server/tools/sf_doctor.d.ts.map +1 -1
  194. package/dist/server/tools/sf_doctor.js +58 -2
  195. package/dist/server/tools/sf_doctor.js.map +1 -1
  196. package/dist/server/tools/sf_task.d.ts +83 -0
  197. package/dist/server/tools/sf_task.d.ts.map +1 -1
  198. package/dist/server/tools/sf_task.js +121 -4
  199. package/dist/server/tools/sf_task.js.map +1 -1
  200. package/dist/server/tools/sf_waiver.d.ts +31 -0
  201. package/dist/server/tools/sf_waiver.d.ts.map +1 -0
  202. package/dist/server/tools/sf_waiver.js +139 -0
  203. package/dist/server/tools/sf_waiver.js.map +1 -0
  204. package/dist/server/tools/sf_work.d.ts +194 -0
  205. package/dist/server/tools/sf_work.d.ts.map +1 -1
  206. package/dist/server/tools/sf_work.js +603 -40
  207. package/dist/server/tools/sf_work.js.map +1 -1
  208. package/dist/shared/paths.d.ts +4 -0
  209. package/dist/shared/paths.d.ts.map +1 -1
  210. package/dist/shared/paths.js +6 -0
  211. package/dist/shared/paths.js.map +1 -1
  212. package/dist/shared/traceability_id_utils.js +3 -3
  213. package/dist/shared/traceability_id_utils.js.map +1 -1
  214. package/dist/types/pipeline_types.d.ts +4 -1
  215. package/dist/types/pipeline_types.d.ts.map +1 -1
  216. package/dist/verify/audit/probe_executor.d.ts +4 -1
  217. package/dist/verify/audit/probe_executor.d.ts.map +1 -1
  218. package/dist/verify/audit/probe_executor.js +4 -1
  219. package/dist/verify/audit/probe_executor.js.map +1 -1
  220. package/dist/verify/audit/probe_rule.d.ts +3 -0
  221. package/dist/verify/audit/probe_rule.d.ts.map +1 -1
  222. package/dist/verify/audit/probe_rule.js +3 -0
  223. package/dist/verify/audit/probe_rule.js.map +1 -1
  224. package/dist/verify/contracts/decision_workshop.d.ts.map +1 -1
  225. package/dist/verify/contracts/decision_workshop.js +4 -3
  226. package/dist/verify/contracts/decision_workshop.js.map +1 -1
  227. package/dist/verify/contracts/runtime_state_recovery_registry.d.ts.map +1 -1
  228. package/dist/verify/contracts/runtime_state_recovery_registry.js +0 -1
  229. package/dist/verify/contracts/runtime_state_recovery_registry.js.map +1 -1
  230. package/package.json +1 -1
  231. package/templates/build/enforced.md +263 -68
  232. package/templates/build//346/263/250/351/207/212/347/272/252/345/276/213.md +48 -0
  233. package/templates/build//346/265/213/350/257/225/344/274/230/345/205/210/347/274/226/347/240/201.md +1 -0
  234. package/templates/build//346/265/213/350/257/225/350/256/241/345/210/222.md +9 -4
  235. package/templates/build//347/274/226/347/240/201/347/272/252/345/276/213.md +28 -1
  236. package/templates/design/API/346/216/245/345/217/243/350/247/204/346/240/274/346/226/207/346/241/243.md +7 -0
  237. package/templates/design/enforced.md +204 -14
  238. package/templates/design//345/205/250/347/224/237/345/221/275/345/221/250/346/234/237/345/267/245/344/275/234/346/265/201/345/257/274/350/210/252.md +9 -7
  239. package/templates/design//345/210/207/347/211/207/350/247/204/345/210/222.md +4 -0
  240. package/templates/design//345/274/200/345/217/221/345/210/207/347/211/207/350/256/241/345/210/222.md +76 -0
  241. package/templates/design//346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/346/226/207/346/241/243.md +2 -0
  242. package/templates/design//346/236/266/346/236/204/350/256/276/350/256/241.md +24 -0
  243. package/templates/design//350/256/276/350/256/241/345/206/263/347/255/226/347/272/252/345/276/213.md +58 -0
  244. package/templates/design//350/256/276/350/256/241/350/264/250/351/207/217/350/246/201/347/202/271.md +58 -0
  245. package/templates/design//351/234/200/346/261/202/345/210/206/346/236/220.md +24 -0
  246. package/templates/operate/UI/350/247/206/350/247/211/351/252/214/346/224/266/347/272/252/345/276/213.md +85 -0
  247. package/templates/operate/enforced.md +42 -6
  248. package/templates/operate//345/217/221/345/270/203/350/257/264/346/230/216.md +19 -1
  249. package/templates/operate//351/203/250/347/275/262/351/205/215/347/275/256.md +10 -0
  250. package/templates/shared/enforced.md +37 -0
  251. package/templates/shared//345/267/245/344/275/234/346/265/201/345/257/274/350/210/252/345/245/221/347/272/246.md +1 -1
  252. package/templates/shared//345/267/245/344/275/234/346/265/201/347/241/254/350/247/204/345/210/231/345/245/221/347/272/246.md +2 -2
  253. package/templates/shared//347/240/224/350/256/250/350/256/260/345/275/225.md +54 -0
  254. package/templates/verify/enforced.md +92 -262
  255. package/templates/verify//344/272/244/344/273/230/345/256/214/345/244/207/346/200/247/345/256/241/346/237/245.md +1 -0
  256. package/templates/verify//344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +12 -2
  257. package/templates/verify//345/256/241/346/237/245/346/270/205/345/215/225.md +3 -1
  258. package/templates/verify//346/236/266/346/236/204/350/257/255/344/271/211/347/272/242/347/272/277.md +60 -0
  259. package/dist/core/scope_checker.d.ts +0 -29
  260. package/dist/core/scope_checker.d.ts.map +0 -1
  261. package/dist/core/scope_checker.js +0 -53
  262. package/dist/core/scope_checker.js.map +0 -1
  263. package/dist/gate/executors/executors_openapi_sync.d.ts +0 -23
  264. package/dist/gate/executors/executors_openapi_sync.d.ts.map +0 -1
  265. package/dist/gate/executors/executors_openapi_sync.js +0 -145
  266. package/dist/gate/executors/executors_openapi_sync.js.map +0 -1
@@ -12,19 +12,22 @@
12
12
  * 持久化:verify 时写 GateRecord
13
13
  */
14
14
  import { existsSync } from "node:fs";
15
+ import { join } from "node:path";
15
16
  import { z } from "zod";
16
17
  import { createToolRegistrar } from "./middleware.js";
17
18
  import { initializeDomainEngines, ALL_CONTRACTS } from "../../domain/index.js";
18
19
  import { DomainRegistry } from "../../domain/registry.js";
19
20
  import { GateRecordStore } from "../../core/gate_record_store.js";
21
+ import { AdversarialReviewStore, REVIEW_SAMPLE_K, checkSamplingComplete, computeIntersection, countBySeverity, } from "../../core/adversarial_review_store.js";
20
22
  import { isTransitionAllowed, detectLoop } from "../../core/domain_transition.js";
21
- import { checkScope } from "../../core/scope_checker.js";
22
23
  import { getFileGitHash } from "../../core/git_utils.js";
23
- import { loadGatesForDomain, evaluateGate } from "../../gate/gate_engine.js";
24
+ import { loadGatesForDomain, evaluateGate, computeRulesetHash } from "../../gate/gate_engine.js";
24
25
  import { resolveConfig } from "../../context/config/resolver.js";
25
26
  import { debug } from "../../shared/logger.js";
26
27
  import { SF_WORK_ACTIONS } from "../../gate/contracts/tool_actions.js";
27
28
  import { CROSS_VALIDATION_ARTIFACT_PATHS } from "../../gate/executors/executors_trace.js";
29
+ import { discoverSourceRoots } from "../../gate/scope_resolver.js";
30
+ import { aggregateMultiSrcHash } from "../../domain/engine_helpers.js";
28
31
  /** system_context(greenfield/brownfield)→ SystemType(新系统/老系统)映射 */
29
32
  function toSystemType(ctx) {
30
33
  return ctx === "brownfield" ? "老系统" : "新系统";
@@ -32,16 +35,20 @@ function toSystemType(ctx) {
32
35
  /** sf_work 参数 schema */
33
36
  const sfWorkSchema = {
34
37
  domain: z.enum(["design", "build", "verify", "operate"]).describe("目标域"),
35
- action: z.enum(SF_WORK_ACTIONS).describe("动作:observe=查看状态 / act=获取 prompt / verify=执行门禁"),
38
+ action: z.enum(SF_WORK_ACTIONS).describe("动作:observe=查看状态 / deliberate=产出前条件研讨(有疑问才触发头脑风暴+第一性原理,无疑问举证一行) / act=获取 prompt / verify=执行门禁 / verify_slice=切片完成时跑切片级门禁(docker compose build/up+HTTP验收+playwright e2e 硬验证) / adversarial_review=对抗审查(per-artifact 单产物 / cross-artifact 跨产物,K 次独立采样取交集、多利益方独立对抗)"),
36
39
  target: z.string().optional().describe("目标产物 kind(act 时必填)"),
37
40
  artifact_path: z.string().optional().describe("产物路径(verify 时指定)"),
41
+ artifact: z.string().optional().describe("对抗审查指定单产物 kind(adversarial_review per-artifact 模式;不传=cross-artifact 跨产物)"),
42
+ round: z.number().optional().describe("对抗审查轮次(1=首轮新建会话;>=2=后续轮,须带 review_id + findings)"),
43
+ review_id: z.string().optional().describe("对抗审查会话 ID(round>=2 必填,首轮返回)"),
44
+ findings: z.array(z.any()).optional().describe("对抗审查本轮发现(round>=2 客户端结构化提交:[{severity:'error|warning|info', artifact, location, quote, issue, suggestion}])"),
38
45
  };
39
46
  // 缓存域引擎注册表(每个 projectRoot 一个)
40
47
  const registryCache = new Map();
41
48
  export async function registerSfWorkTools(ctx) {
42
49
  const { registerSafeTool } = createToolRegistrar(ctx);
43
50
  const projectRoot = ctx.projectPath;
44
- registerSafeTool("sf_work", "域工作工具。observe:派生当前域状态。act:获取工作 prompt(AI 按此写文件)。verify:执行门禁检查。", sfWorkSchema, async (args) => {
51
+ registerSafeTool("sf_work", "域工作工具。observe:派生当前域状态。act:获取工作 prompt(AI 按此写文件)。verify:执行门禁检查。adversarial_review:排独立 agent 对抗审查所有产物(产物校验标准环节,不论门禁通过与否)。", sfWorkSchema, async (args) => {
45
52
  const domain = args.domain;
46
53
  const action = args.action;
47
54
  try {
@@ -84,9 +91,18 @@ export async function registerSfWorkTools(ctx) {
84
91
  if (action === "observe") {
85
92
  return await handleObserve(engine, task);
86
93
  }
94
+ else if (action === "deliberate") {
95
+ return await handleDeliberate(engine, task, args, projectRoot);
96
+ }
87
97
  else if (action === "act") {
88
98
  return await handleAct(engine, task, args, projectRoot);
89
99
  }
100
+ else if (action === "adversarial_review") {
101
+ return await handleAdversarialReview(projectRoot, task, args, taskContext);
102
+ }
103
+ else if (action === "verify_slice") {
104
+ return await handleVerifySlice(engine, task, projectRoot, taskContext, args);
105
+ }
90
106
  else {
91
107
  return await handleVerify(engine, task, projectRoot, taskContext, args);
92
108
  }
@@ -118,23 +134,177 @@ async function handleObserve(engine, task) {
118
134
  verified_artifacts: state.verified_artifacts.map((a) => a.kind),
119
135
  missing_upstream: state.missing_upstream,
120
136
  stale_gates: state.stale_gates.length,
137
+ blocked_artifacts: state.blocked_artifacts?.map((b) => ({ artifact: b.artifact.kind, blocked_by: b.blocked_by })),
138
+ ordered_plan: state.ordered_plan,
121
139
  pending_return: state.pending_return,
122
- next_step: state.pending_artifacts.length > 0
123
- ? `调用 sf_work domain="${state.current_domain}" action="act" target="${state.pending_artifacts[0].kind}"`
124
- : state.unverified_artifacts.length > 0
125
- ? `调用 sf_work domain="${state.current_domain}" action="verify"`
126
- : "所有产物已验证通过",
140
+ next_step: !state.ordered_plan || state.ordered_plan.length === 0
141
+ ? "所有产物已验证通过"
142
+ : state.ordered_plan[0].action === "wait"
143
+ ? `等待:${state.ordered_plan[0].kind}(${state.ordered_plan[0].reason})`
144
+ : `按 ordered_plan 推进,当前:sf_work domain="${state.current_domain}" action="${state.ordered_plan[0].action}" target="${state.ordered_plan[0].kind}"`,
127
145
  },
128
146
  };
129
147
  }
130
148
  /** act:组装 prompt */
131
- async function handleAct(engine, task, args, projectRoot) {
149
+ export async function handleAct(engine, task, args, projectRoot) {
132
150
  const target = args.target;
133
151
  if (!target) {
134
152
  return { result: { success: false, error: "act 时 target 必填(产物 kind)" } };
135
153
  }
154
+ // target 须是当前域产物(否则 engine.act 抛错,非 graceful)— 门禁前校验,返回可用清单
155
+ if (!engine.contract.produces.some((a) => a.kind === target)) {
156
+ return { result: {
157
+ success: false,
158
+ error: `「${target}」非本域(${engine.contract.name})产物。可用:${engine.contract.produces.map((a) => a.kind).join(", ")}`,
159
+ } };
160
+ }
161
+ // 架构决策研讨门禁(仅 design 域;内部 requiresArchitectureDecisionWorkshop 按 intent 判断是否真进,非架构意图自动放行)
162
+ if (engine.contract.name === "design") {
163
+ const { checkArchitectureDecisionWorkshopGate } = await import("../../gate/gate_checks.js");
164
+ const gateResult = await checkArchitectureDecisionWorkshopGate({ ctx: task, workflowIntent: task.intent });
165
+ if (!gateResult.allowed) {
166
+ return { result: {
167
+ success: false,
168
+ error: gateResult.findings?.join("; ") ?? "架构决策研讨门禁未通过",
169
+ gate_blocked: true,
170
+ recovery_guide: gateResult.recovery_guide_zh,
171
+ next_domain: gateResult.next_domain,
172
+ next_step: gateResult.recovery_guide_zh
173
+ ? `按恢复指引逐域确认。下一个待确认域: ${gateResult.next_domain},调 sf_task action=confirm_decisions`
174
+ : "架构决策研讨未完成",
175
+ } };
176
+ }
177
+ }
178
+ // ── act 可做性门禁(①②③ 确定性 enforced,复用 observe 派生,闭环「以 ordered_plan 为准」)──
179
+ // ④ 按序不强制(ordered_plan 仅软指引),避免误伤合法顺序调整
180
+ const eligibility = await engine.observe(task);
181
+ // 共用:target 已存在(重写:未验证 Reflexion 修复 / 已验证演进)→ ②③放行
182
+ // 重写不依赖上游新增(②)、不算并行开新产物(③)
183
+ const isRewrite = eligibility.unverified_artifacts.some((a) => a.kind === target)
184
+ || eligibility.verified_artifacts.some((a) => a.kind === target);
185
+ // ② 跨域缺上游 + 新产物 → 拒(重写放行:Reflexion 修复/演进不依赖上游新增,如 design 未验时 build code 重写修 lazy)
186
+ if (eligibility.missing_upstream.length > 0 && !isRewrite) {
187
+ const missing = eligibility.missing_upstream.map((u) => `${u.from_domain}.${u.artifact_kind}`).join("、");
188
+ return { result: {
189
+ success: false,
190
+ gate_blocked: true,
191
+ error: `本域缺上游产物(${missing})未通过门禁,不能 act 新产物「${target}」。先回上游域产出并 verify。(重写已有产物不拦:target 传该产物)`,
192
+ next_step: `先在上游域产出并 verify:${missing},再回本域 act`,
193
+ } };
194
+ }
195
+ // ① target 被同域依赖阻塞 → 拒(depends_on 未 verify)
196
+ const blockedBy = eligibility.blocked_artifacts?.find((b) => b.artifact.kind === target)?.blocked_by;
197
+ if (blockedBy) {
198
+ return { result: {
199
+ success: false,
200
+ gate_blocked: true,
201
+ error: `产物「${target}」被同域依赖阻塞,需先 verify:${blockedBy.join("、")}`,
202
+ next_step: `先 sf_work action="verify" 验证:${blockedBy.join("、")},再 act「${target}」`,
203
+ } };
204
+ }
205
+ // ③ 不并行/不跳步:存在未验证产物且 target 是新产物(pending,非重写)→ 拒(单产物推进)
206
+ // target ∈ unverified|verified → 放行(重写:Reflexion 修复 / 已验证演进,单产物迭代不算并行)
207
+ if (!isRewrite && eligibility.unverified_artifacts.length > 0) {
208
+ const unverified = eligibility.unverified_artifacts.map((a) => a.kind).join("、");
209
+ return { result: {
210
+ success: false,
211
+ gate_blocked: true,
212
+ error: `存在未验证产物(${unverified}),先 verify 再 act 新产物(单产物推进,不并行/不跳步)。若要重写产物(未验证修复 / 已验证演进),target 传该产物。`,
213
+ next_step: `先 sf_work action="verify" 验证:${unverified}`,
214
+ } };
215
+ }
216
+ // ④ 对抗审查不可省(新产物 act 前):当前域 + 上游域 verified 产物须都有 per_artifact 对抗审查
217
+ // enforce 第80行「adversarial_review 独立审查不可省」——防 AI 选择性跳审(如 db/api/slice_plan)
218
+ // 跨域 act(进 build)时查上游域(design)全 verified,堵每域最后产物漏审
219
+ // (design slice_plan 是 design 最后产物,无后续同域 act 触发④,靠跨域 build act 时查上游补)
220
+ // isRewrite(重写修复/演进)放行:单产物迭代不算开新产物,不截断 Reflexion(同②③,对称)
221
+ if (!isRewrite) {
222
+ const adversarialStore = new AdversarialReviewStore(projectRoot);
223
+ const reviews = await adversarialStore.listByTask(task.task_id);
224
+ // 审查完成(K 轮独立采样)即算「审过」——findings 留痕不阻断,只校验"审查执行过"
225
+ const reviewedKinds = new Set(reviews.filter((r) => r.mode === "per_artifact" && r.status === "completed").map((r) => r.artifact_path));
226
+ // 需审 verified:当前域 + 上游域(跨域查上游堵最后产物漏审)
227
+ const domainsToCheck = new Set([engine.contract.name]);
228
+ for (const req of engine.contract.requires_upstream)
229
+ domainsToCheck.add(req.from_domain);
230
+ const reviewRegistry = getOrCreateRegistry(projectRoot);
231
+ const unreviewed = [];
232
+ for (const dName of domainsToCheck) {
233
+ const dEngine = dName === engine.contract.name ? engine : reviewRegistry.get(dName);
234
+ if (!dEngine)
235
+ continue;
236
+ let dState = eligibility;
237
+ if (dName !== engine.contract.name) {
238
+ // observe 按 task.domain_focus 取域(observer.deriveState 用 ALL_CONTRACTS + domain_focus),
239
+ // 查上游域须临时切 domain_focus 到 dName,observe 后恢复(try/finally,单线程无并发)
240
+ const origDomain = task.domain_focus;
241
+ task.domain_focus = dName;
242
+ try {
243
+ dState = await dEngine.observe(task);
244
+ }
245
+ finally {
246
+ task.domain_focus = origDomain;
247
+ }
248
+ }
249
+ for (const a of dState.verified_artifacts) {
250
+ if (!reviewedKinds.has(a.kind))
251
+ unreviewed.push(a.kind);
252
+ }
253
+ }
254
+ if (unreviewed.length > 0) {
255
+ const unreviewedList = unreviewed.join("、");
256
+ return { result: {
257
+ success: false,
258
+ gate_blocked: true,
259
+ error: `已验证产物未做 per_artifact 对抗审查:${unreviewedList}。adversarial_review 独立审查不可省(所有域所有产物),先审再推进下一个产物。`,
260
+ next_step: `逐个调 sf_work action="adversarial_review" artifact="<kind>" 审:${unreviewedList}`,
261
+ } };
262
+ }
263
+ }
264
+ // ⑤ cross-artifact 不可省(跨域推进前,上游域所有产物做跨产物独立审查:多利益方独立对抗 + 跨产物一致性 + 完整性 + 研讨充分性)
265
+ // 触发:!isRewrite(新产物)+ requires_upstream 非空(进下游域 build/verify/operate)。design 域无上游不触发(域内推进)。
266
+ if (!isRewrite && engine.contract.requires_upstream.length > 0) {
267
+ const xStore = new AdversarialReviewStore(projectRoot);
268
+ const xReviews = await xStore.listByTask(task.task_id);
269
+ const xPerArtifacts = xReviews.filter((r) => r.mode === "per_artifact");
270
+ const xCrossArtifacts = xReviews.filter((r) => r.mode === "cross_artifact" && r.status === "completed");
271
+ const xCrossLatest = xCrossArtifacts.sort((a, b) => b.updated_at - a.updated_at)[0];
272
+ if (!xCrossLatest) {
273
+ return { result: {
274
+ success: false,
275
+ gate_blocked: true,
276
+ error: `跨域推进前须做 cross-artifact 对抗审查(上游域所有产物:多利益方独立对抗 + 跨产物一致性 + 完整性 + 研讨充分性),当前无完成记录。`,
277
+ next_step: `调 sf_work action="adversarial_review"(不传 artifact,cross-artifact 审当前域所有产物),完成 K 轮独立采样后再推进下一域`,
278
+ } };
279
+ }
280
+ const xLatestPer = xPerArtifacts.sort((a, b) => b.updated_at - a.updated_at)[0];
281
+ if (xLatestPer && xCrossLatest.updated_at < xLatestPer.updated_at) {
282
+ return { result: {
283
+ success: false,
284
+ gate_blocked: true,
285
+ error: `cross-artifact 审查已过时(在最新 per-artifact 审查之前)。上游域最新产物产出后须重新 cross-artifact 审。`,
286
+ next_step: `调 sf_work action="adversarial_review"(不传 artifact)重审当前域所有产物,完成后再次推进`,
287
+ } };
288
+ }
289
+ }
136
290
  // 加载上游产物内容(从文件系统读取真实内容)
137
291
  const upstream = await loadUpstreamContent(task, engine, args, projectRoot);
292
+ // 注入域内研讨记录为上游(deliberate 对话研讨后总结留痕,act 承接研讨结论;研讨记录在 docs/研讨记录/{域中文}/)
293
+ const actArtifact = engine.contract.produces.find((a) => a.kind === target);
294
+ if (actArtifact) {
295
+ const { deliberationRecordPath } = await import("../../domain/engine_helpers.js");
296
+ const deliberationPath = deliberationRecordPath(engine.contract.name, actArtifact);
297
+ if (existsSync(join(projectRoot, deliberationPath))) {
298
+ try {
299
+ const { readFileSync } = await import("node:fs");
300
+ const content = readFileSync(join(projectRoot, deliberationPath), "utf-8");
301
+ upstream["研讨记录"] = content.length > 8000 ? content.slice(0, 8000) + "\n...(研讨记录已截断)" : content;
302
+ }
303
+ catch {
304
+ // 读取失败忽略,act 仍可进行(研讨记录非必需上游)
305
+ }
306
+ }
307
+ }
138
308
  // 加载上次验证反馈(Reflexion 闭环:上次 verify 失败的真实 findings → 注入 prompt 让 AI 针对修复)
139
309
  // per-产物:只取当前 target 产物的失败记录(修 D8:多产物场景避免返回别的产物 findings 注入错位)
140
310
  const actArtifactPath = engine.contract.produces.find((a) => a.kind === target)?.path_pattern;
@@ -147,21 +317,6 @@ async function handleAct(engine, task, args, projectRoot) {
147
317
  result.prompt = result.prompt.replace("## 要求", `## 工程参考\n\n### 本阶段可用经验\n${guidance.summary}\n\n${guidance.fullText}\n\n## 要求`);
148
318
  }
149
319
  result.self_review_checklist = guidance.checklist;
150
- // scope 硬检查:校验 act 返回的 allowed_paths
151
- if (result.scope?.allowed_paths && result.scope.allowed_paths.length > 0) {
152
- for (const p of result.scope.allowed_paths) {
153
- const scopeResult = checkScope(p, result.scope.allowed_paths, result.scope.read_only_paths);
154
- if (!scopeResult.allowed) {
155
- return {
156
- result: {
157
- success: false,
158
- error: `scope 检查失败:${scopeResult.reason}`,
159
- scope_violation: p,
160
- },
161
- };
162
- }
163
- }
164
- }
165
320
  return {
166
321
  result: {
167
322
  success: true,
@@ -173,6 +328,26 @@ async function handleAct(engine, task, args, projectRoot) {
173
328
  },
174
329
  };
175
330
  }
331
+ /** deliberate:组装产出前条件研讨 prompt(有疑问才触发头脑风暴+第一性原理,无疑问举证一行)。
332
+ * 产出研讨记录到 docs/deliberation/{domain}/{kind}-研讨记录.md,供后续 act 承接。
333
+ * SoloForge 不调 LLM——只组装 prompt,AI 自己写研讨记录。 */
334
+ export async function handleDeliberate(engine, task, args, projectRoot) {
335
+ const target = args.target;
336
+ if (!target) {
337
+ return { result: { success: false, error: "deliberate 时 target 必填(产物 kind)" } };
338
+ }
339
+ const upstream = await loadUpstreamContent(task, engine, args, projectRoot);
340
+ const result = await engine.deliberate(task, target, upstream);
341
+ return {
342
+ result: {
343
+ success: true,
344
+ prompt: result.prompt,
345
+ template: result.template,
346
+ scope: result.scope,
347
+ next_step: `按研讨纪律产出研讨记录(有疑问走四步法、无疑问举证一行),然后调 sf_work action="act" target="${target}" 承接研讨结论产出实际产物`,
348
+ },
349
+ };
350
+ }
176
351
  /** verify:执行门禁检查并持久化 GateRecord */
177
352
  async function handleVerify(engine, task, projectRoot, taskContext, args) {
178
353
  const gateStore = new GateRecordStore(projectRoot);
@@ -190,23 +365,36 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
190
365
  const retryCount = existingRecord ? existingRecord.retry_count : 0;
191
366
  // 检查是否超过重试限制
192
367
  if (retryCount >= 5) {
193
- // 治本:重试超限升级为业务终态 escalated(原 TaskStore.update({status:escalated}))
194
- await taskContext.updateStatus(task.task_id, "escalated");
195
- return {
196
- result: {
197
- success: false,
198
- error: `门禁重试次数已达上限(${retryCount}/5),任务已升级处理`,
199
- },
200
- };
368
+ // retry 超限:若产物已修复(git hash 变 = stale),允许重验(retry_count 通过时重置,给人工修复后恢复);
369
+ // 未修复(hash 同)→ 真超限升级 escalated(防无限重试)。
370
+ const fullChk = join(projectRoot, artifact.path_pattern);
371
+ const currentHash = existsSync(fullChk) ? await getFileGitHash(artifact.path_pattern, projectRoot) : "";
372
+ const fixed = !!existingRecord && existingRecord.artifact_git_hash !== currentHash;
373
+ if (!fixed) {
374
+ await taskContext.updateStatus(task.task_id, "escalated");
375
+ return {
376
+ result: {
377
+ success: false,
378
+ error: `门禁重试次数已达上限(${retryCount}/5),任务已升级处理。修复产物(改文件内容使 git hash 变)后重新 verify 可重置计数恢复`,
379
+ },
380
+ };
381
+ }
382
+ // 已修复 → 继续重验(passed 时 retry_count 写 0 重置,见下)
201
383
  }
202
- // 检查产物文件是否存在
384
+ // 检查产物文件是否存在。code 产物(path_pattern=src/)多工程下代码分布在 根/{端}/src/,
385
+ // 任一工程 src/ 存在即算产出(discoverSourceRoots 探测);其他产物按精确路径。
203
386
  const fullArtifactPath = `${projectRoot}/${artifact.path_pattern}`;
204
- const artifactExists = existsSync(fullArtifactPath);
387
+ const isMultiSrcArtifact = /(^|\/)src\/?$/.test(artifact.path_pattern);
388
+ const artifactExists = isMultiSrcArtifact
389
+ ? discoverSourceRoots(projectRoot).length > 0 || existsSync(fullArtifactPath)
390
+ : existsSync(fullArtifactPath);
205
391
  // 获取产物 git hash(防漂移)
206
392
  let artifactGitHash = "";
207
393
  if (artifactExists) {
208
394
  try {
209
- artifactGitHash = await getFileGitHash(artifact.path_pattern, projectRoot);
395
+ artifactGitHash = isMultiSrcArtifact
396
+ ? await aggregateMultiSrcHash(projectRoot)
397
+ : await getFileGitHash(artifact.path_pattern, projectRoot);
210
398
  }
211
399
  catch {
212
400
  artifactGitHash = "untracked";
@@ -215,6 +403,9 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
215
403
  // 真实执行域门禁:让 verify 名副其实,findings 含 ARC/PER 等真实违规(Reflexion 反馈数据源)。
216
404
  // 仅在产物存在时跑 gate(产物不存在时 gate 检查无意义,直接报 artifact_exists)。
217
405
  const gateFindings = [];
406
+ const waivedFindings = [];
407
+ const appliedWaiverIds = new Set();
408
+ const skippedSliceChecks = []; // 透明层:切片级 check 的 skipped(docker/e2e 没真跑),供 next_step 提示防 AI 被 passed=true 骗
218
409
  let gatePassed = true;
219
410
  let recoverySummary = "";
220
411
  if (artifactExists) {
@@ -232,11 +423,16 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
232
423
  const evalCtx = {
233
424
  projectRoot,
234
425
  stage: domain,
426
+ artifactPath: artifact.path_pattern,
235
427
  systemType: toSystemType(task.system_context),
236
428
  tech_stack: techStack,
237
429
  taskContext: task,
238
430
  };
239
431
  for (const gate of gates) {
432
+ // 强制层:普通 verify 跳过 slice-gate(切片级 docker/e2e 由专门 verify_slice 触发,
433
+ // 避免混跑误伤开发调试 + 让切片验证有独立硬入口)
434
+ if (gate.gate_scope === "slice")
435
+ continue;
240
436
  // per-产物路由:只跑无 required_artifact 的通用 check + 命中当前 verify 产物的专属 check。
241
437
  // 修复 per-域缺陷:此前 verify 任一产物跑整个 domain-gate,逐个产出时前面产物被后面章节检查阻断。
242
438
  const filteredChecks = gate.checks.filter((c) => {
@@ -247,6 +443,12 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
247
443
  return (CROSS_VALIDATION_ARTIFACT_PATHS[c.required_artifact] || []).includes(artifact.path_pattern);
248
444
  });
249
445
  const result = await evaluateGate({ ...gate, checks: filteredChecks }, evalCtx);
446
+ // 透明层:聚合切片级 check 的 skipped(docker/e2e 没真跑),供 next_step 提示防 AI 被假通过骗
447
+ for (const cr of result.check_results) {
448
+ if (cr.skipped && /^SLICE-/.test(cr.check_id) && !skippedSliceChecks.includes(cr.check_id)) {
449
+ skippedSliceChecks.push(cr.check_id);
450
+ }
451
+ }
250
452
  if (!result.passed) {
251
453
  gatePassed = false;
252
454
  recoverySummary = result.recovery_summary || recoverySummary;
@@ -270,6 +472,25 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
270
472
  message: d.error || d.rule || d.name,
271
473
  });
272
474
  }
475
+ // 被豁免(waiver)的失败项:标 waived=true/waiver_id/waiver_reason 供 Reflexion 看真相,
476
+ // 但不阻断(gate_engine 已从 blocking_errors 排除,gatePassed 不受影响)。
477
+ for (const d of result.waived_details) {
478
+ const applied = result.applied_waivers.find((a) => a.check_id === d.check_id);
479
+ const f = {
480
+ check_id: d.check_id,
481
+ check_name: d.name,
482
+ passed: false,
483
+ severity: "error",
484
+ message: d.error || d.rule || d.name,
485
+ waived: true,
486
+ waiver_id: applied?.waiver_id,
487
+ waiver_reason: applied?.reason,
488
+ };
489
+ gateFindings.push(f);
490
+ waivedFindings.push(f);
491
+ if (applied)
492
+ appliedWaiverIds.add(applied.waiver_id);
493
+ }
273
494
  }
274
495
  }
275
496
  catch (err) {
@@ -285,7 +506,32 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
285
506
  });
286
507
  }
287
508
  }
509
+ // 完整性校验(根因治本:所有域所有产物的 references + companion_files 须存在,防引用悬空/附件遗漏)。
510
+ // 不靠手填 check.evidence_required,从 contract 声明派生——所有域所有产物 verify 时自动全覆盖。
511
+ const bundleMissing = [];
512
+ for (const ref of artifact.references ?? []) {
513
+ if (!existsSync(join(projectRoot, ref)))
514
+ bundleMissing.push(`引用悬空: ${ref}`);
515
+ }
516
+ for (const cf of artifact.companion_files ?? []) {
517
+ if (!existsSync(join(projectRoot, cf)))
518
+ bundleMissing.push(`附件缺失: ${cf}`);
519
+ }
520
+ for (const m of bundleMissing) {
521
+ gateFindings.push({
522
+ check_id: "bundle_integrity",
523
+ check_name: "产物完整性(引用/附件)",
524
+ passed: false,
525
+ severity: "error",
526
+ message: `产物 ${artifact.kind} ${m}`,
527
+ });
528
+ gatePassed = false;
529
+ }
288
530
  const passed = artifactExists && gatePassed;
531
+ // retry 超限恢复:任务因 retry 5 升级 escalated,人工修复后 verify 通过 → 恢复 executing(继续推进)
532
+ if (passed && task.status === "escalated") {
533
+ await taskContext.updateStatus(task.task_id, "executing");
534
+ }
289
535
  // 构建并持久化 GateRecord(task_id 用于 Reflexion 反馈闭环按 task 查询)
290
536
  const record = {
291
537
  domain: domain,
@@ -294,7 +540,7 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
294
540
  artifact_path: artifact.path_pattern,
295
541
  artifact_git_hash: artifactGitHash,
296
542
  passed,
297
- retry_count: passed ? retryCount : retryCount + 1,
543
+ retry_count: passed ? 0 : retryCount + 1, // 通过则重置计数(新一轮),失败累计
298
544
  max_retries: 5,
299
545
  findings: !artifactExists
300
546
  ? [{ check_id: "artifact_exists", check_name: "产物存在性检查", passed: false, severity: "error", message: `产物文件 ${artifact.path_pattern} 不存在` }]
@@ -303,6 +549,9 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
303
549
  ? `请先调用 sf_work domain="${domain}" action="act" target="${artifact.kind}" 生成产物`
304
550
  : (recoverySummary || undefined),
305
551
  evaluated_at: Date.now(),
552
+ ruleset_hash: computeRulesetHash(domain),
553
+ waived_findings: waivedFindings.length > 0 ? waivedFindings : undefined,
554
+ waiver_ids: appliedWaiverIds.size > 0 ? Array.from(appliedWaiverIds) : undefined,
306
555
  };
307
556
  await gateStore.write(record);
308
557
  // 治本:检查 pending_return 恢复(通过后才恢复)
@@ -331,12 +580,326 @@ async function handleVerify(engine, task, projectRoot, taskContext, args) {
331
580
  gate_record: record,
332
581
  retry_count: record.retry_count,
333
582
  next_step: passed
334
- ? "门禁通过。可继续下一个产物或切换域。"
335
- : "门禁未通过。按 recovery_guidance 修复后重新 verify。",
583
+ ? (skippedSliceChecks.length > 0
584
+ ? `门禁通过,但 ⚠️ 切片级验证 [${skippedSliceChecks.join(", ")}] skipped(docker/e2e 未真执行)——若项目需 docker/e2e 验证,请产 docker-compose.yml / playwright.config 后重验,确保切片交付前真跑。之后调 sf_work action=adversarial_review 对抗复审。`
585
+ : "门禁通过。建议调 sf_work action=adversarial_review 排独立 agent 对抗复审(产物校验标准环节,全新视角复核,不论门禁通过与否)。复审后再继续下一个产物或切换域。")
586
+ : "门禁未通过。按 recovery_guidance 修复后重新 verify;修复后建议 sf_work action=adversarial_review 排独立 agent 对抗复审。",
336
587
  },
337
588
  };
338
589
  }
339
- /** 加载上游产物内容(从文件系统读取) */
590
+ /**
591
+ * verify_slice:切片完成时专门跑切片级门禁(slice-gate: docker compose build/up + HTTP 验收 + playwright e2e)。
592
+ * 强制层:普通 verify 跳过 slice-gate(不混跑误伤开发),切片完成时 AI 显式调此 action 触发 docker/e2e 硬验证。
593
+ * 不写 per-artifact GateRecord(切片级非产物);结果直接返回(passed + findings + skipped 透明)。
594
+ */
595
+ async function handleVerifySlice(engine, task, projectRoot, _taskContext, _args) {
596
+ const domain = engine.contract.name;
597
+ // design 域无切片实现(设计阶段无代码/部署)
598
+ if (domain === "design") {
599
+ return { result: { success: true, passed: true, message: "design 域无切片实现,无需切片级验证" } };
600
+ }
601
+ // 解析技术栈(slice-gate 的 check 可能按技术栈过滤)
602
+ let techStack = null;
603
+ try {
604
+ techStack = (await resolveConfig(projectRoot)).tech_stack;
605
+ }
606
+ catch (configErr) {
607
+ debug("切片验证", `技术栈解析失败,降级全跑: ${configErr instanceof Error ? configErr.message : String(configErr)}`);
608
+ }
609
+ // 只跑 gate_scope=slice 的 gate(普通 verify 已跳过,此处专门触发 docker/e2e)
610
+ const sliceGates = loadGatesForDomain(domain, projectRoot).filter((g) => g.gate_scope === "slice");
611
+ if (sliceGates.length === 0) {
612
+ return { result: { success: true, passed: true, message: "项目无切片级门禁 check(SLICE-BUILD/UP/HTTP/E2E),切片验证跳过" } };
613
+ }
614
+ const evalCtx = {
615
+ projectRoot,
616
+ stage: domain,
617
+ systemType: toSystemType(task.system_context),
618
+ tech_stack: techStack,
619
+ taskContext: task,
620
+ };
621
+ let slicePassed = true;
622
+ const findings = [];
623
+ const skippedChecks = [];
624
+ for (const gate of sliceGates) {
625
+ const result = await evaluateGate(gate, evalCtx);
626
+ if (!result.passed)
627
+ slicePassed = false;
628
+ for (const d of result.blocking_details) {
629
+ findings.push({ check_id: d.check_id, severity: "error", message: d.error || d.rule || d.name });
630
+ }
631
+ for (const d of result.warning_details) {
632
+ findings.push({ check_id: d.check_id, severity: "warning", message: d.error || d.rule || d.name });
633
+ }
634
+ for (const cr of result.check_results) {
635
+ if (cr.skipped && !skippedChecks.includes(cr.check_id))
636
+ skippedChecks.push(cr.check_id);
637
+ }
638
+ }
639
+ return {
640
+ result: {
641
+ success: true,
642
+ passed: slicePassed,
643
+ domain,
644
+ gate_scope: "slice",
645
+ findings,
646
+ skipped_slice_checks: skippedChecks,
647
+ message: slicePassed
648
+ ? (skippedChecks.length > 0
649
+ ? `切片级验证通过,但 ⚠️ [${skippedChecks.join(", ")}] skipped(docker/e2e 未真执行)——产 docker-compose.yml / playwright.config 后重验 verify_slice`
650
+ : "切片级验证通过(docker/e2e 真执行)")
651
+ : `切片级验证未通过(docker/e2e 失败),按 findings 修复后重验 verify_slice`,
652
+ next_step: slicePassed
653
+ ? "切片级验证通过,可推进下一切片或切换域"
654
+ : "修复 docker/e2e 问题后重新 verify_slice,通过后再推进",
655
+ },
656
+ };
657
+ }
658
+ /** adversarial_review:对抗性独立审查(per-artifact 单产物 / cross-artifact 跨产物一致性)。
659
+ * SoloForge 不调 LLM——只组装 prompt 返回,AI 用独立 session/subagent 执行(避免确认偏见)。
660
+ * K 次独立采样取交集:K=REVIEW_SAMPLE_K 轮独立审查,每轮不参考前轮结论,完成后取多数交集。
661
+ * 确定性边界原则:findings 留痕不阻断——放弃"零 error 收敛"(LLM 裁判不可收敛),act 只校验"审过"。
662
+ * 多利益方独立对抗:用户/攻击者/维护者/性能/合规 各自独立目标函数,避免单一视角确认偏见。 */
663
+ export async function handleAdversarialReview(projectRoot, task, args, _taskContext) {
664
+ const store = new AdversarialReviewStore(projectRoot);
665
+ const round = typeof args.round === "number" ? args.round : 1;
666
+ const artifactKind = args.artifact;
667
+ // ── 首轮:创建会话,组装多利益方审查 prompt ──
668
+ if (round <= 1) {
669
+ const mode = artifactKind ? "per_artifact" : "cross_artifact";
670
+ const artifacts = await collectReviewArtifacts(projectRoot, mode, artifactKind);
671
+ const reviewId = store.generateReviewId();
672
+ const now = Date.now();
673
+ const record = {
674
+ review_id: reviewId,
675
+ task_id: task.task_id,
676
+ mode,
677
+ artifact_path: mode === "per_artifact" ? artifactKind : undefined, // per_artifact 模式此字段存 artifact **kind**(非文件路径);④门禁 reviewedKinds 据此比 verified_artifacts.kind——名实澄清(domain/core 审查),改存储格式需同步 ④比较+迁移旧记录
678
+ artifacts,
679
+ rounds: [],
680
+ total_rounds: 0,
681
+ completed: false,
682
+ sample_k: REVIEW_SAMPLE_K,
683
+ retained_findings: [],
684
+ status: "in_progress",
685
+ created_at: now,
686
+ updated_at: now,
687
+ };
688
+ await store.create(record);
689
+ const prompt = buildReviewPrompt(artifacts, mode, "");
690
+ return {
691
+ result: {
692
+ success: true,
693
+ action: "adversarial_review",
694
+ review_id: reviewId,
695
+ round: 1,
696
+ mode,
697
+ review_mode: mode === "per_artifact" ? "per-artifact 单产物深度对抗审查" : "cross-artifact 跨产物一致性对抗审查",
698
+ artifact_count: artifacts.length,
699
+ artifacts,
700
+ prompt,
701
+ next_step: `用独立 session/subagent 执行上述 prompt(勿在产出产物的同一 session 审查,避免确认偏见)。审查完把 findings 结构化带回:sf_work action="adversarial_review" round=2 review_id="${reviewId}" findings=[{severity,artifact,location,quote,issue,suggestion},...]`,
702
+ },
703
+ };
704
+ }
705
+ // ── 后续轮:处理 findings,判定 K 轮采样是否完成 ──
706
+ const reviewId = args.review_id;
707
+ if (!reviewId) {
708
+ return { result: { success: false, error: `round=${round} 须带 review_id(首轮返回的会话 ID)` } };
709
+ }
710
+ const record = await store.read(reviewId);
711
+ if (!record) {
712
+ return { result: { success: false, error: `review_id ${reviewId} 不存在(可能跨会话丢失)` } };
713
+ }
714
+ const findings = normalizeFindings(args.findings);
715
+ const { error_count, warning_count } = countBySeverity(findings);
716
+ const roundRecord = {
717
+ round,
718
+ findings,
719
+ error_count,
720
+ warning_count,
721
+ reviewed_at: Date.now(),
722
+ };
723
+ record.rounds.push(roundRecord);
724
+ record.total_rounds = round;
725
+ record.updated_at = Date.now();
726
+ const k = record.sample_k ?? REVIEW_SAMPLE_K;
727
+ // K 次独立采样完成 → 聚合取交集,达标(findings 留痕不阻断)
728
+ if (checkSamplingComplete(record.rounds, k)) {
729
+ const intersection = computeIntersection(record.rounds);
730
+ record.status = "completed";
731
+ record.completed = true;
732
+ record.completed_round = round;
733
+ record.intersection_findings = intersection;
734
+ record.retained_findings = intersection.filter((f) => !f.dismissed);
735
+ await store.write(record);
736
+ return {
737
+ result: {
738
+ success: true,
739
+ action: "adversarial_review",
740
+ review_id: reviewId,
741
+ round,
742
+ review_status: "completed",
743
+ sample_k: k,
744
+ round_summary: { errors: error_count, warnings: warning_count },
745
+ intersection_count: intersection.length,
746
+ intersection_findings: intersection,
747
+ retained_count: record.retained_findings.length,
748
+ completed: true,
749
+ next_step: `对抗审查完成(${k} 次独立采样,交集 ${intersection.length} 条发现)。error 留痕不阻断,可继续下一产物或切换域。`,
750
+ },
751
+ };
752
+ }
753
+ // 未完成 K 轮 → 继续(每轮独立,不注入前轮反馈,保独立性)
754
+ await store.write(record);
755
+ const prompt = buildReviewPrompt(record.artifacts, record.mode, "");
756
+ return {
757
+ result: {
758
+ success: true,
759
+ action: "adversarial_review",
760
+ review_id: reviewId,
761
+ round,
762
+ review_status: "in_progress",
763
+ rounds_completed: record.rounds.length,
764
+ sample_k: k,
765
+ round_summary: { errors: error_count, warnings: warning_count },
766
+ prompt,
767
+ next_step: `已完成 ${record.rounds.length}/${k} 轮独立采样。用全新独立 session 执行上述 prompt(不参考前轮结论,保持独立性),完成后带回 findings:sf_work action="adversarial_review" round=${round + 1} review_id="${reviewId}" findings=[...]`,
768
+ },
769
+ };
770
+ }
771
+ /** 收集审查产物:per=指定单产物 / cross=所有域所有已产出 docs/ 产物;二者都额外收集研讨记录(审查充分性维度) */
772
+ async function collectReviewArtifacts(projectRoot, mode, artifactKind) {
773
+ const artifacts = [];
774
+ if (mode === "per_artifact" && artifactKind) {
775
+ for (const c of ALL_CONTRACTS) {
776
+ const p = c.produces.find((a) => a.kind === artifactKind);
777
+ if (p && p.path_pattern.startsWith("docs/") && existsSync(join(projectRoot, p.path_pattern))) {
778
+ artifacts.push(`${p.path_pattern}(${c.name}/${p.kind})`);
779
+ }
780
+ }
781
+ }
782
+ else {
783
+ for (const c of ALL_CONTRACTS) {
784
+ for (const p of c.produces) {
785
+ if (!p.path_pattern.startsWith("docs/"))
786
+ continue;
787
+ if (existsSync(join(projectRoot, p.path_pattern))) {
788
+ artifacts.push(`${p.path_pattern}(${c.name}/${p.kind})`);
789
+ }
790
+ }
791
+ }
792
+ }
793
+ // 研讨记录(docs/deliberation/):审查"研讨充分性"维度——产物决策是否真承接研讨结论
794
+ await collectDeliberationRecords(projectRoot, artifacts);
795
+ return artifacts;
796
+ }
797
+ /** 扫描 docs/deliberation/ 收集研讨记录(条件触发的研讨留痕,cross 审查复核充分性) */
798
+ async function collectDeliberationRecords(projectRoot, artifacts) {
799
+ const { readdirSync, statSync } = await import("node:fs");
800
+ const root = join(projectRoot, "docs", "研讨记录");
801
+ if (!existsSync(root))
802
+ return;
803
+ const walk = (dir) => {
804
+ for (const entry of readdirSync(dir)) {
805
+ const full = join(dir, entry);
806
+ if (statSync(full).isDirectory()) {
807
+ walk(full);
808
+ }
809
+ else if (entry.endsWith(".md")) {
810
+ const rel = full.replace(join(projectRoot) + "/", "");
811
+ artifacts.push(`${rel}(研讨记录)`);
812
+ }
813
+ }
814
+ };
815
+ walk(root);
816
+ }
817
+ /** 组装多利益方独立对抗审查 prompt。
818
+ * 5 方(用户/攻击者/维护者/性能/合规)各自独立目标函数,审查前不得互参,最后交叉汇总。
819
+ * K 次独立采样语义写进 prompt:每轮独立不互参,完成后取多数交集。 */
820
+ function buildReviewPrompt(artifacts, mode, prevFeedback) {
821
+ const modeLabel = mode === "per_artifact" ? "per-artifact 单产物深度" : "cross-artifact 跨产物一致性";
822
+ const extraDim = mode === "cross_artifact"
823
+ ? `### 跨产物一致性(cross 专项)
824
+ - REQ→API→测试 覆盖链是否**语义**对齐(不只 ID 在,而是意图满足;确定性 cross_validation 只抓 ID/字段名,语义层矛盾只有你能抓)
825
+ - 同一事物跨产物描述是否一致(需求 vs 架构 vs API vs 数据库)
826
+ - **完整性方审查**:REQ→API→DB→切片→代码 全链路覆盖——每个 REQ 有对应 API?每个 API 操作的表在 DB 存在?每个 API 有代码方法实现?有无遗漏致业务断裂?
827
+ - **前端承接完整性**(涉及 FE-* 时):架构 §6.3 每个 FE-* 端有骨架切片?骨架切片落地了路由/状态/API service 壳/认证拦截器?前端各端覆盖需求 §9 声明的所有端?前后端 API 契约一致?
828
+ - **研讨记录充分性**:复杂产物的研讨记录(docs/deliberation/)若全是"无疑问一行举证"——质疑:这个点真无疑问吗?产出是否真承接研讨结论?`
829
+ : `### 单产物内部深度(per 专项)
830
+ - 该产物内部逻辑自洽、章节完整、无空章节/TODO/占位符
831
+ - 按产物类型重点关注:API 文档→字段映射与 schema 对齐;架构→决策依据/回滚/兼容;测试→REQ 覆盖/可执行性`;
832
+ const feedbackSection = prevFeedback
833
+ ? `\n## ⚠️ 上轮审查发现(必须先验证是否已修复,再继续找新问题)\n${prevFeedback}\n`
834
+ : "";
835
+ return `# 对抗性独立审查(${modeLabel},K-sample 独立采样)
836
+
837
+ 你是**独立审查 agent**,与产物创建 agent 完全隔离(不同 session、全新视角)。**默认所有产物有问题**,禁止说"看起来没问题"。每条发现必须 \`quote\` 引用原文——空泛=凑数=无效,会被丢弃。
838
+
839
+ ## 审查范围
840
+ ${artifacts.length > 0 ? artifacts.map((a) => `- \`${a}\``).join("\n") : "(暂无已产出产物,先产出再审查,本轮返回空 findings)"}
841
+
842
+ ## 多利益方独立对抗(每方独立审查,审查前不得参考他方结论,最后才交叉汇总)
843
+
844
+ ### 用户方审查
845
+ 根本需求是否被承接?REQ→产物 覆盖链是否语义对齐(需求 → 实现 的意图满足)?
846
+
847
+ ### 攻击者方审查
848
+ 越权 / 注入 / 路径穿越 / 敏感信息泄露 / 破坏性操作无确认?
849
+
850
+ ### 维护者方审查
851
+ 可读性 / 可改性 / 技术债 / 未接入主链路的孤岛 / 空章节 / TODO?
852
+
853
+ ### 性能方审查
854
+ N+1 / 瓶颈 / 资源泄漏 / 锁竞争 / 扩展性?
855
+
856
+ ### 合规方审查
857
+ 规范一致性 / 隐私 / 回滚方案 / 字段契约对齐 / 破坏性变更确认?
858
+
859
+ ### 前端架构方审查(涉及 FE-* 时)
860
+ 骨架完整性(路由/状态/组件分层/API service 壳)?前端权限承接后端 RBAC?前后端 API 契约一致?多端 shared 边界清晰?状态管理合理?
861
+
862
+ ${extraDim}
863
+ ${feedbackSection}
864
+ ## 采样规则(K 次独立采样取交集)
865
+ - 你是本轮(第 N/K 轮)**独立采样**,**不得参考前轮结论**(保持独立性,避免确认偏见)
866
+ - 每条发现必须 \`quote\` 引用原文——空泛=凑数=无效,会被丢弃
867
+ - error/warning/info 均如实记录,**不追求"零 error"**(findings 留痕不阻断,引擎只取多轮复现的交集)
868
+ - 禁止凑数、禁止为省事放过真问题、禁止用 warning 冒充 error
869
+
870
+ ## 输出格式(严格 JSON 数组,引擎据此计轮数取交集)
871
+ \`\`\`json
872
+ [
873
+ {"severity":"error|warning|info","artifact":"产物路径","location":"章节/行","quote":"引用原文","issue":"问题","suggestion":"修复建议"}
874
+ ]
875
+ \`\`\`
876
+ - 无问题也必须返回 \`[]\`(这是达标的信号,不是"没问题"四个字)
877
+ - error 优先,按 severity 排序`;
878
+ }
879
+ /** 规范化客户端提交的 findings(容错:校验 severity + 补全字符串字段 + 过滤无效条目) */
880
+ function normalizeFindings(raw) {
881
+ if (!Array.isArray(raw))
882
+ return [];
883
+ const findings = [];
884
+ for (const item of raw) {
885
+ if (!item || typeof item !== "object")
886
+ continue;
887
+ const f = item;
888
+ const severity = f.severity;
889
+ if (severity !== "error" && severity !== "warning" && severity !== "info")
890
+ continue;
891
+ findings.push({
892
+ severity,
893
+ artifact: String(f.artifact ?? ""),
894
+ location: String(f.location ?? ""),
895
+ quote: String(f.quote ?? ""),
896
+ issue: String(f.issue ?? ""),
897
+ suggestion: String(f.suggestion ?? ""),
898
+ });
899
+ }
900
+ return findings;
901
+ }
902
+ /** 加载上游产物内容(从文件系统读取真实内容) */
340
903
  async function loadUpstreamContent(task, engine, _args, projectRoot) {
341
904
  const upstream = {};
342
905
  const { readFileSync } = await import("node:fs");