@rpamis/comet 0.4.0-beta.2 → 0.4.0-beta.4

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 (509) hide show
  1. package/README.md +27 -18
  2. package/assets/manifest.json +2 -1
  3. package/assets/skills/comet/SKILL.md +25 -1
  4. package/assets/skills/comet/reference/comet-yaml-fields.md +6 -4
  5. package/assets/skills/comet/reference/context-recovery.md +10 -0
  6. package/assets/skills/comet/reference/scripts.md +47 -15
  7. package/assets/skills/comet/rules/comet-phase-guard.en.md +8 -0
  8. package/assets/skills/comet/rules/comet-phase-guard.md +9 -1
  9. package/assets/skills/comet/scripts/comet-resume-probe.mjs +3 -0
  10. package/assets/skills/comet/scripts/comet-runtime.mjs +1664 -512
  11. package/assets/skills/comet-archive/SKILL.md +24 -11
  12. package/assets/skills/comet-build/SKILL.md +40 -31
  13. package/assets/skills/comet-design/SKILL.md +1 -0
  14. package/assets/skills/comet-hotfix/SKILL.md +3 -1
  15. package/assets/skills/comet-open/SKILL.md +11 -1
  16. package/assets/skills/comet-tweak/SKILL.md +3 -1
  17. package/assets/skills/comet-verify/SKILL.md +29 -20
  18. package/assets/skills-zh/comet/SKILL.md +25 -1
  19. package/assets/skills-zh/comet/reference/comet-yaml-fields.md +5 -4
  20. package/assets/skills-zh/comet/reference/context-recovery.md +10 -0
  21. package/assets/skills-zh/comet/reference/scripts.md +47 -15
  22. package/assets/skills-zh/comet-archive/SKILL.md +24 -11
  23. package/assets/skills-zh/comet-build/SKILL.md +40 -31
  24. package/assets/skills-zh/comet-design/SKILL.md +1 -0
  25. package/assets/skills-zh/comet-hotfix/SKILL.md +3 -1
  26. package/assets/skills-zh/comet-open/SKILL.md +11 -1
  27. package/assets/skills-zh/comet-tweak/SKILL.md +3 -1
  28. package/assets/skills-zh/comet-verify/SKILL.md +30 -21
  29. package/dist/app/cli/comet-banner.d.ts +25 -0
  30. package/dist/app/cli/comet-banner.d.ts.map +1 -0
  31. package/dist/app/cli/comet-banner.js +265 -0
  32. package/dist/app/cli/comet-banner.js.map +1 -0
  33. package/dist/app/cli/index.js +37 -3
  34. package/dist/app/cli/index.js.map +1 -1
  35. package/dist/app/commands/classic.d.ts +4 -0
  36. package/dist/app/commands/classic.d.ts.map +1 -0
  37. package/dist/app/commands/classic.js +11 -0
  38. package/dist/app/commands/classic.js.map +1 -0
  39. package/dist/app/commands/doctor.d.ts +1 -0
  40. package/dist/app/commands/doctor.d.ts.map +1 -1
  41. package/dist/app/commands/doctor.js +100 -35
  42. package/dist/app/commands/doctor.js.map +1 -1
  43. package/dist/app/commands/eval.d.ts.map +1 -1
  44. package/dist/app/commands/eval.js +57 -20
  45. package/dist/app/commands/eval.js.map +1 -1
  46. package/dist/app/commands/i18n.d.ts +1 -1
  47. package/dist/app/commands/i18n.d.ts.map +1 -1
  48. package/dist/app/commands/i18n.js +12 -4
  49. package/dist/app/commands/i18n.js.map +1 -1
  50. package/dist/app/commands/init.d.ts.map +1 -1
  51. package/dist/app/commands/init.js +16 -13
  52. package/dist/app/commands/init.js.map +1 -1
  53. package/dist/app/commands/project-scope-selection.d.ts +13 -0
  54. package/dist/app/commands/project-scope-selection.d.ts.map +1 -0
  55. package/dist/app/commands/project-scope-selection.js +33 -0
  56. package/dist/app/commands/project-scope-selection.js.map +1 -0
  57. package/dist/app/commands/resume-probe.d.ts +11 -0
  58. package/dist/app/commands/resume-probe.d.ts.map +1 -0
  59. package/dist/app/commands/resume-probe.js +63 -0
  60. package/dist/app/commands/resume-probe.js.map +1 -0
  61. package/dist/app/commands/status.d.ts +31 -0
  62. package/dist/app/commands/status.d.ts.map +1 -1
  63. package/dist/app/commands/status.js +109 -20
  64. package/dist/app/commands/status.js.map +1 -1
  65. package/dist/app/commands/uninstall.d.ts +2 -0
  66. package/dist/app/commands/uninstall.d.ts.map +1 -1
  67. package/dist/app/commands/uninstall.js +217 -40
  68. package/dist/app/commands/uninstall.js.map +1 -1
  69. package/dist/app/commands/update.d.ts +6 -0
  70. package/dist/app/commands/update.d.ts.map +1 -1
  71. package/dist/app/commands/update.js +361 -68
  72. package/dist/app/commands/update.js.map +1 -1
  73. package/dist/config/repository-layout.json +4 -2
  74. package/dist/domains/bundle/bundle-platform.d.ts.map +1 -1
  75. package/dist/domains/bundle/bundle-platform.js +3 -1
  76. package/dist/domains/bundle/bundle-platform.js.map +1 -1
  77. package/dist/domains/bundle/eval-manifest-runtime.d.ts +6 -0
  78. package/dist/domains/bundle/eval-manifest-runtime.d.ts.map +1 -0
  79. package/dist/domains/bundle/eval-manifest-runtime.js +58 -0
  80. package/dist/domains/bundle/eval-manifest-runtime.js.map +1 -0
  81. package/dist/domains/bundle/types.d.ts +1 -1
  82. package/dist/domains/bundle/types.d.ts.map +1 -1
  83. package/dist/domains/comet-classic/classic-archive.d.ts +1 -0
  84. package/dist/domains/comet-classic/classic-archive.d.ts.map +1 -1
  85. package/dist/domains/comet-classic/classic-archive.js +35 -38
  86. package/dist/domains/comet-classic/classic-archive.js.map +1 -1
  87. package/dist/domains/comet-classic/classic-cli.d.ts +1 -1
  88. package/dist/domains/comet-classic/classic-cli.d.ts.map +1 -1
  89. package/dist/domains/comet-classic/classic-cli.js +3 -0
  90. package/dist/domains/comet-classic/classic-cli.js.map +1 -1
  91. package/dist/domains/comet-classic/classic-command-checks.d.ts +20 -0
  92. package/dist/domains/comet-classic/classic-command-checks.d.ts.map +1 -0
  93. package/dist/domains/comet-classic/classic-command-checks.js +97 -0
  94. package/dist/domains/comet-classic/classic-command-checks.js.map +1 -0
  95. package/dist/domains/comet-classic/classic-current-change.d.ts +19 -0
  96. package/dist/domains/comet-classic/classic-current-change.d.ts.map +1 -0
  97. package/dist/domains/comet-classic/classic-current-change.js +132 -0
  98. package/dist/domains/comet-classic/classic-current-change.js.map +1 -0
  99. package/dist/domains/comet-classic/classic-diagnostics.d.ts +2 -1
  100. package/dist/domains/comet-classic/classic-diagnostics.d.ts.map +1 -1
  101. package/dist/domains/comet-classic/classic-diagnostics.js +54 -1
  102. package/dist/domains/comet-classic/classic-diagnostics.js.map +1 -1
  103. package/dist/domains/comet-classic/classic-evidence.d.ts.map +1 -1
  104. package/dist/domains/comet-classic/classic-evidence.js +4 -0
  105. package/dist/domains/comet-classic/classic-evidence.js.map +1 -1
  106. package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
  107. package/dist/domains/comet-classic/classic-guard.js +101 -141
  108. package/dist/domains/comet-classic/classic-guard.js.map +1 -1
  109. package/dist/domains/comet-classic/classic-hook-guard.d.ts.map +1 -1
  110. package/dist/domains/comet-classic/classic-hook-guard.js +67 -29
  111. package/dist/domains/comet-classic/classic-hook-guard.js.map +1 -1
  112. package/dist/domains/comet-classic/classic-project-config.d.ts +16 -0
  113. package/dist/domains/comet-classic/classic-project-config.d.ts.map +1 -0
  114. package/dist/domains/comet-classic/classic-project-config.js +40 -0
  115. package/dist/domains/comet-classic/classic-project-config.js.map +1 -0
  116. package/dist/domains/comet-classic/classic-resolver.d.ts.map +1 -1
  117. package/dist/domains/comet-classic/classic-resolver.js +3 -3
  118. package/dist/domains/comet-classic/classic-resolver.js.map +1 -1
  119. package/dist/domains/comet-classic/classic-resume-probe-command.d.ts +3 -0
  120. package/dist/domains/comet-classic/classic-resume-probe-command.d.ts.map +1 -0
  121. package/dist/domains/comet-classic/classic-resume-probe-command.js +58 -0
  122. package/dist/domains/comet-classic/classic-resume-probe-command.js.map +1 -0
  123. package/dist/domains/comet-classic/classic-resume-probe-entry.d.ts +2 -0
  124. package/dist/domains/comet-classic/classic-resume-probe-entry.d.ts.map +1 -0
  125. package/dist/domains/comet-classic/classic-resume-probe-entry.js +4 -0
  126. package/dist/domains/comet-classic/classic-resume-probe-entry.js.map +1 -0
  127. package/dist/domains/comet-classic/classic-resume-probe.d.ts +29 -0
  128. package/dist/domains/comet-classic/classic-resume-probe.d.ts.map +1 -0
  129. package/dist/domains/comet-classic/classic-resume-probe.js +386 -0
  130. package/dist/domains/comet-classic/classic-resume-probe.js.map +1 -0
  131. package/dist/domains/comet-classic/classic-runtime-run.d.ts +2 -1
  132. package/dist/domains/comet-classic/classic-runtime-run.d.ts.map +1 -1
  133. package/dist/domains/comet-classic/classic-runtime-run.js +269 -4
  134. package/dist/domains/comet-classic/classic-runtime-run.js.map +1 -1
  135. package/dist/domains/comet-classic/classic-state-command.d.ts.map +1 -1
  136. package/dist/domains/comet-classic/classic-state-command.js +129 -15
  137. package/dist/domains/comet-classic/classic-state-command.js.map +1 -1
  138. package/dist/domains/comet-classic/classic-state.d.ts +3 -3
  139. package/dist/domains/comet-classic/classic-state.d.ts.map +1 -1
  140. package/dist/domains/comet-classic/classic-state.js +4 -6
  141. package/dist/domains/comet-classic/classic-state.js.map +1 -1
  142. package/dist/domains/comet-classic/classic-store.d.ts +4 -1
  143. package/dist/domains/comet-classic/classic-store.d.ts.map +1 -1
  144. package/dist/domains/comet-classic/classic-store.js +27 -9
  145. package/dist/domains/comet-classic/classic-store.js.map +1 -1
  146. package/dist/domains/comet-classic/classic-transitions.d.ts +1 -1
  147. package/dist/domains/comet-classic/classic-transitions.d.ts.map +1 -1
  148. package/dist/domains/comet-classic/classic-transitions.js +20 -1
  149. package/dist/domains/comet-classic/classic-transitions.js.map +1 -1
  150. package/dist/domains/comet-classic/classic-validate-command.d.ts.map +1 -1
  151. package/dist/domains/comet-classic/classic-validate-command.js +1 -0
  152. package/dist/domains/comet-classic/classic-validate-command.js.map +1 -1
  153. package/dist/domains/comet-classic/index.d.ts +3 -0
  154. package/dist/domains/comet-classic/index.d.ts.map +1 -1
  155. package/dist/domains/comet-classic/index.js +3 -0
  156. package/dist/domains/comet-classic/index.js.map +1 -1
  157. package/dist/domains/dashboard/collector.js +10 -2
  158. package/dist/domains/dashboard/collector.js.map +1 -1
  159. package/dist/domains/dashboard/web/assets/{index-CjU3VXVu.js → index-CIvN-PL8.js} +11 -11
  160. package/dist/domains/dashboard/web/assets/{index-BcO3_QlT.css → index-DcyRPBnT.css} +1 -1
  161. package/dist/domains/dashboard/web/index.html +16 -16
  162. package/dist/domains/engine/types.d.ts +1 -1
  163. package/dist/domains/engine/types.d.ts.map +1 -1
  164. package/dist/domains/integrations/openspec.d.ts.map +1 -1
  165. package/dist/domains/integrations/openspec.js +6 -6
  166. package/dist/domains/integrations/openspec.js.map +1 -1
  167. package/dist/domains/skill/find.d.ts.map +1 -1
  168. package/dist/domains/skill/find.js +1 -6
  169. package/dist/domains/skill/find.js.map +1 -1
  170. package/dist/domains/skill/managed-markdown.d.ts +14 -0
  171. package/dist/domains/skill/managed-markdown.d.ts.map +1 -0
  172. package/dist/domains/skill/managed-markdown.js +126 -0
  173. package/dist/domains/skill/managed-markdown.js.map +1 -0
  174. package/dist/domains/skill/platform-install.d.ts +4 -3
  175. package/dist/domains/skill/platform-install.d.ts.map +1 -1
  176. package/dist/domains/skill/platform-install.js +96 -26
  177. package/dist/domains/skill/platform-install.js.map +1 -1
  178. package/dist/domains/skill/project-instructions.d.ts +22 -0
  179. package/dist/domains/skill/project-instructions.d.ts.map +1 -0
  180. package/dist/domains/skill/project-instructions.js +61 -0
  181. package/dist/domains/skill/project-instructions.js.map +1 -0
  182. package/dist/domains/skill/uninstall.d.ts +3 -1
  183. package/dist/domains/skill/uninstall.d.ts.map +1 -1
  184. package/dist/domains/skill/uninstall.js +86 -39
  185. package/dist/domains/skill/uninstall.js.map +1 -1
  186. package/dist/platform/install/detect.d.ts +2 -1
  187. package/dist/platform/install/detect.d.ts.map +1 -1
  188. package/dist/platform/install/detect.js +10 -1
  189. package/dist/platform/install/detect.js.map +1 -1
  190. package/dist/platform/install/platforms.d.ts +5 -1
  191. package/dist/platform/install/platforms.d.ts.map +1 -1
  192. package/dist/platform/install/platforms.js +21 -4
  193. package/dist/platform/install/platforms.js.map +1 -1
  194. package/dist/platform/install/project-registry.d.ts +37 -0
  195. package/dist/platform/install/project-registry.d.ts.map +1 -0
  196. package/dist/platform/install/project-registry.js +205 -0
  197. package/dist/platform/install/project-registry.js.map +1 -0
  198. package/eval/.env +28 -0
  199. package/eval/.env.example +52 -0
  200. package/eval/CLAUDE.md +43 -0
  201. package/eval/README.md +538 -0
  202. package/eval/langsmith/.env.example +16 -0
  203. package/eval/langsmith/README.md +93 -0
  204. package/eval/langsmith/skills/README.md +5 -0
  205. package/eval/langsmith/tasks/README.md +5 -0
  206. package/eval/langsmith/tests/conftest.py +187 -0
  207. package/eval/langsmith/tests/tasks/test_tasks.py +287 -0
  208. package/eval/langsmith/treatments/README.md +5 -0
  209. package/eval/local/README.md +80 -0
  210. package/eval/local/regression_baseline.json +13 -0
  211. package/eval/local/report-style-demo-assets/quality_cost.en.svg +41 -0
  212. package/eval/local/report-style-demo-assets/quality_cost.zh.svg +41 -0
  213. package/eval/local/report-style-demo-assets/rubric_delta.en.svg +56 -0
  214. package/eval/local/report-style-demo-assets/rubric_delta.zh.svg +56 -0
  215. package/eval/local/report-style-demo-assets/task_outcomes.en.svg +46 -0
  216. package/eval/local/report-style-demo-assets/task_outcomes.zh.svg +46 -0
  217. package/eval/local/report-style-demo.html +867 -0
  218. package/eval/local/scripts/compare_baselines.py +1018 -0
  219. package/eval/local/scripts/generate_report_style_demo_charts.py +322 -0
  220. package/eval/local/scripts/regression_check.py +171 -0
  221. package/eval/local/scripts/render_readme_benchmark_figures.py +416 -0
  222. package/eval/local/scripts/rescore_rubric.py +120 -0
  223. package/eval/local/skills/benchmarks/039-release/comet-classic-039/SKILL.md +282 -0
  224. package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/auto-transition.md +27 -0
  225. package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/comet-yaml-fields.md +68 -0
  226. package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/context-recovery.md +35 -0
  227. package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/debug-gate.md +17 -0
  228. package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/decision-point.md +20 -0
  229. package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/dirty-worktree.md +59 -0
  230. package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/file-structure.md +28 -0
  231. package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/subagent-dispatch.md +113 -0
  232. package/eval/local/skills/benchmarks/039-release/comet-classic-039/rules/comet-phase-guard.md +125 -0
  233. package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-archive.sh +311 -0
  234. package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-env.sh +110 -0
  235. package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-guard.sh +778 -0
  236. package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-handoff.sh +390 -0
  237. package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-hook-guard.sh +336 -0
  238. package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-state.sh +1338 -0
  239. package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-yaml-validate.sh +225 -0
  240. package/eval/local/skills/benchmarks/039-release/comet-classic-039-archive/SKILL.md +100 -0
  241. package/eval/local/skills/benchmarks/039-release/comet-classic-039-build/SKILL.md +304 -0
  242. package/eval/local/skills/benchmarks/039-release/comet-classic-039-design/SKILL.md +263 -0
  243. package/eval/local/skills/benchmarks/039-release/comet-classic-039-hotfix/SKILL.md +204 -0
  244. package/eval/local/skills/benchmarks/039-release/comet-classic-039-open/SKILL.md +220 -0
  245. package/eval/local/skills/benchmarks/039-release/comet-classic-039-tweak/SKILL.md +180 -0
  246. package/eval/local/skills/benchmarks/039-release/comet-classic-039-verify/SKILL.md +234 -0
  247. package/eval/local/skills/benchmarks/040-beta/comet/SKILL.md +261 -0
  248. package/eval/local/skills/benchmarks/040-beta/comet/reference/auto-transition.md +27 -0
  249. package/eval/local/skills/benchmarks/040-beta/comet/reference/comet-yaml-fields.md +73 -0
  250. package/eval/local/skills/benchmarks/040-beta/comet/reference/context-recovery.md +45 -0
  251. package/eval/local/skills/benchmarks/040-beta/comet/reference/debug-gate.md +33 -0
  252. package/eval/local/skills/benchmarks/040-beta/comet/reference/decision-point.md +31 -0
  253. package/eval/local/skills/benchmarks/040-beta/comet/reference/dirty-worktree.md +59 -0
  254. package/eval/local/skills/benchmarks/040-beta/comet/reference/file-structure.md +28 -0
  255. package/eval/local/skills/benchmarks/040-beta/comet/reference/intent-frame.md +74 -0
  256. package/eval/local/skills/benchmarks/040-beta/comet/reference/scripts.md +69 -0
  257. package/eval/local/skills/benchmarks/040-beta/comet/reference/subagent-dispatch.md +169 -0
  258. package/eval/local/skills/benchmarks/040-beta/comet/rules/comet-phase-guard.md +130 -0
  259. package/eval/local/skills/benchmarks/040-beta/comet/runtime/classic/checks.yaml +6 -0
  260. package/eval/local/skills/benchmarks/040-beta/comet/runtime/classic/guardrails.yaml +13 -0
  261. package/eval/local/skills/benchmarks/040-beta/comet/runtime/classic/skill.yaml +122 -0
  262. package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-archive.mjs +9515 -0
  263. package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-env.mjs +15 -0
  264. package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-guard.mjs +10173 -0
  265. package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-handoff.mjs +9491 -0
  266. package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-hook-guard.mjs +9175 -0
  267. package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-intent.mjs +404 -0
  268. package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-state.mjs +9273 -0
  269. package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-yaml-validate.mjs +7627 -0
  270. package/eval/local/skills/benchmarks/040-beta/comet-archive/SKILL.md +110 -0
  271. package/eval/local/skills/benchmarks/040-beta/comet-build/SKILL.md +315 -0
  272. package/eval/local/skills/benchmarks/040-beta/comet-design/SKILL.md +260 -0
  273. package/eval/local/skills/benchmarks/040-beta/comet-hotfix/SKILL.md +190 -0
  274. package/eval/local/skills/benchmarks/040-beta/comet-open/SKILL.md +210 -0
  275. package/eval/local/skills/benchmarks/040-beta/comet-tweak/SKILL.md +183 -0
  276. package/eval/local/skills/benchmarks/040-beta/comet-verify/SKILL.md +230 -0
  277. package/eval/local/skills/benchmarks/dependency/claude-md/comet-workflow/CLAUDE.md +12 -0
  278. package/eval/local/skills/benchmarks/dependency/openspec/openspec-apply-change/SKILL.md +156 -0
  279. package/eval/local/skills/benchmarks/dependency/openspec/openspec-archive-change/SKILL.md +114 -0
  280. package/eval/local/skills/benchmarks/dependency/openspec/openspec-bulk-archive-change/SKILL.md +246 -0
  281. package/eval/local/skills/benchmarks/dependency/openspec/openspec-continue-change/SKILL.md +118 -0
  282. package/eval/local/skills/benchmarks/dependency/openspec/openspec-explore/SKILL.md +288 -0
  283. package/eval/local/skills/benchmarks/dependency/openspec/openspec-ff-change/SKILL.md +101 -0
  284. package/eval/local/skills/benchmarks/dependency/openspec/openspec-new-change/SKILL.md +74 -0
  285. package/eval/local/skills/benchmarks/dependency/openspec/openspec-onboard/SKILL.md +554 -0
  286. package/eval/local/skills/benchmarks/dependency/openspec/openspec-propose/SKILL.md +110 -0
  287. package/eval/local/skills/benchmarks/dependency/openspec/openspec-sync-specs/SKILL.md +138 -0
  288. package/eval/local/skills/benchmarks/dependency/openspec/openspec-verify-change/SKILL.md +168 -0
  289. package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/SKILL.md +159 -0
  290. package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/frame-template.html +213 -0
  291. package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/helper.js +167 -0
  292. package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/server.cjs +723 -0
  293. package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/start-server.sh +209 -0
  294. package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/stop-server.sh +120 -0
  295. package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/spec-document-reviewer-prompt.md +49 -0
  296. package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/visual-companion.md +291 -0
  297. package/eval/local/skills/benchmarks/dependency/superpowers/dispatching-parallel-agents/SKILL.md +185 -0
  298. package/eval/local/skills/benchmarks/dependency/superpowers/executing-plans/SKILL.md +70 -0
  299. package/eval/local/skills/benchmarks/dependency/superpowers/finishing-a-development-branch/SKILL.md +241 -0
  300. package/eval/local/skills/benchmarks/dependency/superpowers/receiving-code-review/SKILL.md +213 -0
  301. package/eval/local/skills/benchmarks/dependency/superpowers/requesting-code-review/SKILL.md +103 -0
  302. package/eval/local/skills/benchmarks/dependency/superpowers/requesting-code-review/code-reviewer.md +172 -0
  303. package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/SKILL.md +418 -0
  304. package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/implementer-prompt.md +139 -0
  305. package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/scripts/review-package +44 -0
  306. package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/scripts/sdd-workspace +22 -0
  307. package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/scripts/task-brief +40 -0
  308. package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/task-reviewer-prompt.md +188 -0
  309. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/CREATION-LOG.md +119 -0
  310. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/SKILL.md +296 -0
  311. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/condition-based-waiting-example.ts +158 -0
  312. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/condition-based-waiting.md +115 -0
  313. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/defense-in-depth.md +122 -0
  314. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/find-polluter.sh +63 -0
  315. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/root-cause-tracing.md +169 -0
  316. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/test-academic.md +14 -0
  317. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/test-pressure-1.md +58 -0
  318. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/test-pressure-2.md +68 -0
  319. package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/test-pressure-3.md +69 -0
  320. package/eval/local/skills/benchmarks/dependency/superpowers/test-driven-development/SKILL.md +371 -0
  321. package/eval/local/skills/benchmarks/dependency/superpowers/test-driven-development/testing-anti-patterns.md +299 -0
  322. package/eval/local/skills/benchmarks/dependency/superpowers/using-git-worktrees/SKILL.md +202 -0
  323. package/eval/local/skills/benchmarks/dependency/superpowers/verification-before-completion/SKILL.md +139 -0
  324. package/eval/local/skills/benchmarks/dependency/superpowers/writing-plans/SKILL.md +174 -0
  325. package/eval/local/skills/benchmarks/dependency/superpowers/writing-plans/plan-document-reviewer-prompt.md +49 -0
  326. package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/SKILL.md +689 -0
  327. package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/anthropic-best-practices.md +1150 -0
  328. package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  329. package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/graphviz-conventions.dot +172 -0
  330. package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/persuasion-principles.md +187 -0
  331. package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/render-graphs.js +168 -0
  332. package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/testing-skills-with-subagents.md +384 -0
  333. package/eval/local/tasks/authoring-skill-smoke/environment/Dockerfile +2 -0
  334. package/eval/local/tasks/authoring-skill-smoke/instruction.md +4 -0
  335. package/eval/local/tasks/authoring-skill-smoke/task.toml +21 -0
  336. package/eval/local/tasks/authoring-skill-smoke/validation/test_authoring_skill_smoke.py +54 -0
  337. package/eval/local/tasks/comet-agent-memory-routing/environment/Dockerfile +28 -0
  338. package/eval/local/tasks/comet-agent-memory-routing/environment/agent_system.py +40 -0
  339. package/eval/local/tasks/comet-agent-memory-routing/environment/test_agent_system.py +29 -0
  340. package/eval/local/tasks/comet-agent-memory-routing/instruction.md +20 -0
  341. package/eval/local/tasks/comet-agent-memory-routing/task.toml +24 -0
  342. package/eval/local/tasks/comet-agent-memory-routing/validation/test_agent_memory_routing.py +24 -0
  343. package/eval/local/tasks/comet-api-cache-ttl/environment/Dockerfile +39 -0
  344. package/eval/local/tasks/comet-api-cache-ttl/environment/cache.py +39 -0
  345. package/eval/local/tasks/comet-api-cache-ttl/environment/test_cache.py +81 -0
  346. package/eval/local/tasks/comet-api-cache-ttl/instruction.md +33 -0
  347. package/eval/local/tasks/comet-api-cache-ttl/task.toml +24 -0
  348. package/eval/local/tasks/comet-api-cache-ttl/validation/test_api_cache_ttl.py +58 -0
  349. package/eval/local/tasks/comet-cross-file-refactor/environment/Dockerfile +15 -0
  350. package/eval/local/tasks/comet-cross-file-refactor/environment/cli.py +13 -0
  351. package/eval/local/tasks/comet-cross-file-refactor/environment/counting.py +13 -0
  352. package/eval/local/tasks/comet-cross-file-refactor/environment/test_textkit.py +21 -0
  353. package/eval/local/tasks/comet-cross-file-refactor/instruction.md +16 -0
  354. package/eval/local/tasks/comet-cross-file-refactor/task.toml +20 -0
  355. package/eval/local/tasks/comet-cross-file-refactor/validation/test_cross_file_refactor.py +20 -0
  356. package/eval/local/tasks/comet-dependency-confusion/environment/Dockerfile +28 -0
  357. package/eval/local/tasks/comet-dependency-confusion/environment/app.py +8 -0
  358. package/eval/local/tasks/comet-dependency-confusion/environment/settings_loader.py +14 -0
  359. package/eval/local/tasks/comet-dependency-confusion/environment/test_settings_service.py +19 -0
  360. package/eval/local/tasks/comet-dependency-confusion/instruction.md +18 -0
  361. package/eval/local/tasks/comet-dependency-confusion/task.toml +24 -0
  362. package/eval/local/tasks/comet-dependency-confusion/validation/test_dependency_confusion.py +24 -0
  363. package/eval/local/tasks/comet-fix-median/environment/Dockerfile +50 -0
  364. package/eval/local/tasks/comet-fix-median/environment/stats.py +54 -0
  365. package/eval/local/tasks/comet-fix-median/environment/test_stats.py +68 -0
  366. package/eval/local/tasks/comet-fix-median/instruction.md +25 -0
  367. package/eval/local/tasks/comet-fix-median/task.toml +24 -0
  368. package/eval/local/tasks/comet-fix-median/validation/test_fix_median.py +60 -0
  369. package/eval/local/tasks/comet-framework-selection/environment/Dockerfile +28 -0
  370. package/eval/local/tasks/comet-framework-selection/environment/architecture.py +12 -0
  371. package/eval/local/tasks/comet-framework-selection/environment/test_architecture.py +30 -0
  372. package/eval/local/tasks/comet-framework-selection/instruction.md +22 -0
  373. package/eval/local/tasks/comet-framework-selection/task.toml +24 -0
  374. package/eval/local/tasks/comet-framework-selection/validation/test_framework_selection.py +24 -0
  375. package/eval/local/tasks/comet-full-workflow/environment/Dockerfile +53 -0
  376. package/eval/local/tasks/comet-full-workflow/environment/test_wordcount.py +29 -0
  377. package/eval/local/tasks/comet-full-workflow/environment/wordcount.py +40 -0
  378. package/eval/local/tasks/comet-full-workflow/instruction.md +18 -0
  379. package/eval/local/tasks/comet-full-workflow/task.toml +24 -0
  380. package/eval/local/tasks/comet-full-workflow/validation/test_full_workflow.py +171 -0
  381. package/eval/local/tasks/comet-graph-execution-review/environment/Dockerfile +28 -0
  382. package/eval/local/tasks/comet-graph-execution-review/environment/pipeline.py +19 -0
  383. package/eval/local/tasks/comet-graph-execution-review/environment/test_pipeline.py +53 -0
  384. package/eval/local/tasks/comet-graph-execution-review/instruction.md +21 -0
  385. package/eval/local/tasks/comet-graph-execution-review/task.toml +24 -0
  386. package/eval/local/tasks/comet-graph-execution-review/validation/test_graph_execution_review.py +24 -0
  387. package/eval/local/tasks/comet-human-approval-flow/environment/Dockerfile +15 -0
  388. package/eval/local/tasks/comet-human-approval-flow/environment/approvals.py +17 -0
  389. package/eval/local/tasks/comet-human-approval-flow/environment/test_approvals.py +30 -0
  390. package/eval/local/tasks/comet-human-approval-flow/instruction.md +15 -0
  391. package/eval/local/tasks/comet-human-approval-flow/task.toml +20 -0
  392. package/eval/local/tasks/comet-human-approval-flow/validation/test_human_approval_flow.py +20 -0
  393. package/eval/local/tasks/comet-layered-streaming-fix/environment/Dockerfile +20 -0
  394. package/eval/local/tasks/comet-layered-streaming-fix/environment/chat_app.py +19 -0
  395. package/eval/local/tasks/comet-layered-streaming-fix/environment/test_chat_app.py +15 -0
  396. package/eval/local/tasks/comet-layered-streaming-fix/instruction.md +14 -0
  397. package/eval/local/tasks/comet-layered-streaming-fix/task.toml +20 -0
  398. package/eval/local/tasks/comet-layered-streaming-fix/validation/test_layered_streaming_fix.py +20 -0
  399. package/eval/local/tasks/comet-noise-distractor/environment/Dockerfile +15 -0
  400. package/eval/local/tasks/comet-noise-distractor/environment/distractor.md +4 -0
  401. package/eval/local/tasks/comet-noise-distractor/environment/invoice.py +5 -0
  402. package/eval/local/tasks/comet-noise-distractor/environment/test_invoice.py +18 -0
  403. package/eval/local/tasks/comet-noise-distractor/instruction.md +17 -0
  404. package/eval/local/tasks/comet-noise-distractor/task.toml +20 -0
  405. package/eval/local/tasks/comet-noise-distractor/validation/test_noise_distractor.py +20 -0
  406. package/eval/local/tasks/comet-observability-env-template/environment/Dockerfile +15 -0
  407. package/eval/local/tasks/comet-observability-env-template/environment/env_writer.py +17 -0
  408. package/eval/local/tasks/comet-observability-env-template/environment/test_env_writer.py +17 -0
  409. package/eval/local/tasks/comet-observability-env-template/instruction.md +20 -0
  410. package/eval/local/tasks/comet-observability-env-template/task.toml +20 -0
  411. package/eval/local/tasks/comet-observability-env-template/validation/test_observability_env_template.py +46 -0
  412. package/eval/local/tasks/comet-perf-dedupe/environment/Dockerfile +39 -0
  413. package/eval/local/tasks/comet-perf-dedupe/environment/dedupe.py +22 -0
  414. package/eval/local/tasks/comet-perf-dedupe/environment/test_dedupe.py +43 -0
  415. package/eval/local/tasks/comet-perf-dedupe/instruction.md +25 -0
  416. package/eval/local/tasks/comet-perf-dedupe/task.toml +24 -0
  417. package/eval/local/tasks/comet-perf-dedupe/validation/test_perf_dedupe.py +40 -0
  418. package/eval/local/tasks/comet-persistence-threading/environment/Dockerfile +15 -0
  419. package/eval/local/tasks/comet-persistence-threading/environment/session_store.py +15 -0
  420. package/eval/local/tasks/comet-persistence-threading/environment/test_session_store.py +21 -0
  421. package/eval/local/tasks/comet-persistence-threading/instruction.md +14 -0
  422. package/eval/local/tasks/comet-persistence-threading/task.toml +20 -0
  423. package/eval/local/tasks/comet-persistence-threading/validation/test_persistence_threading.py +20 -0
  424. package/eval/local/tasks/comet-refactor-counter/environment/Dockerfile +39 -0
  425. package/eval/local/tasks/comet-refactor-counter/environment/test_text_processor.py +57 -0
  426. package/eval/local/tasks/comet-refactor-counter/environment/text_processor.py +49 -0
  427. package/eval/local/tasks/comet-refactor-counter/instruction.md +31 -0
  428. package/eval/local/tasks/comet-refactor-counter/task.toml +24 -0
  429. package/eval/local/tasks/comet-refactor-counter/validation/test_refactor_counter.py +75 -0
  430. package/eval/local/tasks/comet-robust-config/environment/Dockerfile +39 -0
  431. package/eval/local/tasks/comet-robust-config/environment/config_loader.py +56 -0
  432. package/eval/local/tasks/comet-robust-config/environment/test_config_loader.py +72 -0
  433. package/eval/local/tasks/comet-robust-config/instruction.md +30 -0
  434. package/eval/local/tasks/comet-robust-config/task.toml +24 -0
  435. package/eval/local/tasks/comet-robust-config/validation/test_robust_config.py +37 -0
  436. package/eval/local/tasks/generic-skill-smoke/environment/Dockerfile +2 -0
  437. package/eval/local/tasks/generic-skill-smoke/instruction.md +7 -0
  438. package/eval/local/tasks/generic-skill-smoke/task.toml +21 -0
  439. package/eval/local/tasks/generic-skill-smoke/validation/test_generic_skill_smoke.py +36 -0
  440. package/eval/local/tasks/index.yaml +155 -0
  441. package/eval/local/tasks/workflow-overlay-contract/environment/Dockerfile +5 -0
  442. package/eval/local/tasks/workflow-overlay-contract/instruction.md +5 -0
  443. package/eval/local/tasks/workflow-overlay-contract/task.toml +21 -0
  444. package/eval/local/tasks/workflow-overlay-contract/validation/test_workflow_overlay_contract.py +235 -0
  445. package/eval/local/tasks/workflow-route-conformance/environment/Dockerfile +2 -0
  446. package/eval/local/tasks/workflow-route-conformance/instruction.md +5 -0
  447. package/eval/local/tasks/workflow-route-conformance/task.toml +21 -0
  448. package/eval/local/tasks/workflow-route-conformance/validation/test_workflow_route_conformance.py +84 -0
  449. package/eval/local/tests/conftest.py +1148 -0
  450. package/eval/local/tests/scaffold/test_attribution.py +48 -0
  451. package/eval/local/tests/scaffold/test_compare_baselines.py +999 -0
  452. package/eval/local/tests/scaffold/test_conftest_helpers.py +534 -0
  453. package/eval/local/tests/scaffold/test_evidence.py +15 -0
  454. package/eval/local/tests/scaffold/test_langsmith_conftest.py +244 -0
  455. package/eval/local/tests/scaffold/test_logging.py +243 -0
  456. package/eval/local/tests/scaffold/test_manifests.py +132 -0
  457. package/eval/local/tests/scaffold/test_profiles.py +866 -0
  458. package/eval/local/tests/scaffold/test_regression_check.py +19 -0
  459. package/eval/local/tests/scaffold/test_report_style_demo_charts.py +45 -0
  460. package/eval/local/tests/scaffold/test_sample_quality.py +211 -0
  461. package/eval/local/tests/scaffold/test_tasks.py +168 -0
  462. package/eval/local/tests/scaffold/test_treatments.py +244 -0
  463. package/eval/local/tests/scaffold/test_utils.py +139 -0
  464. package/eval/local/tests/tasks/test_tasks.py +445 -0
  465. package/eval/local/tests/tasks/test_validation_scripts.py +143 -0
  466. package/eval/local/treatments/comet/comet_full_039.yaml +141 -0
  467. package/eval/local/treatments/comet/comet_full_040_beta.yaml +140 -0
  468. package/eval/local/treatments/common/control.yaml +4 -0
  469. package/eval/pyproject.toml +41 -0
  470. package/eval/report-html-config.json +6 -0
  471. package/eval/scaffold/__init__.py +67 -0
  472. package/eval/scaffold/python/__init__.py +95 -0
  473. package/eval/scaffold/python/attribution.py +43 -0
  474. package/eval/scaffold/python/evidence.py +38 -0
  475. package/eval/scaffold/python/external_data_handler.py +18 -0
  476. package/eval/scaffold/python/generic_llm_judge.py +235 -0
  477. package/eval/scaffold/python/judge_config.py +168 -0
  478. package/eval/scaffold/python/llm_judge.py +191 -0
  479. package/eval/scaffold/python/logging.py +705 -0
  480. package/eval/scaffold/python/manifests.py +129 -0
  481. package/eval/scaffold/python/paper_charts.py +25 -0
  482. package/eval/scaffold/python/pass_at_k.py +107 -0
  483. package/eval/scaffold/python/paths.py +56 -0
  484. package/eval/scaffold/python/profiles.py +127 -0
  485. package/eval/scaffold/python/report_outputs.py +1391 -0
  486. package/eval/scaffold/python/sample_quality.py +339 -0
  487. package/eval/scaffold/python/schema.py +39 -0
  488. package/eval/scaffold/python/skill_parser.py +469 -0
  489. package/eval/scaffold/python/tasks.py +330 -0
  490. package/eval/scaffold/python/treatments.py +271 -0
  491. package/eval/scaffold/python/utils.py +366 -0
  492. package/eval/scaffold/python/validation/__init__.py +21 -0
  493. package/eval/scaffold/python/validation/authoring_rubric.py +326 -0
  494. package/eval/scaffold/python/validation/comet_workflow.py +187 -0
  495. package/eval/scaffold/python/validation/core.py +264 -0
  496. package/eval/scaffold/python/validation/dataset.py +337 -0
  497. package/eval/scaffold/python/validation/docker.py +106 -0
  498. package/eval/scaffold/python/validation/evaluator.py +549 -0
  499. package/eval/scaffold/python/validation/generic_rubric.py +169 -0
  500. package/eval/scaffold/python/validation/rubric.py +740 -0
  501. package/eval/scaffold/python/validation/runner.py +237 -0
  502. package/eval/scaffold/python/validation/scripts.py +58 -0
  503. package/eval/scaffold/python/validation/tracing.py +313 -0
  504. package/eval/scaffold/shell/common.sh +126 -0
  505. package/eval/scaffold/shell/docker.sh +482 -0
  506. package/eval/scaffold/shell/run-claude-loop.sh +181 -0
  507. package/eval/scaffold/shell/setup.sh +259 -0
  508. package/eval/simulator-instruction.md +9 -0
  509. package/package.json +24 -2
