@hunter-harness/workflow-harness 0.2.35 → 0.2.37

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 (226) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/contracts/build-profile-v3.schema.json +147 -0
  3. package/harness/bundles/general/claude-code/contracts/doctor-capability.schema.json +43 -0
  4. package/harness/bundles/general/claude-code/contracts/external-platform-observation.schema.json +62 -0
  5. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  9. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  15. package/harness/bundles/general/claude-code/harness-sync/reference.md +2 -2
  16. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  17. package/harness/bundles/general/claude-code/harness-test/pitfalls.md +1 -1
  18. package/harness/bundles/general/claude-code/protocols/ci-layering-protocol.md +22 -0
  19. package/harness/bundles/general/claude-code/protocols/external-platform-protocol.md +27 -0
  20. package/harness/bundles/general/claude-code/protocols/memory-closeout-protocol.md +21 -0
  21. package/harness/bundles/general/claude-code/protocols/registry-governance-protocol.md +31 -0
  22. package/harness/bundles/general/claude-code/protocols/verification-graph-protocol.md +26 -0
  23. package/harness/bundles/general/claude-code/scripts/harness_archive.py +13 -9
  24. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +121 -12
  25. package/harness/bundles/general/claude-code/scripts/harness_preflight.py +2 -2
  26. package/harness/bundles/general/claude-code/scripts/harness_profile.py +308 -23
  27. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  28. package/harness/bundles/general/codebuddy/contracts/build-profile-v3.schema.json +147 -0
  29. package/harness/bundles/general/codebuddy/contracts/doctor-capability.schema.json +43 -0
  30. package/harness/bundles/general/codebuddy/contracts/external-platform-observation.schema.json +62 -0
  31. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  32. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  33. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  34. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  35. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  38. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  41. package/harness/bundles/general/codebuddy/harness-sync/reference.md +2 -2
  42. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  43. package/harness/bundles/general/codebuddy/harness-test/pitfalls.md +1 -1
  44. package/harness/bundles/general/codebuddy/protocols/ci-layering-protocol.md +22 -0
  45. package/harness/bundles/general/codebuddy/protocols/external-platform-protocol.md +27 -0
  46. package/harness/bundles/general/codebuddy/protocols/memory-closeout-protocol.md +21 -0
  47. package/harness/bundles/general/codebuddy/protocols/registry-governance-protocol.md +31 -0
  48. package/harness/bundles/general/codebuddy/protocols/verification-graph-protocol.md +26 -0
  49. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +13 -9
  50. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +121 -12
  51. package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +2 -2
  52. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +308 -23
  53. package/harness/bundles/general/codex/.harness-build.json +1 -1
  54. package/harness/bundles/general/codex/contracts/build-profile-v3.schema.json +147 -0
  55. package/harness/bundles/general/codex/contracts/doctor-capability.schema.json +43 -0
  56. package/harness/bundles/general/codex/contracts/external-platform-observation.schema.json +62 -0
  57. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  58. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  59. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  60. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  61. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  62. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  63. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  64. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  65. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  66. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-sync/reference.md +2 -2
  68. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  69. package/harness/bundles/general/codex/harness-test/pitfalls.md +1 -1
  70. package/harness/bundles/general/codex/protocols/ci-layering-protocol.md +22 -0
  71. package/harness/bundles/general/codex/protocols/external-platform-protocol.md +27 -0
  72. package/harness/bundles/general/codex/protocols/memory-closeout-protocol.md +21 -0
  73. package/harness/bundles/general/codex/protocols/registry-governance-protocol.md +31 -0
  74. package/harness/bundles/general/codex/protocols/verification-graph-protocol.md +26 -0
  75. package/harness/bundles/general/codex/scripts/harness_archive.py +13 -9
  76. package/harness/bundles/general/codex/scripts/harness_ledger.py +121 -12
  77. package/harness/bundles/general/codex/scripts/harness_preflight.py +2 -2
  78. package/harness/bundles/general/codex/scripts/harness_profile.py +308 -23
  79. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  80. package/harness/bundles/general/cursor/contracts/build-profile-v3.schema.json +147 -0
  81. package/harness/bundles/general/cursor/contracts/doctor-capability.schema.json +43 -0
  82. package/harness/bundles/general/cursor/contracts/external-platform-observation.schema.json +62 -0
  83. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  84. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  85. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  86. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  87. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  88. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  89. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  90. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  91. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  92. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  93. package/harness/bundles/general/cursor/harness-sync/reference.md +2 -2
  94. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  95. package/harness/bundles/general/cursor/harness-test/pitfalls.md +1 -1
  96. package/harness/bundles/general/cursor/protocols/ci-layering-protocol.md +22 -0
  97. package/harness/bundles/general/cursor/protocols/external-platform-protocol.md +27 -0
  98. package/harness/bundles/general/cursor/protocols/memory-closeout-protocol.md +21 -0
  99. package/harness/bundles/general/cursor/protocols/registry-governance-protocol.md +31 -0
  100. package/harness/bundles/general/cursor/protocols/verification-graph-protocol.md +26 -0
  101. package/harness/bundles/general/cursor/scripts/harness_archive.py +13 -9
  102. package/harness/bundles/general/cursor/scripts/harness_ledger.py +121 -12
  103. package/harness/bundles/general/cursor/scripts/harness_preflight.py +2 -2
  104. package/harness/bundles/general/cursor/scripts/harness_profile.py +308 -23
  105. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  106. package/harness/bundles/java/claude-code/contracts/build-profile-v3.schema.json +147 -0
  107. package/harness/bundles/java/claude-code/contracts/doctor-capability.schema.json +43 -0
  108. package/harness/bundles/java/claude-code/contracts/external-platform-observation.schema.json +62 -0
  109. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  110. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  111. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  112. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  113. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  114. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  115. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  116. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  117. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  118. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  119. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  120. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  121. package/harness/bundles/java/claude-code/harness-sync/reference.md +2 -2
  122. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  123. package/harness/bundles/java/claude-code/harness-test/pitfalls.md +1 -1
  124. package/harness/bundles/java/claude-code/protocols/ci-layering-protocol.md +22 -0
  125. package/harness/bundles/java/claude-code/protocols/external-platform-protocol.md +27 -0
  126. package/harness/bundles/java/claude-code/protocols/memory-closeout-protocol.md +21 -0
  127. package/harness/bundles/java/claude-code/protocols/registry-governance-protocol.md +31 -0
  128. package/harness/bundles/java/claude-code/protocols/verification-graph-protocol.md +26 -0
  129. package/harness/bundles/java/claude-code/scripts/harness_archive.py +13 -9
  130. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +121 -12
  131. package/harness/bundles/java/claude-code/scripts/harness_preflight.py +2 -2
  132. package/harness/bundles/java/claude-code/scripts/harness_profile.py +308 -23
  133. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  134. package/harness/bundles/java/codebuddy/contracts/build-profile-v3.schema.json +147 -0
  135. package/harness/bundles/java/codebuddy/contracts/doctor-capability.schema.json +43 -0
  136. package/harness/bundles/java/codebuddy/contracts/external-platform-observation.schema.json +62 -0
  137. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  138. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  139. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  140. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  141. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  142. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  143. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  144. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  145. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  146. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  147. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  148. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  149. package/harness/bundles/java/codebuddy/harness-sync/reference.md +2 -2
  150. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  151. package/harness/bundles/java/codebuddy/harness-test/pitfalls.md +1 -1
  152. package/harness/bundles/java/codebuddy/protocols/ci-layering-protocol.md +22 -0
  153. package/harness/bundles/java/codebuddy/protocols/external-platform-protocol.md +27 -0
  154. package/harness/bundles/java/codebuddy/protocols/memory-closeout-protocol.md +21 -0
  155. package/harness/bundles/java/codebuddy/protocols/registry-governance-protocol.md +31 -0
  156. package/harness/bundles/java/codebuddy/protocols/verification-graph-protocol.md +26 -0
  157. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +13 -9
  158. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +121 -12
  159. package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +2 -2
  160. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +308 -23
  161. package/harness/bundles/java/codex/.harness-build.json +1 -1
  162. package/harness/bundles/java/codex/contracts/build-profile-v3.schema.json +147 -0
  163. package/harness/bundles/java/codex/contracts/doctor-capability.schema.json +43 -0
  164. package/harness/bundles/java/codex/contracts/external-platform-observation.schema.json +62 -0
  165. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  166. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  167. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  168. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  169. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  170. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  171. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  172. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  173. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  174. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  175. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  176. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  177. package/harness/bundles/java/codex/harness-sync/reference.md +2 -2
  178. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  179. package/harness/bundles/java/codex/harness-test/pitfalls.md +1 -1
  180. package/harness/bundles/java/codex/protocols/ci-layering-protocol.md +22 -0
  181. package/harness/bundles/java/codex/protocols/external-platform-protocol.md +27 -0
  182. package/harness/bundles/java/codex/protocols/memory-closeout-protocol.md +21 -0
  183. package/harness/bundles/java/codex/protocols/registry-governance-protocol.md +31 -0
  184. package/harness/bundles/java/codex/protocols/verification-graph-protocol.md +26 -0
  185. package/harness/bundles/java/codex/scripts/harness_archive.py +13 -9
  186. package/harness/bundles/java/codex/scripts/harness_ledger.py +121 -12
  187. package/harness/bundles/java/codex/scripts/harness_preflight.py +2 -2
  188. package/harness/bundles/java/codex/scripts/harness_profile.py +308 -23
  189. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  190. package/harness/bundles/java/cursor/contracts/build-profile-v3.schema.json +147 -0
  191. package/harness/bundles/java/cursor/contracts/doctor-capability.schema.json +43 -0
  192. package/harness/bundles/java/cursor/contracts/external-platform-observation.schema.json +62 -0
  193. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  194. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  195. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  196. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  197. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  198. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  199. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  200. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  201. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  202. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  203. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  204. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  205. package/harness/bundles/java/cursor/harness-sync/reference.md +2 -2
  206. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  207. package/harness/bundles/java/cursor/harness-test/pitfalls.md +1 -1
  208. package/harness/bundles/java/cursor/protocols/ci-layering-protocol.md +22 -0
  209. package/harness/bundles/java/cursor/protocols/external-platform-protocol.md +27 -0
  210. package/harness/bundles/java/cursor/protocols/memory-closeout-protocol.md +21 -0
  211. package/harness/bundles/java/cursor/protocols/registry-governance-protocol.md +31 -0
  212. package/harness/bundles/java/cursor/protocols/verification-graph-protocol.md +26 -0
  213. package/harness/bundles/java/cursor/scripts/harness_archive.py +13 -9
  214. package/harness/bundles/java/cursor/scripts/harness_ledger.py +121 -12
  215. package/harness/bundles/java/cursor/scripts/harness_preflight.py +2 -2
  216. package/harness/bundles/java/cursor/scripts/harness_profile.py +308 -23
  217. package/harness/manifests/general/claude-code.json +59 -21
  218. package/harness/manifests/general/codebuddy.json +59 -21
  219. package/harness/manifests/general/codex.json +59 -21
  220. package/harness/manifests/general/cursor.json +59 -21
  221. package/harness/manifests/java/claude-code.json +61 -23
  222. package/harness/manifests/java/codebuddy.json +61 -23
  223. package/harness/manifests/java/codex.json +61 -23
  224. package/harness/manifests/java/cursor.json +61 -23
  225. package/hunter-workflow-family.json +18 -6
  226. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Harness profile v3 — module/command graph resolver, recommender and migrator.
