@hunter-harness/workflow-harness 0.2.32 → 0.2.34

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 (267) hide show
  1. package/README.md +25 -25
  2. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  3. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -0
  4. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  6. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  7. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  10. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +4 -4
  12. package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -0
  13. package/harness/bundles/general/claude-code/harness-plan/reference.md +6 -0
  14. package/harness/bundles/general/claude-code/harness-review/SKILL.md +2 -2
  15. package/harness/bundles/general/claude-code/harness-run/SKILL.md +4 -4
  16. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  17. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  18. package/harness/bundles/general/claude-code/harness-test/SKILL.md +4 -4
  19. package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -0
  20. package/harness/bundles/general/claude-code/harness-test/reference.md +2 -1
  21. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +2 -2
  22. package/harness/bundles/general/claude-code/scripts/harness_archive.py +404 -12
  23. package/harness/bundles/general/claude-code/scripts/harness_context.py +717 -0
  24. package/harness/bundles/general/claude-code/scripts/harness_gate.py +1 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +77 -9
  26. package/harness/bundles/general/claude-code/scripts/harness_phase.py +1 -0
  27. package/harness/bundles/general/claude-code/scripts/harness_plan_aggregate.py +249 -0
  28. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +43 -11
  29. package/harness/bundles/general/claude-code/scripts/harness_profile.py +272 -15
  30. package/harness/bundles/general/claude-code/scripts/harness_report_model.py +420 -0
  31. package/harness/bundles/general/claude-code/scripts/harness_review.py +16 -1
  32. package/harness/bundles/general/claude-code/scripts/harness_verification.py +114 -0
  33. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  34. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -0
  35. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  37. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  38. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  41. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  42. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +4 -4
  43. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -0
  44. package/harness/bundles/general/codebuddy/harness-plan/reference.md +6 -0
  45. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +2 -2
  46. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +4 -4
  47. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  48. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  49. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +4 -4
  50. package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -0
  51. package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -1
  52. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +2 -2
  53. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +404 -12
  54. package/harness/bundles/general/codebuddy/scripts/harness_context.py +717 -0
  55. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +1 -0
  56. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +77 -9
  57. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +1 -0
  58. package/harness/bundles/general/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  59. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +43 -11
  60. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +272 -15
  61. package/harness/bundles/general/codebuddy/scripts/harness_report_model.py +420 -0
  62. package/harness/bundles/general/codebuddy/scripts/harness_review.py +16 -1
  63. package/harness/bundles/general/codebuddy/scripts/harness_verification.py +114 -0
  64. package/harness/bundles/general/codex/.harness-build.json +1 -1
  65. package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -0
  66. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +215 -106
  68. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +8 -0
  69. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  70. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  71. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  72. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  73. package/harness/bundles/general/codex/harness-plan/SKILL.md +4 -4
  74. package/harness/bundles/general/codex/harness-plan/checklist.md +2 -0
  75. package/harness/bundles/general/codex/harness-plan/reference.md +6 -0
  76. package/harness/bundles/general/codex/harness-review/SKILL.md +2 -2
  77. package/harness/bundles/general/codex/harness-run/SKILL.md +4 -4
  78. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  79. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  80. package/harness/bundles/general/codex/harness-test/SKILL.md +4 -4
  81. package/harness/bundles/general/codex/harness-test/checklist.md +1 -0
  82. package/harness/bundles/general/codex/harness-test/reference.md +2 -1
  83. package/harness/bundles/general/codex/protocols/ledger-protocol.md +2 -2
  84. package/harness/bundles/general/codex/scripts/harness_archive.py +404 -12
  85. package/harness/bundles/general/codex/scripts/harness_context.py +717 -0
  86. package/harness/bundles/general/codex/scripts/harness_gate.py +1 -0
  87. package/harness/bundles/general/codex/scripts/harness_ledger.py +77 -9
  88. package/harness/bundles/general/codex/scripts/harness_phase.py +1 -0
  89. package/harness/bundles/general/codex/scripts/harness_plan_aggregate.py +249 -0
  90. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +43 -11
  91. package/harness/bundles/general/codex/scripts/harness_profile.py +272 -15
  92. package/harness/bundles/general/codex/scripts/harness_report_model.py +420 -0
  93. package/harness/bundles/general/codex/scripts/harness_review.py +16 -1
  94. package/harness/bundles/general/codex/scripts/harness_verification.py +114 -0
  95. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  96. package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -0
  97. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  98. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +215 -106
  99. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +8 -0
  100. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  101. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  102. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  103. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  104. package/harness/bundles/general/cursor/harness-plan/SKILL.md +4 -4
  105. package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -0
  106. package/harness/bundles/general/cursor/harness-plan/reference.md +6 -0
  107. package/harness/bundles/general/cursor/harness-review/SKILL.md +2 -2
  108. package/harness/bundles/general/cursor/harness-run/SKILL.md +4 -4
  109. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  110. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  111. package/harness/bundles/general/cursor/harness-test/SKILL.md +4 -4
  112. package/harness/bundles/general/cursor/harness-test/checklist.md +1 -0
  113. package/harness/bundles/general/cursor/harness-test/reference.md +2 -1
  114. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +2 -2
  115. package/harness/bundles/general/cursor/scripts/harness_archive.py +404 -12
  116. package/harness/bundles/general/cursor/scripts/harness_context.py +717 -0
  117. package/harness/bundles/general/cursor/scripts/harness_gate.py +1 -0
  118. package/harness/bundles/general/cursor/scripts/harness_ledger.py +77 -9
  119. package/harness/bundles/general/cursor/scripts/harness_phase.py +1 -0
  120. package/harness/bundles/general/cursor/scripts/harness_plan_aggregate.py +249 -0
  121. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +43 -11
  122. package/harness/bundles/general/cursor/scripts/harness_profile.py +272 -15
  123. package/harness/bundles/general/cursor/scripts/harness_report_model.py +420 -0
  124. package/harness/bundles/general/cursor/scripts/harness_review.py +16 -1
  125. package/harness/bundles/general/cursor/scripts/harness_verification.py +114 -0
  126. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  127. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +1 -0
  128. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  129. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  130. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  131. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  132. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  133. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  134. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  135. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  136. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  137. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +4 -4
  138. package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -0
  139. package/harness/bundles/java/claude-code/harness-plan/reference.md +6 -0
  140. package/harness/bundles/java/claude-code/harness-review/SKILL.md +2 -2
  141. package/harness/bundles/java/claude-code/harness-run/SKILL.md +4 -4
  142. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  143. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  144. package/harness/bundles/java/claude-code/harness-test/SKILL.md +4 -4
  145. package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -0
  146. package/harness/bundles/java/claude-code/harness-test/reference.md +2 -0
  147. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +2 -2
  148. package/harness/bundles/java/claude-code/scripts/harness_archive.py +404 -12
  149. package/harness/bundles/java/claude-code/scripts/harness_context.py +717 -0
  150. package/harness/bundles/java/claude-code/scripts/harness_gate.py +1 -0
  151. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +77 -9
  152. package/harness/bundles/java/claude-code/scripts/harness_phase.py +1 -0
  153. package/harness/bundles/java/claude-code/scripts/harness_plan_aggregate.py +249 -0
  154. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +43 -11
  155. package/harness/bundles/java/claude-code/scripts/harness_profile.py +272 -15
  156. package/harness/bundles/java/claude-code/scripts/harness_report_model.py +420 -0
  157. package/harness/bundles/java/claude-code/scripts/harness_review.py +16 -1
  158. package/harness/bundles/java/claude-code/scripts/harness_verification.py +114 -0
  159. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  160. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -0
  161. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  162. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  163. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  164. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  165. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  166. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  167. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  168. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  169. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  170. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +4 -4
  171. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -0
  172. package/harness/bundles/java/codebuddy/harness-plan/reference.md +6 -0
  173. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +2 -2
  174. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +4 -4
  175. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  176. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  177. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +4 -4
  178. package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -0
  179. package/harness/bundles/java/codebuddy/harness-test/reference.md +2 -0
  180. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +2 -2
  181. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +404 -12
  182. package/harness/bundles/java/codebuddy/scripts/harness_context.py +717 -0
  183. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +1 -0
  184. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +77 -9
  185. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +1 -0
  186. package/harness/bundles/java/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  187. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +43 -11
  188. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +272 -15
  189. package/harness/bundles/java/codebuddy/scripts/harness_report_model.py +420 -0
  190. package/harness/bundles/java/codebuddy/scripts/harness_review.py +16 -1
  191. package/harness/bundles/java/codebuddy/scripts/harness_verification.py +114 -0
  192. package/harness/bundles/java/codex/.harness-build.json +1 -1
  193. package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -0
  194. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  195. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  196. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +215 -106
  197. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +8 -0
  198. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  199. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  200. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  201. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  202. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  203. package/harness/bundles/java/codex/harness-plan/SKILL.md +4 -4
  204. package/harness/bundles/java/codex/harness-plan/checklist.md +2 -0
  205. package/harness/bundles/java/codex/harness-plan/reference.md +6 -0
  206. package/harness/bundles/java/codex/harness-review/SKILL.md +2 -2
  207. package/harness/bundles/java/codex/harness-run/SKILL.md +4 -4
  208. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  209. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  210. package/harness/bundles/java/codex/harness-test/SKILL.md +4 -4
  211. package/harness/bundles/java/codex/harness-test/checklist.md +1 -0
  212. package/harness/bundles/java/codex/harness-test/reference.md +2 -0
  213. package/harness/bundles/java/codex/protocols/ledger-protocol.md +2 -2
  214. package/harness/bundles/java/codex/scripts/harness_archive.py +404 -12
  215. package/harness/bundles/java/codex/scripts/harness_context.py +717 -0
  216. package/harness/bundles/java/codex/scripts/harness_gate.py +1 -0
  217. package/harness/bundles/java/codex/scripts/harness_ledger.py +77 -9
  218. package/harness/bundles/java/codex/scripts/harness_phase.py +1 -0
  219. package/harness/bundles/java/codex/scripts/harness_plan_aggregate.py +249 -0
  220. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +43 -11
  221. package/harness/bundles/java/codex/scripts/harness_profile.py +272 -15
  222. package/harness/bundles/java/codex/scripts/harness_report_model.py +420 -0
  223. package/harness/bundles/java/codex/scripts/harness_review.py +16 -1
  224. package/harness/bundles/java/codex/scripts/harness_verification.py +114 -0
  225. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  226. package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -0
  227. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  228. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  229. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +215 -106
  230. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +8 -0
  231. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  232. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  233. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  234. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  235. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  236. package/harness/bundles/java/cursor/harness-plan/SKILL.md +4 -4
  237. package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -0
  238. package/harness/bundles/java/cursor/harness-plan/reference.md +6 -0
  239. package/harness/bundles/java/cursor/harness-review/SKILL.md +2 -2
  240. package/harness/bundles/java/cursor/harness-run/SKILL.md +4 -4
  241. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  242. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  243. package/harness/bundles/java/cursor/harness-test/SKILL.md +4 -4
  244. package/harness/bundles/java/cursor/harness-test/checklist.md +1 -0
  245. package/harness/bundles/java/cursor/harness-test/reference.md +2 -0
  246. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +2 -2
  247. package/harness/bundles/java/cursor/scripts/harness_archive.py +404 -12
  248. package/harness/bundles/java/cursor/scripts/harness_context.py +717 -0
  249. package/harness/bundles/java/cursor/scripts/harness_gate.py +1 -0
  250. package/harness/bundles/java/cursor/scripts/harness_ledger.py +77 -9
  251. package/harness/bundles/java/cursor/scripts/harness_phase.py +1 -0
  252. package/harness/bundles/java/cursor/scripts/harness_plan_aggregate.py +249 -0
  253. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +43 -11
  254. package/harness/bundles/java/cursor/scripts/harness_profile.py +272 -15
  255. package/harness/bundles/java/cursor/scripts/harness_report_model.py +420 -0
  256. package/harness/bundles/java/cursor/scripts/harness_review.py +16 -1
  257. package/harness/bundles/java/cursor/scripts/harness_verification.py +114 -0
  258. package/harness/manifests/general/claude-code.json +45 -29
  259. package/harness/manifests/general/codebuddy.json +45 -29
  260. package/harness/manifests/general/codex.json +45 -29
  261. package/harness/manifests/general/cursor.json +45 -29
  262. package/harness/manifests/java/claude-code.json +47 -31
  263. package/harness/manifests/java/codebuddy.json +47 -31
  264. package/harness/manifests/java/codex.json +47 -31
  265. package/harness/manifests/java/cursor.json +47 -31
  266. package/hunter-workflow-family.json +4 -4
  267. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Harness profile v2public deterministic profile resolver/validator/migrator.