@@ -0,0 +1,1018 @@
1
+ """Compare comet baseline treatments across the rubric dimensions.
2
+
3
+ Reads experiment reports from ``local/logs/experiments/<id>/reports/*.json`` and
4
+ emits a markdown comparison report highlighting where the workflow (0.4.0-beta.1) baseline
5
+ scores below the 0.3.9 baseline.
6
+
7
+ Usage::
8
+
9
+ uv run python local/scripts/compare_baselines.py [--experiment <id>] [--report-config <path>]
10
+
11
+ If ``--experiment`` is omitted the most recent experiment directory is used.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import json
18
+ import re
19
+ import statistics
20
+ import sys
21
+ from collections import defaultdict
22
+ from dataclasses import dataclass
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ EVAL_ROOT = Path(__file__).resolve().parents[2]
27
+ if str(EVAL_ROOT) not in sys.path:
28
+ sys.path.insert(0, str(EVAL_ROOT))
29
+ if hasattr(sys.stdout, "reconfigure"):
30
+ sys.stdout.reconfigure(encoding="utf-8")
31
+ if hasattr(sys.stderr, "reconfigure"):
32
+ sys.stderr.reconfigure(encoding="utf-8")
33
+
34
+ from scaffold.python.paths import get_logs_dir
35
+ from scaffold.python.report_outputs import (
36
+ load_report_output_config,
37
+ write_report_outputs,
38
+ )
39
+ from scaffold.python.pass_at_k import pass_metrics_table
40
+ from scaffold.python.sample_quality import quality_from_report, sample_quality_dict
41
+ from scaffold.python.validation.rubric import RUBRIC_DIMENSIONS, _DIMENSION_WEIGHTS
42
+
43
+ RUBRIC_RE = re.compile(r"\[RUBRIC\]\s+(\S+):\s*([0-9.]+)")
44
+ RUBRIC_JUDGE_RE = re.compile(r"\[RUBRIC-JUDGE\]\s+(\S+):\s*([0-9.]+)")
45
+ RUBRIC_DERIVED_METRICS = ("weighted_score",)
46
+ RUBRIC_DIMENSION_GUIDE = {
47
+ "main_flow": "Completion of the expected Comet workflow phases.",
48
+ "gate_guard": "Use of required guard, state transition, and apply checkpoints.",
49
+ "skill_invocation": "Invocation of Comet, OpenSpec, and Superpowers dependency Skills.",
50
+ "spec_drift": "Whether build-time spec changes were reconciled before archive.",
51
+ "business_completion": "Business validator pass rate for the requested task behavior.",
52
+ "workflow_completion": "Workflow validator pass rate for Comet workflow artifacts.",
53
+ "efficiency": "Runtime effort score from turns, tool calls, and duration.",
54
+ "decision_point_compliance": "Whether blocking decision points were surfaced instead of auto-decided.",
55
+ "artifact_quality": "Whether generated proposal, design, task, and test artifacts are substantive.",
56
+ "recovery_resilience": "Whether workflow state was preserved and recovered across interruptions.",
57
+ }
58
+
59
+ # Treatments we compare. CONTROL is included for context but not used in the
60
+ # pass/fail decision.
61
+ TREATMENTS = ("CONTROL", "COMET_FULL_040_BETA", "COMET_FULL_039")
62
+ WORKFLOW = "COMET_FULL_040_BETA"
63
+ BASELINE = "COMET_FULL_039"
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class ReportPartitions:
68
+ raw: dict[str, list[dict]]
69
+ analysis: dict[str, list[dict]]
70
+ flagged: dict[str, list[dict]]
71
+ excluded: dict[str, list[dict]]
72
+
73
+
74
+ def _treatment_from_report_name(raw_name: str) -> str:
75
+ for treatment in sorted(TREATMENTS, key=len, reverse=True):
76
+ if re.search(rf"(?:^|-){re.escape(treatment)}(?:-r\d+)?$", raw_name):
77
+ return treatment
78
+ return raw_name
79
+
80
+
81
+ def _latest_experiment(logs: Path) -> Path | None:
82
+ exp_root = logs / "experiments"
83
+ if not exp_root.exists():
84
+ return None
85
+ dirs = sorted([d for d in exp_root.iterdir() if d.is_dir()], key=lambda p: p.stat().st_mtime)
86
+ return dirs[-1] if dirs else None
87
+
88
+
89
+ def _run_passed(report: dict) -> bool:
90
+ """A run 'passes' when its task-level validator has zero failures.
91
+
92
+ Rubric [RUBRIC]/[RUBRIC-JUDGE] checks are informational and never appear in
93
+ checks_failed, so this reflects whether the feature was actually completed.
94
+ """
95
+ return len(report.get("checks_failed", [])) == 0
96
+
97
+
98
+ def _pass_fail_by_treatment(by_treatment: dict[str, list[dict]]) -> dict[str, list[bool]]:
99
+ """Per-run pass/fail booleans keyed by treatment id."""
100
+ return {t: [_run_passed(r) for r in reps] for t, reps in by_treatment.items()}
101
+
102
+
103
+ def _rubric_dimension_passed(report: dict, dim: str) -> bool | None:
104
+ """Return True/False for a rubric dimension, or None when not applicable."""
105
+ scores = _scores_from_report(report)
106
+ if dim not in scores:
107
+ return None
108
+ return scores[dim] >= 1.0
109
+
110
+
111
+ def _pass_fail_by_rubric_dimension(
112
+ by_treatment: dict[str, list[dict]],
113
+ dim: str,
114
+ ) -> dict[str, list[bool]]:
115
+ """Per-run pass/fail booleans from a rubric dimension score."""
116
+ by_metric: dict[str, list[bool]] = {}
117
+ for treatment, reports in by_treatment.items():
118
+ values: list[bool] = []
119
+ for report in reports:
120
+ passed = _rubric_dimension_passed(report, dim)
121
+ if passed is not None:
122
+ values.append(passed)
123
+ by_metric[treatment] = values
124
+ return by_metric
125
+
126
+
127
+ def _load_reports(experiment_dir: Path) -> dict[str, list[dict]]:
128
+ """Group report JSON files by treatment name.
129
+
130
+ Report ``name`` fields carry the ``<task>-<TREATMENT>`` form (e.g.
131
+ ``comet-full-workflow-COMET_FULL_040_BETA``); we key by the canonical treatment id
132
+ (``COMET_FULL_040_BETA``) so the rest of the script can use the short names.
133
+ """
134
+ reports_dir = experiment_dir / "reports"
135
+ by_treatment: dict[str, list[dict]] = defaultdict(list)
136
+ if not reports_dir.exists():
137
+ return by_treatment
138
+ for rf in sorted(reports_dir.glob("*.json")):
139
+ try:
140
+ data = json.loads(rf.read_text(encoding="utf-8"))
141
+ except (json.JSONDecodeError, OSError):
142
+ continue
143
+ raw_name = data.get("name", "unknown")
144
+ # Match the canonical treatment id, allowing pytest repetition suffixes
145
+ # such as ``COMET_FULL_040_BETA-r1``.
146
+ name = _treatment_from_report_name(raw_name)
147
+ by_treatment[name].append(data)
148
+ return by_treatment
149
+
150
+
151
+ def _partition_reports(
152
+ by_treatment: dict[str, list[dict]],
153
+ experiment_dir: Path,
154
+ ) -> ReportPartitions:
155
+ analysis: dict[str, list[dict]] = defaultdict(list)
156
+ flagged: dict[str, list[dict]] = defaultdict(list)
157
+ excluded: dict[str, list[dict]] = defaultdict(list)
158
+
159
+ for treatment, reports in by_treatment.items():
160
+ for report in reports:
161
+ quality = quality_from_report(report, experiment_dir=experiment_dir)
162
+ if quality.status == "excluded" or not quality.include_in_analysis:
163
+ excluded[treatment].append(report)
164
+ else:
165
+ analysis[treatment].append(report)
166
+ if quality.status == "flagged":
167
+ flagged[treatment].append(report)
168
+
169
+ return ReportPartitions(
170
+ raw={key: list(value) for key, value in by_treatment.items()},
171
+ analysis={key: list(value) for key, value in analysis.items()},
172
+ flagged={key: list(value) for key, value in flagged.items()},
173
+ excluded={key: list(value) for key, value in excluded.items()},
174
+ )
175
+
176
+
177
+ def _quality_counts(partitions: ReportPartitions, treatment: str) -> tuple[int, int, int, int]:
178
+ raw = len(partitions.raw.get(treatment, []))
179
+ analysis_set = len(partitions.analysis.get(treatment, []))
180
+ flagged = len(partitions.flagged.get(treatment, []))
181
+ excluded = len(partitions.excluded.get(treatment, []))
182
+ return raw, analysis_set, flagged, excluded
183
+
184
+
185
+ def _report_ref(report: dict) -> str:
186
+ return (
187
+ (report.get("events_summary", {}).get("artifact_references") or {}).get("report")
188
+ or report.get("run_id")
189
+ or "none"
190
+ )
191
+
192
+
193
+ def _scores_from_report(report: dict) -> dict[str, float]:
194
+ """Parse rubric scores from a single report's passed checks."""
195
+ scores: dict[str, float] = {}
196
+ for check in report.get("checks_passed", []):
197
+ m = RUBRIC_RE.search(check)
198
+ if m:
199
+ try:
200
+ scores[m.group(1)] = float(m.group(2))
201
+ except ValueError:
202
+ continue
203
+ return scores
204
+
205
+
206
+ def _judge_scores_from_report(report: dict) -> dict[str, float]:
207
+ """Parse [RUBRIC-JUDGE] overlay scores (if LLM judge ran)."""
208
+ scores: dict[str, float] = {}
209
+ for check in report.get("checks_passed", []):
210
+ m = RUBRIC_JUDGE_RE.search(check)
211
+ if m:
212
+ try:
213
+ scores[m.group(1)] = float(m.group(2))
214
+ except ValueError:
215
+ continue
216
+ return scores
217
+
218
+
219
+ def _aggregate_judge(reports: list[dict]) -> dict[str, dict[str, float]]:
220
+ """Judge-overlay distribution stats (same shape as _aggregate)."""
221
+ per_dim: dict[str, list[float]] = defaultdict(list)
222
+ for rep in reports:
223
+ for dim, score in _judge_scores_from_report(rep).items():
224
+ per_dim[dim].append(score)
225
+ result: dict[str, dict[str, float]] = {}
226
+ for dim in ("artifact_quality", "spec_drift", "main_flow"):
227
+ vals = per_dim.get(dim, [])
228
+ n = len(vals)
229
+ if not vals:
230
+ result[dim] = {
231
+ "mean": 0.0,
232
+ "stdev": 0.0,
233
+ "min": 0.0,
234
+ "max": 0.0,
235
+ "pass_rate": 0.0,
236
+ "n": 0,
237
+ }
238
+ continue
239
+ result[dim] = {
240
+ "mean": statistics.fmean(vals),
241
+ "stdev": statistics.pstdev(vals) if len(vals) > 1 else 0.0,
242
+ "min": min(vals),
243
+ "max": max(vals),
244
+ "pass_rate": sum(1 for v in vals if v >= 0.5) / n,
245
+ "n": n,
246
+ }
247
+ return result
248
+
249
+
250
+ def _aggregate(reports: list[dict]) -> dict[str, dict[str, float]]:
251
+ """Per-dimension distribution stats across a treatment's reports.
252
+
253
+ Returns ``{dim: {mean, stdev, min, max, pass_rate, n}}`` where pass_rate is
254
+ the fraction of runs scoring >= 0.5 on that dimension (a "mostly passed"
255
+ threshold) and n is the run count with a score for that dimension.
256
+ """
257
+ per_dim: dict[str, list[float]] = defaultdict(list)
258
+ for rep in reports:
259
+ for dim, score in _scores_from_report(rep).items():
260
+ per_dim[dim].append(score)
261
+ result: dict[str, dict[str, float]] = {}
262
+ for dim in tuple(RUBRIC_DIMENSIONS) + RUBRIC_DERIVED_METRICS:
263
+ vals = per_dim.get(dim, [])
264
+ n = len(vals)
265
+ if not vals:
266
+ result[dim] = {
267
+ "mean": 0.0,
268
+ "stdev": 0.0,
269
+ "min": 0.0,
270
+ "max": 0.0,
271
+ "pass_rate": 0.0,
272
+ "n": 0,
273
+ }
274
+ continue
275
+ mean = statistics.fmean(vals)
276
+ stdev = statistics.pstdev(vals) if len(vals) > 1 else 0.0
277
+ result[dim] = {
278
+ "mean": mean,
279
+ "stdev": stdev,
280
+ "min": min(vals),
281
+ "max": max(vals),
282
+ "pass_rate": sum(1 for v in vals if v >= 0.5) / n,
283
+ "n": n,
284
+ }
285
+ return result
286
+
287
+
288
+ def _overall(agg: dict[str, dict[str, float]]) -> float:
289
+ """Use weighted_score if available, otherwise fall back to unweighted mean."""
290
+ # weighted_score is computed by the new binary-check + weighted aggregation
291
+ weighted = agg.get("weighted_score", {})
292
+ if weighted and weighted.get("n", 0) > 0:
293
+ return weighted["mean"]
294
+ # Fallback: mean of per-dimension means (legacy behavior)
295
+ vals = [agg.get(d, {}).get("mean", 0.0) for d in RUBRIC_DIMENSIONS]
296
+ return statistics.fmean(vals) if vals else 0.0
297
+
298
+
299
+ def _fmt_dim(stats: dict[str, float] | None, with_dist: bool = False) -> str:
300
+ """Format a dimension cell. With distribution: 'mean±std (pass%)'."""
301
+ if not stats or stats.get("n", 0) == 0:
302
+ return "/"
303
+ mean = stats["mean"]
304
+ if not with_dist or stats["n"] < 2:
305
+ return f"{mean:.2f}"
306
+ return f"{mean:.2f}±{stats['stdev']:.2f} ({stats['pass_rate'] * 100:.0f}%)"
307
+
308
+
309
+ def _sum_metric(reports: list[dict], key: str) -> int | float | None:
310
+ values = [
311
+ rep.get("events_summary", {}).get(key)
312
+ for rep in reports
313
+ if rep.get("events_summary", {}).get(key) is not None
314
+ ]
315
+ return sum(values) if values else None
316
+
317
+
318
+ def _fmt_tokens(value: int | float | None) -> str:
319
+ return "N/A" if value is None else f"{value:,.0f}"
320
+
321
+
322
+ def _fmt_cost(value: int | float | None) -> str:
323
+ return "N/A" if value is None else f"${value:.4f}"
324
+
325
+
326
+ def _fmt_seconds(value: int | float | None) -> str:
327
+ return "N/A" if value is None else f"{value:,.0f}s"
328
+
329
+
330
+ def _fmt_average(value: int | float | None, decimals: int = 1) -> str:
331
+ return "N/A" if value is None else f"{value:,.{decimals}f}"
332
+
333
+
334
+ def _quality_run_rows(
335
+ reports: list[dict],
336
+ treatment: str,
337
+ experiment_dir: Path,
338
+ *,
339
+ include_column: bool,
340
+ ) -> list[str]:
341
+ rows: list[str] = []
342
+ for report in reports:
343
+ quality = sample_quality_dict(report, experiment_dir=experiment_dir)
344
+ task = _task_name(report, treatment)
345
+ evidence = _quality_evidence_summary(quality.get("evidence", []))
346
+ include_text = (
347
+ f" | {'yes' if quality.get('include_in_analysis') else 'no'}" if include_column else ""
348
+ )
349
+ rows.append(
350
+ f"| `{report.get('run_id') or 'n/a'}` | {task} | {treatment} | "
351
+ f"{quality.get('reason_code')} | {evidence}{include_text} | {_report_ref(report)} |"
352
+ )
353
+ return rows
354
+
355
+
356
+ def _quality_evidence_summary(evidence_items: list[Any]) -> str:
357
+ raw = "\n".join(str(item) for item in evidence_items if item)
358
+ if not raw:
359
+ return "none"
360
+
361
+ loop_lines = re.findall(r"^\s*\[loop\]\s+(.+)$", raw, flags=re.MULTILINE)
362
+ if loop_lines:
363
+ finished = re.search(r"\[loop\]\s+finished after\s+(\d+)\s+turns?", raw)
364
+ turn_numbers = [
365
+ int(match.group(1))
366
+ for match in re.finditer(r"\[loop\]\s+turn\s+(\d+)/\d+", raw)
367
+ ]
368
+ turns = int(finished.group(1)) if finished else (max(turn_numbers) if turn_numbers else 0)
369
+ replies = len(re.findall(r"\[loop\]\s+simulated reply", raw))
370
+ turn_text = f"{turns} {'turn' if turns == 1 else 'turns'}"
371
+ reply_text = f"{replies} simulated decision {'reply' if replies == 1 else 'replies'}"
372
+ return f"loop trace: {turn_text}; {reply_text}; see report JSON for full evidence"
373
+
374
+ summary = "; ".join(_collapse_table_cell_text(str(item)) for item in evidence_items[:2] if item)
375
+ if len(summary) > 220:
376
+ summary = f"{summary[:217].rstrip()}..."
377
+ return summary or "none"
378
+
379
+
380
+ def _collapse_table_cell_text(value: str) -> str:
381
+ return re.sub(r"\s+", " ", value.replace("|", "/")).strip()
382
+
383
+
384
+ def _overall_by_reports(reports: list[dict]) -> float | None:
385
+ if not reports:
386
+ return None
387
+ return _overall(_aggregate(reports))
388
+
389
+
390
+ def _fmt_optional_score(value: float | None) -> str:
391
+ return "—" if value is None else f"{value:.2f}"
392
+
393
+
394
+ def _source_summary(report: dict, quality: dict[str, Any]) -> str:
395
+ events = report.get("events_summary", {})
396
+ run_id = report.get("run_id") or "n/a"
397
+ profile = events.get("profile") or "n/a"
398
+ sources = events.get("skill_sources") or []
399
+ source_text = (
400
+ ", ".join(
401
+ (
402
+ f"{item.get('name', 'skill')}@{item.get('hash', item.get('path', 'unknown'))}"
403
+ if isinstance(item, dict)
404
+ else str(item)
405
+ )
406
+ for item in sources
407
+ )
408
+ or "none"
409
+ )
410
+ manifest = events.get("eval_manifest") or "none"
411
+ report_ref = (events.get("artifact_references") or {}).get("report", "none")
412
+ return (
413
+ f"| `{run_id}` | {quality.get('status')} | {quality.get('reason_code')} | "
414
+ f"{profile} | {source_text} | {manifest} | {report_ref} |"
415
+ )
416
+
417
+
418
+ def _task_name(report: dict, treatment: str) -> str:
419
+ raw_name = str(report.get("name") or "unknown")
420
+ match = re.match(rf"^(.*)-{re.escape(treatment)}(?:-r\d+)?$", raw_name)
421
+ if match:
422
+ return match.group(1)
423
+ return raw_name
424
+
425
+
426
+ def _task_outcomes(by_treatment: dict[str, list[dict]]) -> dict[str, dict[str, bool]]:
427
+ outcomes: dict[str, dict[str, bool]] = {}
428
+ for treatment in TREATMENTS:
429
+ for report in by_treatment.get(treatment, []):
430
+ task = _task_name(report, treatment)
431
+ outcomes.setdefault(task, {})[treatment] = _run_passed(report)
432
+ return outcomes
433
+
434
+
435
+ # --- Attribution (improvement 2) -------------------------------------------
436
+ # Classify a failed check into one of three buckets:
437
+ # business - the requested business behavior did not pass validation
438
+ # workflow - the skill guidance is wrong/missing (skill not invoked, guard not used)
439
+ # task - the task/validator itself is ambiguous or buggy
440
+ # uncategorized - valid completed failure that does not match a specific bucket
441
+ _ATTRIBUTION_RULES = [
442
+ # (regex on failed-check text, bucket, reason)
443
+ (
444
+ re.compile(r"business validator failed|business[_ -]?completion", re.I),
445
+ "business",
446
+ "business implementation did not pass validation",
447
+ ),
448
+ (
449
+ re.compile(r"workflow validator failed|workflow[_ -]?completion", re.I),
450
+ "workflow",
451
+ "workflow validation did not pass",
452
+ ),
453
+ (
454
+ re.compile(r"skill.*(not invoke|not found)|did not invoke", re.I),
455
+ "workflow",
456
+ "skill was not invoked — workflow guidance failed to trigger the skill",
457
+ ),
458
+ (
459
+ re.compile(r"guard|state|apply|transition", re.I),
460
+ "workflow",
461
+ "guard/state machinery not exercised — workflow did not drive phase transitions",
462
+ ),
463
+ (
464
+ re.compile(r"--sentences|sentence_feature|wordcount", re.I),
465
+ "business",
466
+ "feature implementation incomplete",
467
+ ),
468
+ (
469
+ re.compile(r"openspec_artifacts|not found in.*archive|directory not found", re.I),
470
+ "task",
471
+ "artifact path/layout mismatch — likely task/validator path assumption",
472
+ ),
473
+ (
474
+ re.compile(r"comet_state|\.comet\.yaml", re.I),
475
+ "workflow",
476
+ "comet state file missing — workflow did not initialise state machine",
477
+ ),
478
+ (
479
+ re.compile(r"tests_exist|no.*tests", re.I),
480
+ "workflow",
481
+ "expected tests were not written",
482
+ ),
483
+ ]
484
+
485
+
486
+ def _attribute_failure(check_text: str) -> tuple[str, str]:
487
+ """Return (bucket, reason) for a failed check."""
488
+ for rx, bucket, reason in _ATTRIBUTION_RULES:
489
+ if rx.search(check_text):
490
+ return bucket, reason
491
+ return "uncategorized", "uncategorized valid failure"
492
+
493
+
494
+ def _attributions(reports: list[dict]) -> dict[str, list[str]]:
495
+ """Bucket all failed checks across a treatment's runs by attribution."""
496
+ buckets: dict[str, list[str]] = defaultdict(list)
497
+ for rep in reports:
498
+ structured = rep.get("events_summary", {}).get("failure_attribution") or []
499
+ if structured:
500
+ for item in structured:
501
+ bucket = item.get("bucket", "uncategorized")
502
+ buckets[bucket].append(
503
+ f"{item.get('check', '')} -> [{bucket}] {item.get('reason', '')}"
504
+ )
505
+ continue
506
+ for fail in rep.get("checks_failed", []):
507
+ # Skip rubric informational checks (they never fail, but be safe)
508
+ if "[RUBRIC]" in fail:
509
+ continue
510
+ bucket, reason = _attribute_failure(fail)
511
+ buckets[bucket].append(f"{fail} → [{bucket}] {reason}")
512
+ return buckets
513
+
514
+
515
+ def _summarize_attribution_items(items: list[str]) -> list[str]:
516
+ """Collapse repeated failure causes while preserving first-seen order."""
517
+ counts: dict[str, int] = {}
518
+ for item in items:
519
+ counts[item] = counts.get(item, 0) + 1
520
+ summarized = []
521
+ for item, count in counts.items():
522
+ summarized.append(f"x{count} {item}" if count > 1 else item)
523
+ return summarized
524
+
525
+
526
+ def build_report(experiment_dir: Path) -> str:
527
+ raw_by_treatment = _load_reports(experiment_dir)
528
+ partitions = _partition_reports(raw_by_treatment, experiment_dir)
529
+ by_treatment = partitions.analysis
530
+ aggregated = {t: _aggregate(reps) for t, reps in by_treatment.items() if reps}
531
+
532
+ lines: list[str] = []
533
+ lines.append("# Comet Baseline Comparison Report")
534
+ lines.append("")
535
+ lines.append(f"- Experiment: `{experiment_dir.name}`")
536
+ lines.append(f"- Treatments with data: {', '.join(sorted(raw_by_treatment)) or 'none'}")
537
+ lines.append("")
538
+
539
+ if not raw_by_treatment:
540
+ lines.append("No report data found. Run the eval suite first.")
541
+ return "\n".join(lines)
542
+
543
+ lines.append("## Metric guide")
544
+ lines.append("")
545
+ lines.append("| Metric | Meaning | Source | Report section |")
546
+ lines.append("|--------|---------|--------|----------------|")
547
+ lines.append("| `raw runs` | All discovered report JSON files before quality filtering. | report files | Data quality summary |")
548
+ lines.append("| `analysis set` | Runs included in comparison metrics after excluding hard infrastructure noise. | sample_quality.include_in_analysis | Data quality summary / Run counts |")
549
+ lines.append("| `flagged` | Completed runs kept in analysis but marked as suspicious, usually harness/task/observability risk. | sample_quality.status | Data quality summary / Flagged runs |")
550
+ lines.append("| `excluded` | Runs removed from headline metrics, typically API, quota, auth, network, container, or runner failures before a complete result. | sample_quality.status | Data quality summary / Excluded runs |")
551
+ lines.append("| `pass@k` | Probability that at least one of k attempts succeeds; capability ceiling. | pass/fail booleans | pass@k / pass^k |")
552
+ lines.append("| `pass^k` | Probability that all k attempts succeed; reliability floor. | pass/fail booleans | pass@k / pass^k |")
553
+ lines.append("| `overall` | Run passes when task-level `checks_failed == []`. | checks_failed | pass@k / Task outcomes |")
554
+ lines.append("| `business_completion` | Business validator pass rate; CONTROL is evaluated on this without requiring Comet workflow artifacts. | `[RUBRIC] business_completion` | Rubric dimensions / pass@k |")
555
+ lines.append("| `workflow_completion` | Comet workflow validator pass rate; `/` means not applicable for CONTROL. | `[RUBRIC] workflow_completion` | Rubric dimensions / pass@k |")
556
+ lines.append("| `weighted_score` | Weighted average across applicable rubric dimensions; N/A dimensions are skipped. | `[RUBRIC] weighted_score` | Rubric dimensions / Overall |")
557
+ lines.append("| `tokens` / `cost` | Total model token and USD cost telemetry for included runs. | events_summary | Spend summary |")
558
+ lines.append("| `turns` / `duration` / `tool calls` | Runtime effort telemetry for included runs; also feeds the `efficiency` rubric. | events_summary | Runtime summary / Rubric dimensions |")
559
+ lines.append("| `run-level failed checks` | Buckets sample-level `checks_failed` entries into harness, business, workflow, task, or uncategorized causes; this is not the same as the task outcome matrix. | checks_failed / events_summary.failure_attribution | Run-level failed checks |")
560
+ lines.append("| `source evidence` | Run id, quality status, profile, Skill source hashes, eval manifest, and raw report reference. | events_summary / sample_quality | Source evidence |")
561
+ lines.append("")
562
+
563
+ lines.append("## Data quality summary")
564
+ lines.append("")
565
+ lines.append("| Treatment | Raw runs | Analysis set | Flagged | Excluded |")
566
+ lines.append("|-----------|----------|--------------|---------|----------|")
567
+ for t in TREATMENTS:
568
+ raw, analysis_set, flagged, excluded = _quality_counts(partitions, t)
569
+ if raw == 0:
570
+ continue
571
+ lines.append(f"| {t} | {raw} | {analysis_set} | {flagged} | {excluded} |")
572
+ lines.append("")
573
+
574
+ lines.append("## Run counts")
575
+ lines.append("")
576
+ lines.append("_Analysis set only; excluded hard-noise runs are omitted._")
577
+ lines.append("")
578
+ lines.append("| Treatment | Runs |")
579
+ lines.append("|-----------|------|")
580
+ for t in TREATMENTS:
581
+ if t in by_treatment:
582
+ lines.append(f"| {t} | {len(by_treatment[t])} |")
583
+ lines.append("")
584
+
585
+ has_dist = any(len(by_treatment.get(t, [])) >= 2 for t in TREATMENTS)
586
+
587
+ pass_fail_metrics = {
588
+ "overall": _pass_fail_by_treatment(by_treatment),
589
+ "business": _pass_fail_by_rubric_dimension(by_treatment, "business_completion"),
590
+ "workflow": _pass_fail_by_rubric_dimension(by_treatment, "workflow_completion"),
591
+ }
592
+ max_n = max(
593
+ (len(v) for metric_values in pass_fail_metrics.values() for v in metric_values.values()),
594
+ default=0,
595
+ )
596
+ ks = [k for k in (1, 2, 3, 5) if k <= max_n] or [1]
597
+
598
+ lines.append("## pass@k / pass^k — capability vs reliability")
599
+ lines.append("")
600
+ lines.append("- **pass@k**: probability ≥1 of k attempts succeeds (capability ceiling)")
601
+ lines.append("- **pass^k**: probability all k attempts succeed (reliability floor)")
602
+ lines.append("- **overall**: run passes when `checks_failed == []`.")
603
+ lines.append("- **business**: run passes when `business_completion == 1.00`.")
604
+ lines.append("- **workflow**: run passes when `workflow_completion == 1.00`; `/` means not applicable.")
605
+ lines.append(
606
+ "- The gap (pass@k − pass^k) measures instability: high ceiling, low floor = unreliable."
607
+ )
608
+ lines.append("")
609
+ header_cells = ["Metric", "Treatment"]
610
+ for k in ks:
611
+ header_cells.append(f"pass@{k}")
612
+ for k in ks:
613
+ header_cells.append(f"pass^{k}")
614
+ header_cells.append("pass/fail")
615
+ lines.append("| " + " | ".join(header_cells) + " |")
616
+ lines.append("|" + "|".join(["---"] * len(header_cells)) + "|")
617
+ for metric_name, metric_values in pass_fail_metrics.items():
618
+ ptable = pass_metrics_table(metric_values, ks=ks)
619
+ for t in TREATMENTS:
620
+ values = metric_values.get(t, [])
621
+ if not values:
622
+ cells = [metric_name, t] + ["/"] * (len(ks) * 2 + 1)
623
+ lines.append("| " + " | ".join(cells) + " |")
624
+ continue
625
+ cells = [metric_name, t]
626
+ for k in ks:
627
+ cells.append(f"{ptable[t][k]['pass_at_k']:.2f}")
628
+ for k in ks:
629
+ cells.append(f"{ptable[t][k]['pass_pow_k']:.0f}")
630
+ c = ptable[t][ks[0]]["c"]
631
+ n = ptable[t][ks[0]]["n"]
632
+ cells.append(f"{c}/{n}")
633
+ lines.append("| " + " | ".join(cells) + " |")
634
+ lines.append("")
635
+ if max_n < 2:
636
+ lines.append(
637
+ f"_Only {max_n} run per treatment — pass@k/pass^k for k>1 need ≥2 runs "
638
+ "to be meaningful. Use ``--count 5``._"
639
+ )
640
+ lines.append("")
641
+
642
+ task_outcomes = _task_outcomes(by_treatment)
643
+ if task_outcomes:
644
+ lines.append("## Task outcomes")
645
+ lines.append("")
646
+ lines.append("| Task | " + " | ".join(TREATMENTS) + " |")
647
+ lines.append("|------|" + "|".join(["------"] * len(TREATMENTS)) + "|")
648
+ for task in sorted(task_outcomes):
649
+ cells = [task]
650
+ for treatment in TREATMENTS:
651
+ if treatment not in task_outcomes[task]:
652
+ cells.append("—")
653
+ else:
654
+ cells.append("PASS" if task_outcomes[task][treatment] else "FAIL")
655
+ lines.append("| " + " | ".join(cells) + " |")
656
+ lines.append("")
657
+
658
+ lines.append("## Spend summary")
659
+ lines.append("")
660
+ lines.append("| Treatment | Runs | Tokens | Cost | Avg Tokens/Run | Avg Cost/Run |")
661
+ lines.append("|-----------|------|--------|------|----------------|--------------|")
662
+ for t in TREATMENTS:
663
+ reps = by_treatment.get(t, [])
664
+ if not reps:
665
+ continue
666
+ total_tokens = _sum_metric(reps, "total_tokens")
667
+ total_cost = _sum_metric(reps, "total_cost_usd")
668
+ avg_tokens = (total_tokens / len(reps)) if total_tokens is not None else None
669
+ avg_cost = (total_cost / len(reps)) if total_cost is not None else None
670
+ lines.append(
671
+ f"| {t} | {len(reps)} | {_fmt_tokens(total_tokens)} | "
672
+ f"{_fmt_cost(total_cost)} | {_fmt_tokens(avg_tokens)} | "
673
+ f"{_fmt_cost(avg_cost)} |"
674
+ )
675
+ lines.append("")
676
+
677
+ lines.append("## Runtime summary")
678
+ lines.append("")
679
+ lines.append("| Treatment | Runs | Turns | Duration | Tool Calls | Avg Turns/Run | Avg Duration/Run | Avg Tool Calls/Run |")
680
+ lines.append("|-----------|------|-------|----------|------------|---------------|------------------|--------------------|")
681
+ for t in TREATMENTS:
682
+ reps = by_treatment.get(t, [])
683
+ if not reps:
684
+ continue
685
+ total_turns = _sum_metric(reps, "num_turns")
686
+ total_duration = _sum_metric(reps, "duration_seconds")
687
+ total_tool_calls = _sum_metric(reps, "tool_calls")
688
+ avg_turns = (total_turns / len(reps)) if total_turns is not None else None
689
+ avg_duration = (total_duration / len(reps)) if total_duration is not None else None
690
+ avg_tool_calls = (
691
+ (total_tool_calls / len(reps)) if total_tool_calls is not None else None
692
+ )
693
+ lines.append(
694
+ f"| {t} | {len(reps)} | {_fmt_tokens(total_turns)} | "
695
+ f"{_fmt_seconds(total_duration)} | {_fmt_tokens(total_tool_calls)} | "
696
+ f"{_fmt_average(avg_turns)} | {_fmt_seconds(avg_duration)} | "
697
+ f"{_fmt_average(avg_tool_calls)} |"
698
+ )
699
+ lines.append("")
700
+
701
+ lines.append("## Source evidence")
702
+ lines.append("")
703
+ lines.append("Use this section to trace each aggregate metric back to the raw run artifacts.")
704
+ lines.append("")
705
+ lines.append("- `Run` is the run id or fallback report id.")
706
+ lines.append("- `Quality` is the sample-quality status used by the analysis-set filter.")
707
+ lines.append("- `Reason` explains why the run is included, flagged, or excluded.")
708
+ lines.append("- `Profile` is the eval rubric/profile that scored the run.")
709
+ lines.append("- `Skill sources` records installed Skill identity or hash evidence when the run provides it.")
710
+ lines.append("- `Eval manifest` is the task manifest that defined the evaluated scenario.")
711
+ lines.append("- `Report` points to the raw per-run report JSON for deeper inspection.")
712
+ lines.append("")
713
+ lines.append("| Run | Quality | Reason | Profile | Skill sources | Eval manifest | Report |")
714
+ lines.append("|-----|---------|--------|---------|---------------|---------------|--------|")
715
+ for treatment in TREATMENTS:
716
+ for rep in raw_by_treatment.get(treatment, []):
717
+ quality = sample_quality_dict(rep, experiment_dir=experiment_dir)
718
+ lines.append(_source_summary(rep, quality))
719
+ lines.append("")
720
+
721
+ label = "(mean±stdev, pass-rate across runs; 0.00–1.00)" if has_dist else "(mean, 0.00–1.00)"
722
+ lines.append(f"## Rubric dimensions {label}")
723
+ lines.append("")
724
+ lines.append(
725
+ "_Scores are binary pass-rates (0.0-1.0). Overall uses the "
726
+ "validator-emitted weighted_score when available (see weights below)._"
727
+ )
728
+ lines.append("")
729
+ lines.append("### Dimension guide")
730
+ lines.append("")
731
+ lines.append("| Dimension | Meaning |")
732
+ lines.append("|-----------|---------|")
733
+ for dim in RUBRIC_DIMENSIONS:
734
+ lines.append(f"| {dim} | {RUBRIC_DIMENSION_GUIDE[dim]} |")
735
+ lines.append("")
736
+
737
+ header = "| Dimension | " + " | ".join(TREATMENTS) + " | Δ (workflow−baseline) |"
738
+ sep = "|-----------|" + "|".join(["------"] * len(TREATMENTS)) + "|------|"
739
+ lines.append(header)
740
+ lines.append(sep)
741
+
742
+ regressions: list[tuple[str, float, float]] = []
743
+ for dim in RUBRIC_DIMENSIONS:
744
+ row = [f"{dim}"]
745
+ wf_stats = aggregated.get(WORKFLOW, {}).get(dim)
746
+ bl_stats = aggregated.get(BASELINE, {}).get(dim)
747
+ for t in TREATMENTS:
748
+ row.append(_fmt_dim(aggregated.get(t, {}).get(dim), with_dist=has_dist))
749
+ if wf_stats and bl_stats:
750
+ delta = wf_stats["mean"] - bl_stats["mean"]
751
+ row.append(f"{delta:+.2f}" + (" ⚠️" if delta < -0.05 else ""))
752
+ if delta < -0.05:
753
+ regressions.append((dim, wf_stats["mean"], bl_stats["mean"]))
754
+ else:
755
+ row.append("—")
756
+ lines.append("| " + " | ".join(row) + " |")
757
+ lines.append("")
758
+
759
+ lines.append(
760
+ "| **Overall** | "
761
+ + " | ".join(
762
+ (f"{_overall(aggregated[t]):.2f}" if t in aggregated else "—") for t in TREATMENTS
763
+ )
764
+ + " | "
765
+ + (
766
+ f"{_overall(aggregated[WORKFLOW]) - _overall(aggregated[BASELINE]):+.2f}"
767
+ if WORKFLOW in aggregated and BASELINE in aggregated
768
+ else "—"
769
+ )
770
+ + " |"
771
+ )
772
+ lines.append("")
773
+
774
+ lines.append("### Dimension weights")
775
+ lines.append("")
776
+ lines.append("| Dimension | Weight |")
777
+ lines.append("|-----------|--------|")
778
+ for dim in RUBRIC_DIMENSIONS:
779
+ weight = _DIMENSION_WEIGHTS.get(dim, 1.0)
780
+ lines.append(f"| {dim} | {weight:.1f} |")
781
+ lines.append("")
782
+
783
+ excluded_total = sum(len(items) for items in partitions.excluded.values())
784
+ if excluded_total:
785
+ lines.append("## Excluded runs")
786
+ lines.append("")
787
+ lines.append("| Run | Task | Treatment | Reason | Evidence | Report |")
788
+ lines.append("|-----|------|-----------|--------|----------|--------|")
789
+ for treatment in TREATMENTS:
790
+ lines.extend(
791
+ _quality_run_rows(
792
+ partitions.excluded.get(treatment, []),
793
+ treatment,
794
+ experiment_dir,
795
+ include_column=False,
796
+ )
797
+ )
798
+ lines.append("")
799
+
800
+ flagged_total = sum(len(items) for items in partitions.flagged.values())
801
+ if flagged_total:
802
+ lines.append("## Flagged runs")
803
+ lines.append("")
804
+ lines.append("| Run | Task | Treatment | Reason | Evidence | Included? | Report |")
805
+ lines.append("|-----|------|-----------|--------|----------|-----------|--------|")
806
+ for treatment in TREATMENTS:
807
+ lines.extend(
808
+ _quality_run_rows(
809
+ partitions.flagged.get(treatment, []),
810
+ treatment,
811
+ experiment_dir,
812
+ include_column=True,
813
+ )
814
+ )
815
+ lines.append("")
816
+
817
+ lines.append("## Raw vs analysis sensitivity")
818
+ lines.append("")
819
+ lines.append("| Metric | Raw | Analysis | Delta |")
820
+ lines.append("|--------|-----|----------|-------|")
821
+ for treatment in TREATMENTS:
822
+ raw_value = _overall_by_reports(raw_by_treatment.get(treatment, []))
823
+ analysis_value = _overall_by_reports(by_treatment.get(treatment, []))
824
+ if raw_value is None and analysis_value is None:
825
+ continue
826
+ delta = (
827
+ "—"
828
+ if raw_value is None or analysis_value is None
829
+ else f"{analysis_value - raw_value:+.2f}"
830
+ )
831
+ lines.append(
832
+ f"| {treatment} overall | {_fmt_optional_score(raw_value)} | "
833
+ f"{_fmt_optional_score(analysis_value)} | {delta} |"
834
+ )
835
+ lines.append("")
836
+
837
+ lines.append("## Run-level failed checks")
838
+ lines.append("")
839
+ lines.append(
840
+ "These are sample-level `checks_failed` entries. They can coexist with "
841
+ "`workflow_completion == 1.00`, `pass@k == 1.00`, or a passing task outcome "
842
+ "because they describe stricter run-contract failures rather than the "
843
+ "workflow artifact completion score or the task outcome matrix."
844
+ )
845
+ lines.append("")
846
+ lines.append(
847
+ "Each failed baseline check is bucketed as **harness** (runner/trigger issue), "
848
+ "**business** (requested behavior issue), **workflow** (skill guidance or workflow "
849
+ "artifact issue), **task** (task/validator issue), or **uncategorized** "
850
+ "(valid completed failure that needs inspection)."
851
+ )
852
+ lines.append("")
853
+ any_failures = False
854
+ for t in TREATMENTS:
855
+ attr = _attributions(raw_by_treatment.get(t, []))
856
+ total = sum(len(v) for v in attr.values())
857
+ if total == 0:
858
+ continue
859
+ any_failures = True
860
+ lines.append(f"### {t} ({total} failure(s))")
861
+ lines.append("")
862
+ for bucket in ("harness", "business", "workflow", "task", "uncategorized"):
863
+ items = attr.get(bucket, [])
864
+ if not items:
865
+ continue
866
+ lines.append(f"- **{bucket}** ({len(items)}):")
867
+ for item in _summarize_attribution_items(items):
868
+ lines.append(f" - {item}")
869
+ lines.append("")
870
+ if not any_failures:
871
+ lines.append("_No baseline check failures across treatments._")
872
+ lines.append("")
873
+
874
+ lines.append("## Verdict")
875
+ lines.append("")
876
+ key_treatments = (WORKFLOW, BASELINE)
877
+ missing_clean = [t for t in key_treatments if not by_treatment.get(t)]
878
+ noisy_majority = []
879
+ for t in key_treatments:
880
+ raw, _included, _flagged, excluded = _quality_counts(partitions, t)
881
+ if raw > 0 and excluded / raw > 0.5:
882
+ noisy_majority.append(t)
883
+
884
+ if missing_clean:
885
+ lines.append(
886
+ "⚠️ **Insufficient clean data**: analysis set has no included runs for "
887
+ + ", ".join(f"`{t}`" for t in missing_clean)
888
+ + ". Rerun the affected task/treatment pairs or inspect the excluded runs above."
889
+ )
890
+ elif noisy_majority:
891
+ lines.append(
892
+ "⚠️ **Inconclusive due to data quality**: more than half of the raw runs were "
893
+ "excluded for "
894
+ + ", ".join(f"`{t}`" for t in noisy_majority)
895
+ + ". The analysis metrics are shown, but the A/B verdict should not be treated as final."
896
+ )
897
+ elif WORKFLOW not in aggregated or BASELINE not in aggregated:
898
+ lines.append(f"Insufficient data: need both `{WORKFLOW}` and `{BASELINE}` runs.")
899
+ elif regressions:
900
+ lines.append(
901
+ f"❌ **Workflow regresses on {len(regressions)} dimension(s) vs 0.3.9 baseline:**"
902
+ )
903
+ lines.append("")
904
+ for dim, wf, bl in regressions:
905
+ lines.append(f"- **{dim}**: workflow {wf:.2f} < baseline {bl:.2f} (Δ {wf - bl:+.2f})")
906
+ lines.append("")
907
+ lines.append(
908
+ "See the run-level failed checks section above and the events/raw logs "
909
+ "for root-cause analysis."
910
+ )
911
+ else:
912
+ wf_overall = _overall(aggregated[WORKFLOW])
913
+ bl_overall = _overall(aggregated[BASELINE])
914
+ if wf_overall >= bl_overall:
915
+ lines.append(
916
+ f"✅ **Workflow is stable**: overall {wf_overall:.2f} ≥ "
917
+ f"baseline {bl_overall:.2f}, no dimension regresses beyond "
918
+ "the 0.05 tolerance."
919
+ )
920
+ else:
921
+ lines.append(
922
+ f"⚠️ **Workflow overall lower** ({wf_overall:.2f} < {bl_overall:.2f}) "
923
+ f"but no single dimension regresses beyond tolerance."
924
+ )
925
+
926
+ if (
927
+ not missing_clean
928
+ and not noisy_majority
929
+ and WORKFLOW in aggregated
930
+ and BASELINE in aggregated
931
+ ):
932
+ raw, included, flagged, excluded = _quality_counts(partitions, WORKFLOW)
933
+ lines.append("")
934
+ lines.append(
935
+ f"_Verdict uses analysis set: `{WORKFLOW}` included {included}/{raw} raw run(s), "
936
+ f"flagged {flagged}, excluded {excluded}._"
937
+ )
938
+
939
+ if has_dist:
940
+ lines.append("")
941
+ lines.append("_Distribution stats computed from ≥2 runs per treatment._")
942
+
943
+ judge_aggregated = {t: _aggregate_judge(reps) for t, reps in by_treatment.items() if reps}
944
+ has_judge = any(
945
+ stats.get(d, {}).get("n", 0) > 0
946
+ for stats in judge_aggregated.values()
947
+ for d in ("artifact_quality", "spec_drift", "main_flow")
948
+ )
949
+ if has_judge:
950
+ lines.append("")
951
+ lines.append("## LLM-judge overlay (rule vs judge)")
952
+ lines.append("")
953
+ lines.append(
954
+ "Independent LLM re-scored the three qualitative dimensions by reading "
955
+ "the actual artifacts. Large rule-vs-judge gaps flag heuristic weaknesses."
956
+ )
957
+ lines.append("")
958
+ lines.append("| Dimension | Treatment | Rule | Judge | Gap |")
959
+ lines.append("|-----------|-----------|------|-------|-----|")
960
+ for dim in ("artifact_quality", "spec_drift", "main_flow"):
961
+ for t in TREATMENTS:
962
+ rule_stats = aggregated.get(t, {}).get(dim, {})
963
+ judge_stats = judge_aggregated.get(t, {}).get(dim, {})
964
+ rule = rule_stats.get("mean") if rule_stats.get("n") else None
965
+ judge = judge_stats.get("mean") if judge_stats.get("n") else None
966
+ if rule is None and judge is None:
967
+ continue
968
+ gap = f"{judge - rule:+.2f}" if (rule is not None and judge is not None) else "—"
969
+ lines.append(
970
+ f"| {dim} | {t} | {_fmt_optional_score(rule)} | "
971
+ f"{_fmt_optional_score(judge)} | {gap} |"
972
+ )
973
+ lines.append("")
974
+
975
+ return "\n".join(lines)
976
+
977
+
978
+ def main(argv: list[str] | None = None) -> int:
979
+ parser = argparse.ArgumentParser(description=__doc__)
980
+ parser.add_argument("--experiment", default=None, help="Experiment id (defaults to latest)")
981
+ parser.add_argument(
982
+ "--out",
983
+ default=None,
984
+ help="Output path (defaults to <experiment>/comparison_report.md)",
985
+ )
986
+ parser.add_argument("--report-config", default=None, help="JSON/YAML config for report outputs")
987
+ args = parser.parse_args(argv)
988
+
989
+ logs = get_logs_dir()
990
+ if args.experiment:
991
+ experiment_dir = logs / "experiments" / args.experiment
992
+ if not experiment_dir.exists():
993
+ print(f"Experiment not found: {experiment_dir}", file=sys.stderr)
994
+ return 1
995
+ else:
996
+ experiment_dir = _latest_experiment(logs)
997
+ if experiment_dir is None:
998
+ print("No experiments found under logs/experiments/", file=sys.stderr)
999
+ return 1
1000
+
1001
+ report = build_report(experiment_dir)
1002
+ out_path = Path(args.out) if args.out else experiment_dir / "comparison_report.md"
1003
+ outputs = write_report_outputs(
1004
+ report,
1005
+ out_path,
1006
+ load_report_output_config(args.report_config),
1007
+ title="Comet Baseline Comparison Report",
1008
+ )
1009
+ print(report)
1010
+ if outputs:
1011
+ print("\nWrote: " + ", ".join(str(path) for path in outputs.values()))
1012
+ else:
1013
+ print("\nReport outputs disabled by report config")
1014
+ return 0
1015
+
1016
+
1017
+ if __name__ == "__main__":
1018
+ raise SystemExit(main())