2
+ """Harness profile v3 — module/command/verification graph resolver, recommender and migrator.
3
3
 
4
4
  Change cluster 1 of harness-deterministic-performance.
5
5
 
@@ -7,8 +7,8 @@ Spec §3.1:每个 verification 声明 command/argvTemplate/scope/inputs/covera
7
7
  source/basis;持久 profile 只保存模板,不含具体 change-name/worktree 路径或已解析
8
8
  overlay;运行期 resolve 结果写入 change runtime/session。
9
9
 
10
- 兼容性:保留顶层 `verificationInputs`(派生自 commands.<key>.inputs)以兼容
11
- harness_ledger.py v1 的 expand_profile_input_files,直到 cluster 2 升级 ledger。
10
+ 兼容性:保留顶层 `verificationInputs`(派生自 commands.<key>.inputs)供旧调用方
11
+ 展开输入闭包;`verificationGraph.targets` 是动态验证目标的权威声明。
12
12
 
13
13
  Python 3.10+ stdlib only. UTF-8 without BOM. Windows path friendly.
14
14
  """
@@ -157,6 +157,15 @@ def project_profile_v3(profile: dict[str, Any]) -> dict[str, Any]:
157
157
  return projected
158
158
  projected["schemaVersion"] = SCHEMA_VERSION