2
+ """Harness profile v3module/command graph resolver, recommender and migrator.
3
3
 
4
4
  Change cluster 1 of harness-deterministic-performance.
5
5
 
@@ -40,9 +40,189 @@ if hasattr(sys.stderr, "reconfigure"):
40
40
  sys.stderr.reconfigure(encoding="utf-8")
41
41
 
42
42
 
43
- SCHEMA_VERSION = 2
43
+ SCHEMA_VERSION = 3
44
44
  PROFILE_REL = Path(".harness") / "config" / "build-profile.json"
45
45
 
46
+
47
+ def recommend(project: Path) -> dict[str, Any]:
48
+ """Return a reviewable profile recommendation without writing it."""
49
+ project = Path(project).resolve()
50
+ project_type = detect_project_type(project)
51
+ modules: list[dict[str, Any]] = []
52
+ targets: list[dict[str, Any]] = []
53
+ if project_type == "java-maven":
54
+ reactor = [item for item in find_reactor_modules(project, DEFAULT_EXCLUDED_ROOTS) if item != "."]
55
+ modules = [
56
+ {
57
+ "id": module,
58
+ "path": module,
59
+ "type": "java-maven",
60
+ "ownedPaths": [f"{module}/src/**", f"{module}/pom.xml"],
61
+ "dependsOn": [],
62
+ }
63
+ for module in sorted(reactor)
64
+ ]
65
+ if modules:
66
+ targets.append(
67
+ {
68
+ "id": "unitTest-module",
69
+ "verification": "unitTestFull",
70
+ "level": "module",
71
+ "command": "mvn test -pl {modules} -am -o",
72
+ "inputs": [f"{module['path']}/src/**" for module in modules],
73
+ "sharedConsumer": False,
74
+ }
75
+ )
76
+ targets.append(
77
+ {
78
+ "id": "unitTest-candidate",
79
+ "verification": "unitTestFull",
80
+ "level": "candidate",
81
+ "command": "mvn -f pom.xml test -o",
82
+ "inputs": ["pom.xml", "**/src/**"],
83
+ "sharedConsumer": True,
84
+ }
85
+ )
86
+ elif project_type == "node":
87
+ try:
88
+ package = read_json(project / "package.json")
89
+ except (OSError, json.JSONDecodeError):
90
+ package = {}
91
+ workspaces = package.get("workspaces", []) if isinstance(package, dict) else []
92
+ if isinstance(workspaces, dict):
93
+ workspaces = workspaces.get("packages", [])
94
+ paths: set[str] = set()
95
+ for pattern in workspaces if isinstance(workspaces, list) else []:
96
+ for match in project.glob(str(pattern)):
97
+ if match.is_dir() and (match / "package.json").is_file():
98
+ paths.add(match.relative_to(project).as_posix())
99
+ modules = [
100
+ {
101
+ "id": path.replace("/", "-"),
102
+ "path": path,
103
+ "type": "node-workspace",
104
+ "ownedPaths": [f"{path}/src/**", f"{path}/test/**"],
105
+ "dependsOn": [],
106
+ }
107
+ for path in sorted(paths)
108
+ ]
109
+ targets.extend(
110
+ {
111
+ "id": f"{module['id']}-test",
112
+ "verification": "unitTest",
113
+ "level": "module",
114
+ "command": f"npm test -w {module['path']}",
115
+ "inputs": module["ownedPaths"],
116
+ "sharedConsumer": False,
117
+ }
118
+ for module in modules
119
+ )
120
+ node_commands = _node_commands(project)
121
+ if "unitTestFull" in node_commands:
122
+ targets.append(
123
+ {
124
+ "id": "workspace-candidate",
125
+ "verification": "unitTestFull",
126
+ "level": "candidate",
127
+ "command": node_commands["unitTestFull"]["command"],
128
+ "inputs": node_commands["unitTestFull"]["inputs"],
129
+ "sharedConsumer": True,
130
+ }
131
+ )
132
+ boundaries_proven = bool(modules) or project_type in {"node", "java-maven"}
133
+ return {
134
+ "ok": True,
135
+ "code": "PROFILE_RECOMMENDED",
136
+ "schemaVersion": SCHEMA_VERSION,
137
+ "projectType": project_type,
138
+ "modules": modules,
139
+ "targets": targets,
140
+ "moduleGraph": {
141
+ "modules": modules,
142
+ "boundariesProven": boundaries_proven,
143
+ "needsReview": not boundaries_proven,
144
+ },
145
+ "needsReview": not boundaries_proven,
146
+ "applied": False,
147
+ }
148
+
149
+
150
+ def project_profile_v3(profile: dict[str, Any]) -> dict[str, Any]:
151
+ """Compatibility projection for review before a protected v3 update."""
152
+ projected = json.loads(json.dumps(profile))
153
+ source_version = projected.get("schemaVersion")
154
+ if source_version == SCHEMA_VERSION:
155
+ return projected
156
+ if source_version != 2:
157
+ return projected
158
+ projected["schemaVersion"] = SCHEMA_VERSION
159
+ commands = projected.get("commands") if isinstance(projected.get("commands"), dict) else {}
160
+ projected.setdefault(
161
+ "moduleGraph",
162
+ {"modules": [], "boundariesProven": False, "needsReview": True},
163
+ )
164
+ projected["commandGraph"] = {
165
+ "targets": [
166
+ {
167
+ "id": key,
168
+ "verification": key,
169
+ "command": command.get("command"),
170
+ "scope": command.get("scope"),
171
+ "inputs": list(command.get("inputs") or []),
172
+ "coverage": command.get("coverage"),
173
+ "source": command.get("source"),
174
+ }
175
+ for key, command in sorted(commands.items())
176
+ if isinstance(command, dict)
177
+ ]
178
+ }
179
+ projected["migration"] = {
180
+ "fromSchemaVersion": 2,
181
+ "projected": True,
182
+ "needsReview": True,
183
+ "protectedUserCommands": sorted(
184
+ key
185
+ for key, command in commands.items()
186
+ if isinstance(command, dict) and command.get("source") == "user"
187
+ ),
188
+ }
189
+ return projected
190
+
191
+
192
+ def audit_profile(project: Path) -> dict[str, Any]:
193
+ path = Path(project).resolve() / PROFILE_REL
194
+ if not path.is_file():
195
+ return {
196
+ "ok": False,
197
+ "code": "PROFILE_MISSING",
198
+ "needsReview": True,
199
+ "issues": ["build-profile.json missing"],
200
+ }
201
+ try:
202
+ profile = read_json(path)
203
+ except (OSError, json.JSONDecodeError) as exc:
204
+ return {
205
+ "ok": False,
206
+ "code": "PROFILE_INVALID",
207
+ "needsReview": True,
208
+ "issues": [str(exc)],
209
+ }
210
+ issues: list[str] = []
211
+ if profile.get("schemaVersion") != SCHEMA_VERSION:
212
+ issues.append("profile schema requires migration")
213
+ module_graph = profile.get("moduleGraph")
214
+ if not isinstance(module_graph, dict) or module_graph.get("boundariesProven") is not True:
215
+ issues.append("module boundaries are not proven")
216
+ if not isinstance(profile.get("commandGraph"), dict):
217
+ issues.append("command graph missing")
218
+ needs_review = bool(issues)
219
+ return {
220
+ "ok": not needs_review,
221
+ "code": "PROFILE_REVIEW_REQUIRED" if needs_review else "PROFILE_AUDITED",
222
+ "needsReview": needs_review,
223
+ "issues": issues,
224
+ }
225
+
46
226
  # 排除策略(spec §3.1):所有路径必须位于 project root,排除以下目录。
47
227
  # 兄弟 worktree、构建产物、依赖目录、缓存一律不进入 verification inputs。
48
228
  DEFAULT_EXCLUDED_ROOTS: tuple[str, ...] = (
@@ -406,6 +586,10 @@ def load_profile(project: Path) -> dict[str, Any] | None:
406
586
  except (OSError, json.JSONDecodeError):
407
587
  pass
408
588
 
589
+ if common_data is not None and common_data.get("schemaVersion") == 2:
590
+ common_data = project_profile_v3(common_data)
591
+ if exec_data is not None and exec_data.get("schemaVersion") == 2:
592
+ exec_data = project_profile_v3(exec_data)
409
593
  if common_data is None and exec_data is None:
410
594
  return None
411
595
  if common_data is None:
@@ -422,6 +606,19 @@ def load_profile(project: Path) -> dict[str, Any] | None:
422
606
  if isinstance(exec_cmds, dict):
423
607
  merged_cmds.update(exec_cmds)
424
608
  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
618
+ _derive_verification_inputs(merged)
619
+ for field in ("moduleGraph", "commandGraph", "migration"):
620
+ if field in exec_data:
621
+ merged[field] = exec_data[field]
425
622
  return merged
426
623
 
427
624
 
@@ -533,6 +730,24 @@ def detect(project: Path) -> dict[str, Any]:
533
730
 
534
731
  _merge_user_overrides(profile, existing)
535
732
  _derive_verification_inputs(profile)
733
+ recommendation = recommend(project)
734
+ profile["moduleGraph"] = recommendation["moduleGraph"]
735
+ profile["commandGraph"] = {
736
+ "targets": [
737
+ {
738
+ "id": key,
739
+ "verification": key,
740
+ "command": command.get("command"),
741
+ "scope": command.get("scope"),
742
+ "inputs": list(command.get("inputs") or []),
743
+ "coverage": command.get("coverage"),
744
+ "source": command.get("source"),
745
+ }
746
+ for key, command in sorted(profile["commands"].items())
747
+ if isinstance(command, dict)
748
+ ]
749
+ + list(recommendation["targets"])
750
+ }
536
751
 
537
752
  out_path = project / PROFILE_REL
538
753
  write_json(out_path, profile)
@@ -735,8 +950,8 @@ def resolve_command(
735
950
  # migrate(v1 → v2,dry-run/apply/备份)
736
951
  # ---------------------------------------------------------------------------
737
952
 
738
- def _backup_path(profile_path: Path) -> Path:
739
- return profile_path.with_suffix(profile_path.suffix + ".v1.bak")
953
+ def _backup_path(profile_path: Path, version: int = 1) -> Path:
954
+ return profile_path.with_suffix(profile_path.suffix + f".v{version}.bak")
740
955
 
741
956
 
742
957
  def migrate(project: Path, *, dry_run: bool = True) -> dict[str, Any]:
@@ -780,7 +995,7 @@ def migrate(project: Path, *, dry_run: bool = True) -> dict[str, Any]:
780
995
  "needsMigration": False,
781
996
  "reason": f"already schemaVersion {SCHEMA_VERSION}",
782
997
  }
783
- if sv != 1:
998
+ if sv not in {1, 2}:
784
999
  return {
785
1000
  "ok": False,
786
1001
  "action": "migrate",
@@ -789,13 +1004,21 @@ def migrate(project: Path, *, dry_run: bool = True) -> dict[str, Any]:
789
1004
  "error": f"unsupported schemaVersion: {sv}",
790
1005
  }
791
1006
 
792
- # v1 → v2 迁移清单
1007
+ # v1/v2v3 迁移清单
793
1008
  changes = [
794
- f"schemaVersion 1 → {SCHEMA_VERSION}",
795
- "buildCommands commands(含 argvTemplate/scope/inputs/coverage/source/basis)",
796
- "excludedRoots 显式声明(.git/.harness/worktrees/target/build/node_modules/...)",
797
- "identifier 约束(pattern/maxLength/prefix)",
1009
+ f"schemaVersion {sv} → {SCHEMA_VERSION}",
1010
+ "新增 moduleGraph(模块边界/owned paths/dependency)",
1011
+ "新增 commandGraph(dynamic verification targets)",
1012
+ "source=user 命令字节语义保持不变并列入 protectedUserCommands",
798
1013
  ]
1014
+ if sv == 1:
1015
+ changes.extend(
1016
+ [
1017
+ "buildCommands → commands(含 argvTemplate/scope/inputs/coverage/source/basis)",
1018
+ "excludedRoots 显式声明(.git/.harness/worktrees/target/build/node_modules/...)",
1019
+ "identifier 约束(pattern/maxLength/prefix)",
1020
+ ]
1021
+ )
799
1022
  dropped: list[str] = []
800
1023
  v1_svc = existing.get("serviceStart") or {}
801
1024
  if isinstance(v1_svc, dict):
@@ -811,23 +1034,37 @@ def migrate(project: Path, *, dry_run: bool = True) -> dict[str, Any]:
811
1034
  dropped.append(f"serviceStart.{field}(无 provenance,已备份;请用 record-quirk 重配)")
812
1035
 
813
1036
  if dry_run:
1037
+ projected = project_profile_v3(existing) if sv == 2 else None
814
1038
  return {
815
1039
  "ok": True,
1040
+ "code": "PROFILE_MIGRATION_PLANNED",
816
1041
  "action": "migrate",
817
1042
  "dry_run": True,
818
1043
  "needsMigration": True,
819
1044
  "changes": changes,
820
1045
  "droppedFields": dropped,
821
1046
  "profilePath": str(profile_path),
1047
+ "projectedProfile": projected,
1048
+ "protectedUserCommands": (
1049
+ projected.get("migration", {}).get("protectedUserCommands", [])
1050
+ if isinstance(projected, dict)
1051
+ else []
1052
+ ),
822
1053
  }
823
1054
 
824
- # apply:先备份原 v1(不覆盖已有备份),再重新 detect 生成 v2
825
- backup = _backup_path(profile_path)
1055
+ # apply:先备份旧 profile。v2 结构兼容投影,不重新 detect,避免覆盖 user command。
1056
+ backup = _backup_path(profile_path, int(sv))
826
1057
  if not backup.is_file():
827
1058
  backup.write_text(profile_path.read_text(encoding="utf-8-sig"), encoding="utf-8", newline="\n")
828
- detect(project)
1059
+ if sv == 2:
1060
+ projected = project_profile_v3(existing)
1061
+ projected.setdefault("migration", {})["appliedAt"] = now_iso()
1062
+ write_json(profile_path, projected)
1063
+ else:
1064
+ detect(project)
829
1065
  return {
830
1066
  "ok": True,
1067
+ "code": "PROFILE_MIGRATED",
831
1068
  "action": "migrate",
832
1069
  "dry_run": False,
833
1070
  "needsMigration": False,
@@ -845,7 +1082,7 @@ def migrate(project: Path, *, dry_run: bool = True) -> dict[str, Any]:
845
1082
  def build_parser() -> argparse.ArgumentParser:
846
1083
  parser = argparse.ArgumentParser(
847
1084
  prog="harness_profile.py",
848
- description="Harness profile v2: detect/check/validate/resolve/migrate",
1085
+ description="Harness profile v3: recommend/detect/audit/validate/resolve/migrate",
849
1086
  )
850
1087
  parser.add_argument("--json", action="store_true", help="emit JSON")
851
1088
  sub = parser.add_subparsers(dest="command", required=True)
@@ -854,6 +1091,18 @@ def build_parser() -> argparse.ArgumentParser:
854
1091
  p_detect.add_argument("--project", required=True, type=Path)
855
1092
  p_detect.add_argument("--json", action="store_true")
856
1093
 
1094
+ p_recommend = sub.add_parser(
1095
+ "recommend", help="analyze modules/targets without writing canonical profile"
1096
+ )
1097
+ p_recommend.add_argument("--project", required=True, type=Path)
1098
+ p_recommend.add_argument("--json", action="store_true")
1099
+
1100
+ p_audit = sub.add_parser(
1101
+ "audit", help="fail closed when module or command boundaries are unproven"
1102
+ )
1103
+ p_audit.add_argument("--project", required=True, type=Path)
1104
+ p_audit.add_argument("--json", action="store_true")
1105
+
857
1106
  p_check = sub.add_parser("check", help="missing/invalid/stale/ready")
858
1107
  p_check.add_argument("--project", required=True, type=Path)
859
1108
  p_check.add_argument("--json", action="store_true")
@@ -879,7 +1128,7 @@ def build_parser() -> argparse.ArgumentParser:
879
1128
  )
880
1129
  p_resolve.add_argument("--json", action="store_true")
881
1130
 
882
- p_migrate = sub.add_parser("migrate", help="migrate v1 → v2")
1131
+ p_migrate = sub.add_parser("migrate", help="migrate v1/v2v3")
883
1132
  p_migrate.add_argument("--project", required=True, type=Path)
884
1133
  p_migrate.add_argument("--apply", action="store_true", help="apply (default: dry-run)")
885
1134
  p_migrate.add_argument("--json", action="store_true")
@@ -896,6 +1145,14 @@ def main(argv: list[str] | None = None) -> int:
896
1145
  result = detect(args.project)
897
1146
  sys.stdout.write(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
898
1147
  return 0 if result.get("ok") else 1
1148
+ if args.command == "recommend":
1149
+ result = recommend(args.project)
1150
+ sys.stdout.write(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
1151
+ return 0 if result.get("ok") else 1
1152
+ if args.command == "audit":
1153
+ result = audit_profile(args.project)
1154
+ sys.stdout.write(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
1155
+ return 0 if result.get("ok") else 1
899
1156
  if args.command == "check":
900
1157
  result = check(args.project)
901
1158
  sys.stdout.write(json.dumps(result, ensure_ascii=False, indent=2) + "\n")