159
159
  commands = projected.get("commands") if isinstance(projected.get("commands"), dict) else {}
160
+ legacy_verification_inputs = {
161
+ key: list(patterns)
162
+ for key, patterns in (
163
+ projected.get("verificationInputs")
164
+ if isinstance(projected.get("verificationInputs"), dict)
165
+ else {}
166
+ ).items()
167
+ if isinstance(key, str) and isinstance(patterns, list)
168
+ }
160
169
  projected.setdefault(
161
170
  "moduleGraph",
162
171
  {"modules": [], "boundariesProven": False, "needsReview": True},
@@ -186,6 +195,13 @@ def project_profile_v3(profile: dict[str, Any]) -> dict[str, Any]:
186
195
  if isinstance(command, dict) and command.get("source") == "user"
187
196
  ),
188
197
  }
198
+ projected["defaultsFingerprint"] = profile_defaults_fingerprint()
199
+ _derive_verification_inputs(projected)
200
+ projected["verificationInputs"] = {
201
+ **legacy_verification_inputs,
202
+ **projected["verificationInputs"],
203
+ }
204
+ _derive_verification_graph(projected)
189
205
  return projected
190
206
 
191
207
 
@@ -266,6 +282,7 @@ VERIFICATION_KEYS: tuple[str, ...] = (
266
282
  )
267
283
 
268
284
  VALID_SOURCES = ("detected", "user")
285
+ TARGET_NAME = re.compile(r"^[A-Za-z][A-Za-z0-9_-]{0,63}$")
269
286
 
270
287
 
271
288
  def now_iso() -> str:
@@ -328,6 +345,16 @@ def is_path_excluded(rel_path: str, excluded: tuple[str, ...] | list[str]) -> bo
328
345
  def detect_project_type(project: Path) -> str:
329
346
  if (project / "pom.xml").is_file():
330
347
  return "java-maven"
348
+ if any(
349
+ (project / marker).is_file()
350
+ for marker in (
351
+ "build.gradle",
352
+ "build.gradle.kts",
353
+ "settings.gradle",
354
+ "settings.gradle.kts",
355
+ )
356
+ ):
357
+ return "java-gradle"
331
358
  if (project / "package.json").is_file():
332
359
  return "node"
333
360
  return "unknown"
@@ -341,6 +368,8 @@ def discover_nested_components(
341
368
  "pyproject.toml": "python",
342
369
  "package.json": "node",
343
370
  "pom.xml": "java-maven",
371
+ "build.gradle": "java-gradle",
372
+ "build.gradle.kts": "java-gradle",
344
373
  }
345
374
  found: list[tuple[str, str]] = []
346
375
  for marker, kind in markers.items():
@@ -412,6 +441,8 @@ def _java_commands(reactor_modules: list[str], pom_hash: str) -> dict[str, Any]:
412
441
  "scope": "module",
413
442
  "inputs": _cmd_inputs(reactor_modules, pom=True, main=True, test=False),
414
443
  "coverage": "compile",
444
+ "dependsOn": [],
445
+ "requiredCapabilities": ["java", "maven"],
415
446
  "source": "detected",
416
447
  "basis": dict(basis),
417
448
  },
@@ -421,6 +452,8 @@ def _java_commands(reactor_modules: list[str], pom_hash: str) -> dict[str, Any]:
421
452
  "scope": "incremental",
422
453
  "inputs": _cmd_inputs(reactor_modules, pom=True, main=False, test=True),
423
454
  "coverage": "unitTest",
455
+ "dependsOn": ["compile"],
456
+ "requiredCapabilities": ["java", "maven"],
424
457
  "source": "detected",
425
458
  "basis": dict(basis),
426
459
  },
@@ -430,6 +463,8 @@ def _java_commands(reactor_modules: list[str], pom_hash: str) -> dict[str, Any]:
430
463
  "scope": "full",
431
464
  "inputs": full_inputs,
432
465
  "coverage": "unitTestFull",
466
+ "dependsOn": ["compile"],
467
+ "requiredCapabilities": ["java", "maven"],
433
468
  "source": "detected",
434
469
  "basis": dict(basis),
435
470
  },
@@ -439,6 +474,8 @@ def _java_commands(reactor_modules: list[str], pom_hash: str) -> dict[str, Any]:
439
474
  "scope": "module-am",
440
475
  "inputs": _cmd_inputs(reactor_modules, pom=True, main=False, test=False),
441
476
  "coverage": "install",
477
+ "dependsOn": ["compile"],
478
+ "requiredCapabilities": ["java", "maven"],
442
479
  "source": "detected",
443
480
  "basis": dict(basis),
444
481
  },
@@ -448,12 +485,96 @@ def _java_commands(reactor_modules: list[str], pom_hash: str) -> dict[str, Any]:
448
485
  "scope": "module",
449
486
  "inputs": _cmd_inputs(reactor_modules, pom=True, main=True, test=True),
450
487
  "coverage": "package",
488
+ "dependsOn": ["compile"],
489
+ "requiredCapabilities": ["java", "maven"],
451
490
  "source": "detected",
452
491
  "basis": dict(basis),
453
492
  },
454
493
  }
455
494
 
456
495
 
496
+ def _gradle_commands(project: Path) -> dict[str, Any]:
497
+ if (project / "gradlew").is_file():
498
+ executable = "./gradlew"
499
+ required_capabilities = ["java"]
500
+ elif (project / "gradlew.bat").is_file():
501
+ executable = "gradlew.bat"
502
+ required_capabilities = ["java"]
503
+ else:
504
+ executable = "gradle"
505
+ required_capabilities = ["java", "gradle"]
506
+ build_inputs = [
507
+ "build.gradle",
508
+ "build.gradle.kts",
509
+ "settings.gradle",
510
+ "settings.gradle.kts",
511
+ "gradle.properties",
512
+ "gradle/wrapper/**",
513
+ "gradlew",
514
+ "gradlew.bat",
515
+ ]
516
+ full_inputs = build_inputs + ["src/main/**", "src/test/**"]
517
+ basis = {
518
+ "buildSystem": "gradle",
519
+ "wrapper": executable != "gradle",
520
+ }
521
+
522
+ def command(
523
+ task: str,
524
+ *,
525
+ scope: str,
526
+ inputs: list[str],
527
+ coverage: str,
528
+ depends_on: list[str],
529
+ extra: list[str] | None = None,
530
+ ) -> dict[str, Any]:
531
+ argv = [executable, task, *(extra or []), "--offline"]
532
+ return {
533
+ "command": " ".join(argv),
534
+ "argvTemplate": argv,
535
+ "scope": scope,
536
+ "inputs": inputs,
537
+ "coverage": coverage,
538
+ "dependsOn": depends_on,
539
+ "requiredCapabilities": list(required_capabilities),
540
+ "source": "detected",
541
+ "basis": dict(basis),
542
+ }
543
+
544
+ return {
545
+ "compile": command(
546
+ "classes",
547
+ scope="module",
548
+ inputs=build_inputs + ["src/main/**"],
549
+ coverage="compile",
550
+ depends_on=[],
551
+ ),
552
+ "unitTest": command(
553
+ "test",
554
+ scope="incremental",
555
+ inputs=build_inputs + ["src/main/**", "src/test/**"],
556
+ coverage="unitTest",
557
+ depends_on=["compile"],
558
+ extra=["--tests", "{testClasses}"],
559
+ ),
560
+ "unitTestFull": command(
561
+ "test",
562
+ scope="full",
563
+ inputs=full_inputs,
564
+ coverage="unitTestFull",
565
+ depends_on=["compile"],
566
+ ),
567
+ "package": command(
568
+ "build",
569
+ scope="module",
570
+ inputs=full_inputs,
571
+ coverage="package",
572
+ depends_on=["compile"],
573
+ extra=["-x", "test"],
574
+ ),
575
+ }
576
+
577
+
457
578
  def _node_commands(project: Path) -> dict[str, Any]:
458
579
  """commands for a node project: unitTestFull = `npm run check` (or `npm test`)
459
580
  with an input closure covering TS sources/tests + config (+ harness Python/.mjs
@@ -473,9 +594,21 @@ def _node_commands(project: Path) -> dict[str, Any]:
473
594
  if not script_key:
474
595
  return {}
475
596
  full_cmd = scripts[script_key]
597
+ declared_package_manager = data.get("packageManager")
598
+ package_manager = (
599
+ "pnpm"
600
+ if (
601
+ isinstance(declared_package_manager, str)
602
+ and declared_package_manager.split("@", 1)[0] == "pnpm"
603
+ )
604
+ or (project / "pnpm-lock.yaml").is_file()
605
+ else "npm"
606
+ )
476
607
  # Precise globs (avoid node_modules/** which would make the inputs hash unstable).
477
608
  inputs = [
478
609
  "package.json",
610
+ "package-lock.json",
611
+ "pnpm-lock.yaml",
479
612
  "tsconfig.json",
480
613
  "tsconfig.*.json",
481
614
  "vitest.config.*",
@@ -511,8 +644,13 @@ def _node_commands(project: Path) -> dict[str, Any]:
511
644
  "scope": "full",
512
645
  "inputs": inputs,
513
646
  "coverage": "unitTestFull",
647
+ "dependsOn": [],
648
+ "requiredCapabilities": ["node", package_manager],
514
649
  "source": "detected",
515
- "basis": {"packageScript": script_key},
650
+ "basis": {
651
+ "packageScript": script_key,
652
+ "packageManager": package_manager,
653
+ },
516
654
  }
517
655
  }
518
656
 
@@ -523,11 +661,17 @@ def empty_profile_skeleton(excluded: tuple[str, ...] | list[str]) -> dict[str, A
523
661
  "defaultsFingerprint": profile_defaults_fingerprint(),
524
662
  "detectedAt": "",
525
663
  "projectType": "unknown",
526
- "toolPaths": {"node": "", "mvn": ""},
664
+ "toolPaths": {"node": "", "mvn": "", "gradle": "", "npm": "", "pnpm": ""},
527
665
  "excludedRoots": list(excluded),
528
666
  "commands": {},
529
- # 兼容字段:cluster 2 升级 ledger 后由 commands 派生;期间保持 v1 可消费。
667
+ # 兼容字段:由 commands 派生,供旧调用方展开输入闭包。
530
668
  "verificationInputs": {},
669
+ "verificationGraph": {
670
+ "schemaVersion": 1,
671
+ "source": "detected",
672
+ "candidateTarget": None,
673
+ "targets": {},
674
+ },
531
675
  "serviceStart": {
532
676
  "command": "",
533
677
  "healthUrl": "",
@@ -597,8 +741,18 @@ def load_profile(project: Path) -> dict[str, Any] | None:
597
741
  if exec_data is None:
598
742
  return common_data
599
743
 
600
- # Merge: start from common, overlay execution's buildCommands.
744
+ # Merge: start from common, overlay execution's current commands.
601
745
  merged = dict(common_data)
746
+ common_current = common_data.get("commands") or {}
747
+ exec_current = exec_data.get("commands") or {}
748
+ if common_current or exec_current:
749
+ merged_current = (
750
+ dict(common_current) if isinstance(common_current, dict) else {}
751
+ )
752
+ if isinstance(exec_current, dict):
753
+ merged_current.update(exec_current)
754
+ merged["commands"] = merged_current
755
+ # Legacy v1 aliases remain readable during migration.
602
756
  common_cmds = common_data.get("buildCommands") or {}
603
757
  exec_cmds = exec_data.get("buildCommands") or {}
604
758
  if common_cmds or exec_cmds:
@@ -606,19 +760,14 @@ def load_profile(project: Path) -> dict[str, Any] | None:
606
760
  if isinstance(exec_cmds, dict):
607
761
  merged_cmds.update(exec_cmds)
608
762
  merged["buildCommands"] = merged_cmds
609
- common_commands = common_data.get("commands") or {}
610
- exec_commands = exec_data.get("commands") or {}
611
- if common_commands or exec_commands:
612
- merged_commands = (
613
- dict(common_commands) if isinstance(common_commands, dict) else {}
614
- )
615
- if isinstance(exec_commands, dict):
616
- merged_commands.update(exec_commands)
617
- merged["commands"] = merged_commands
763
+ if isinstance(merged.get("commands"), dict):
618
764
  _derive_verification_inputs(merged)
619
765
  for field in ("moduleGraph", "commandGraph", "migration"):
620
766
  if field in exec_data:
621
767
  merged[field] = exec_data[field]
768
+ exec_graph = exec_data.get("verificationGraph")
769
+ if isinstance(exec_graph, dict):
770
+ merged["verificationGraph"] = dict(exec_graph)
622
771
  return merged
623
772
 
624
773
 
@@ -644,8 +793,11 @@ def _merge_user_overrides(
644
793
  test_tracking = existing.get("testTracking")
645
794
  if isinstance(test_tracking, dict) and test_tracking.get("source") == "user":
646
795
  profile["testTracking"] = dict(test_tracking)
647
- # 兼容字段:用户在 v1 配置的 verificationInputs 不保留(无 provenance),
648
- # commands 重新派生。knownPreexistingErrors/shellQuirks 是人工标注,保留。
796
+ graph = existing.get("verificationGraph")
797
+ if isinstance(graph, dict) and graph.get("source") == "user":
798
+ profile["verificationGraph"] = dict(graph)
799
+ # verificationInputs 由 commands 重新派生。knownPreexistingErrors /
800
+ # shellQuirks 是人工标注,保留。
649
801
 
650
802
 
651
803
  def _derive_verification_inputs(profile: dict[str, Any]) -> None:
@@ -658,6 +810,61 @@ def _derive_verification_inputs(profile: dict[str, Any]) -> None:
658
810
  profile["verificationInputs"] = vi
659
811
 
660
812
 
813
+ def _required_coverage(scope: Any) -> str:
814
+ value = str(scope or "").strip()
815
+ if value == "incremental":
816
+ return "incremental"
817
+ if value == "module-am":
818
+ return "module-am"
819
+ if value in {"module", "full"}:
820
+ # A full command may satisfy the module-or-broader candidate gate.
821
+ return "module"
822
+ return "module"
823
+
824
+
825
+ def _derive_verification_graph(profile: dict[str, Any]) -> None:
826
+ existing = profile.get("verificationGraph")
827
+ if isinstance(existing, dict) and existing.get("source") == "user":
828
+ return
829
+ commands = profile.get("commands")
830
+ targets: dict[str, dict[str, Any]] = {}
831
+ if isinstance(commands, dict):
832
+ for key, command in sorted(commands.items()):
833
+ if not isinstance(key, str) or not TARGET_NAME.fullmatch(key):
834
+ continue
835
+ if not isinstance(command, dict):
836
+ continue
837
+ depends_on = command.get("dependsOn")
838
+ if not isinstance(depends_on, list):
839
+ depends_on = []
840
+ required_capabilities = command.get("requiredCapabilities")
841
+ if not isinstance(required_capabilities, list):
842
+ required_capabilities = []
843
+ targets[key] = {
844
+ "commandKey": key,
845
+ "dependsOn": [
846
+ item
847
+ for item in depends_on
848
+ if isinstance(item, str) and TARGET_NAME.fullmatch(item)
849
+ ],
850
+ "requiredCoverage": _required_coverage(command.get("scope")),
851
+ "candidate": key == "unitTestFull",
852
+ "requiredCapabilities": [
853
+ item
854
+ for item in required_capabilities
855
+ if isinstance(item, str) and item.strip()
856
+ ],
857
+ }
858
+ profile["verificationGraph"] = {
859
+ "schemaVersion": 1,
860
+ "source": "detected",
861
+ "candidateTarget": (
862
+ "unitTestFull" if "unitTestFull" in targets else None
863
+ ),
864
+ "targets": targets,
865
+ }
866
+
867
+
661
868
  def detect(project: Path) -> dict[str, Any]:
662
869
  project = project.resolve()
663
870
  existing = load_profile(project)
@@ -696,7 +903,13 @@ def detect(project: Path) -> dict[str, Any]:
696
903
 
697
904
  node_path = which_tool("node")
698
905
  mvn_path = which_tool("mvn")
699
- profile["toolPaths"] = {"node": node_path, "mvn": mvn_path}
906
+ profile["toolPaths"] = {
907
+ "node": node_path,
908
+ "mvn": mvn_path,
909
+ "gradle": which_tool("gradle"),
910
+ "npm": which_tool("npm"),
911
+ "pnpm": which_tool("pnpm"),
912
+ }
700
913
 
701
914
  pom_hash = sha256_file(project / "pom.xml") if (project / "pom.xml").is_file() else ""
702
915
  profile["fingerprint"] = {"mvnVersion": "", "nodeVersion": "", "pomHash": pom_hash}
@@ -709,6 +922,9 @@ def detect(project: Path) -> dict[str, Any]:
709
922
  "src/test/**" if module == "." else f"{module}/src/test/**"
710
923
  for module in reactor_modules
711
924
  )
925
+ elif project_type == "java-gradle":
926
+ profile["commands"] = _gradle_commands(project)
927
+ profile["testTracking"]["paths"] = ["src/test/**"]
712
928
  elif project_type == "node":
713
929
  node_cmds = _node_commands(project)
714
930
  if node_cmds:
@@ -748,6 +964,7 @@ def detect(project: Path) -> dict[str, Any]:
748
964
  ]
749
965
  + list(recommendation["targets"])
750
966
  }
967
+ _derive_verification_graph(profile)
751
968
 
752
969
  out_path = project / PROFILE_REL
753
970
  write_json(out_path, profile)
@@ -863,6 +1080,11 @@ def check(project: Path) -> dict[str, Any]:
863
1080
  stale = True
864
1081
  issues.append(f"toolPaths.{tool_name} missing: {p}")
865
1082
 
1083
+ structural_issues = validate_profile(profile, project)
1084
+ if structural_issues:
1085
+ stale = True
1086
+ issues.extend(structural_issues)
1087
+
866
1088
  status = "stale" if stale else "ready"
867
1089
  return {
868
1090
  "ok": not stale,
@@ -897,6 +1119,66 @@ def validate_profile(profile: dict[str, Any], project: Path) -> list[str]:
897
1119
  issues.append(
898
1120
  f"command {key} input in excluded root: {inp}"
899
1121
  )
1122
+ graph = profile.get("verificationGraph")
1123
+ targets = graph.get("targets") if isinstance(graph, dict) else None
1124
+ if (
1125
+ not isinstance(graph, dict)
1126
+ or graph.get("schemaVersion") != 1
1127
+ or not isinstance(targets, dict)
1128
+ ):
1129
+ issues.append("verificationGraph schemaVersion=1 and targets object required")
1130
+ return issues
1131
+ dependencies: dict[str, list[str]] = {}
1132
+ for name, target in targets.items():
1133
+ if not isinstance(name, str) or not TARGET_NAME.fullmatch(name):
1134
+ issues.append(f"verification target name invalid: {name}")
1135
+ continue
1136
+ if not isinstance(target, dict):
1137
+ issues.append(f"verification target {name} must be an object")
1138
+ continue
1139
+ command_key = target.get("commandKey")
1140
+ if command_key not in commands:
1141
+ issues.append(
1142
+ f"verification target {name} references missing command {command_key}"
1143
+ )
1144
+ required = target.get("requiredCoverage")
1145
+ if required not in {"incremental", "module", "module-am", "full"}:
1146
+ issues.append(
1147
+ f"verification target {name} requiredCoverage invalid: {required}"
1148
+ )
1149
+ raw_dependencies = target.get("dependsOn")
1150
+ if not isinstance(raw_dependencies, list):
1151
+ issues.append(f"verification target {name} dependsOn must be a list")
1152
+ continue
1153
+ dependencies[name] = []
1154
+ for dependency in raw_dependencies:
1155
+ if dependency not in targets:
1156
+ issues.append(
1157
+ f"verification target {name} depends on unknown target {dependency}"
1158
+ )
1159
+ elif isinstance(dependency, str):
1160
+ dependencies[name].append(dependency)
1161
+ candidate = graph.get("candidateTarget")
1162
+ if candidate is not None and candidate not in targets:
1163
+ issues.append(f"verificationGraph candidateTarget missing: {candidate}")
1164
+
1165
+ visiting: set[str] = set()
1166
+ visited: set[str] = set()
1167
+
1168
+ def visit(name: str) -> None:
1169
+ if name in visiting:
1170
+ issues.append(f"verification target dependency cycle at {name}")
1171
+ return
1172
+ if name in visited:
1173
+ return
1174
+ visiting.add(name)
1175
+ for dependency in dependencies.get(name, []):
1176
+ visit(dependency)
1177
+ visiting.remove(name)
1178
+ visited.add(name)
1179
+
1180
+ for target_name in dependencies:
1181
+ visit(target_name)
900
1182
  return issues
901
1183
 
902
1184
 
@@ -947,11 +1229,13 @@ def resolve_command(
947
1229
 
948
1230
 
949
1231
  # ---------------------------------------------------------------------------
950
- # migrate(v1 → v2,dry-run/apply/备份)
1232
+ # migrate(v1/v2v3,dry-run/apply/备份)
951
1233
  # ---------------------------------------------------------------------------
952
1234
 
953
- def _backup_path(profile_path: Path, version: int = 1) -> Path:
954
- return profile_path.with_suffix(profile_path.suffix + f".v{version}.bak")
1235
+ def _backup_path(profile_path: Path, schema_version: int) -> Path:
1236
+ return profile_path.with_suffix(
1237
+ profile_path.suffix + f".v{schema_version}.bak"
1238
+ )
955
1239
 
956
1240
 
957
1241
  def migrate(project: Path, *, dry_run: bool = True) -> dict[str, Any]:
@@ -1019,6 +1303,7 @@ def migrate(project: Path, *, dry_run: bool = True) -> dict[str, Any]:
1019
1303
  "identifier 约束(pattern/maxLength/prefix)",
1020
1304
  ]
1021
1305
  )
1306
+ changes.append("commands → verificationGraph.targets 动态验证图")
1022
1307
  dropped: list[str] = []
1023
1308
  v1_svc = existing.get("serviceStart") or {}
1024
1309
  if isinstance(v1_svc, dict):
@@ -1082,7 +1367,7 @@ def migrate(project: Path, *, dry_run: bool = True) -> dict[str, Any]:
1082
1367
  def build_parser() -> argparse.ArgumentParser:
1083
1368
  parser = argparse.ArgumentParser(
1084
1369
  prog="harness_profile.py",
1085
- description="Harness profile v3: recommend/detect/audit/validate/resolve/migrate",
1370
+ description="Harness Build Profile v3: recommend/detect/audit/validate/resolve/migrate",
1086
1371
  )
1087
1372
  parser.add_argument("--json", action="store_true", help="emit JSON")
1088
1373
  sub = parser.add_subparsers(dest="command", required=True)
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "java",
4
4
  "agent": "claude-code",
5
- "coreHash": "3e44d00025d595e6"
5
+ "coreHash": "e47155d4f364fc9e"
6
6
  }
@@ -0,0 +1,147 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://hunter-harness.local/contracts/build-profile-v3.schema.json",
4
+ "title": "Hunter Harness Build Profile v3",
5
+ "type": "object",
6
+ "required": [
7
+ "schemaVersion",
8
+ "commands",
9
+ "verificationInputs",
10
+ "verificationGraph"
11
+ ],
12
+ "properties": {
13
+ "schemaVersion": {
14
+ "const": 3
15
+ },
16
+ "commands": {
17
+ "type": "object",
18
+ "additionalProperties": {
19
+ "type": "object",
20
+ "required": [
21
+ "command",
22
+ "argvTemplate",
23
+ "scope",
24
+ "inputs",
25
+ "coverage",
26
+ "source"
27
+ ],
28
+ "properties": {
29
+ "command": {
30
+ "type": "string",
31
+ "minLength": 1
32
+ },
33
+ "argvTemplate": {
34
+ "type": "array",
35
+ "items": {
36
+ "type": "string"
37
+ },
38
+ "minItems": 1
39
+ },
40
+ "scope": {
41
+ "type": "string",
42
+ "minLength": 1
43
+ },
44
+ "inputs": {
45
+ "type": "array",
46
+ "items": {
47
+ "type": "string",
48
+ "minLength": 1
49
+ }
50
+ },
51
+ "coverage": {
52
+ "type": "string",
53
+ "minLength": 1
54
+ },
55
+ "source": {
56
+ "enum": [
57
+ "detected",
58
+ "user"
59
+ ]
60
+ }
61
+ }
62
+ }
63
+ },
64
+ "verificationInputs": {
65
+ "type": "object",
66
+ "additionalProperties": {
67
+ "type": "array",
68
+ "items": {
69
+ "type": "string",
70
+ "minLength": 1
71
+ }
72
+ }
73
+ },
74
+ "verificationGraph": {
75
+ "type": "object",
76
+ "required": [
77
+ "schemaVersion",
78
+ "source",
79
+ "candidateTarget",
80
+ "targets"
81
+ ],
82
+ "properties": {
83
+ "schemaVersion": {
84
+ "const": 1
85
+ },
86
+ "source": {
87
+ "enum": [
88
+ "detected",
89
+ "user"
90
+ ]
91
+ },
92
+ "candidateTarget": {
93
+ "type": [
94
+ "string",
95
+ "null"
96
+ ]
97
+ },
98
+ "targets": {
99
+ "type": "object",
100
+ "additionalProperties": {
101
+ "type": "object",
102
+ "additionalProperties": false,
103
+ "required": [
104
+ "commandKey",
105
+ "dependsOn",
106
+ "requiredCoverage",
107
+ "candidate",
108
+ "requiredCapabilities"
109
+ ],
110
+ "properties": {
111
+ "commandKey": {
112
+ "type": "string",
113
+ "pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
114
+ },
115
+ "dependsOn": {
116
+ "type": "array",
117
+ "items": {
118
+ "type": "string",
119
+ "pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
120
+ },
121
+ "uniqueItems": true
122
+ },
123
+ "requiredCoverage": {
124
+ "enum": [
125
+ "incremental",
126
+ "module-am",
127
+ "module"
128
+ ]
129
+ },
130
+ "candidate": {
131
+ "type": "boolean"
132
+ },
133
+ "requiredCapabilities": {
134
+ "type": "array",
135
+ "items": {
136
+ "type": "string",
137
+ "minLength": 1
138
+ },
139
+ "uniqueItems": true
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }