@rpamis/comet 0.4.0-beta.3 → 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.
- package/README.md +10 -0
- package/assets/manifest.json +2 -1
- package/assets/skills/comet/SKILL.md +25 -1
- package/assets/skills/comet/reference/comet-yaml-fields.md +5 -2
- package/assets/skills/comet/reference/context-recovery.md +10 -0
- package/assets/skills/comet/reference/scripts.md +47 -15
- package/assets/skills/comet/rules/comet-phase-guard.en.md +8 -0
- package/assets/skills/comet/rules/comet-phase-guard.md +9 -1
- package/assets/skills/comet/scripts/comet-resume-probe.mjs +3 -0
- package/assets/skills/comet/scripts/comet-runtime.mjs +1387 -424
- package/assets/skills/comet-archive/SKILL.md +24 -11
- package/assets/skills/comet-build/SKILL.md +42 -25
- package/assets/skills/comet-design/SKILL.md +1 -0
- package/assets/skills/comet-hotfix/SKILL.md +3 -1
- package/assets/skills/comet-open/SKILL.md +11 -1
- package/assets/skills/comet-tweak/SKILL.md +3 -1
- package/assets/skills/comet-verify/SKILL.md +29 -20
- package/assets/skills-zh/comet/SKILL.md +25 -1
- package/assets/skills-zh/comet/reference/comet-yaml-fields.md +5 -2
- package/assets/skills-zh/comet/reference/context-recovery.md +10 -0
- package/assets/skills-zh/comet/reference/scripts.md +47 -15
- package/assets/skills-zh/comet-archive/SKILL.md +24 -11
- package/assets/skills-zh/comet-build/SKILL.md +42 -25
- package/assets/skills-zh/comet-design/SKILL.md +1 -0
- package/assets/skills-zh/comet-hotfix/SKILL.md +3 -1
- package/assets/skills-zh/comet-open/SKILL.md +11 -1
- package/assets/skills-zh/comet-tweak/SKILL.md +3 -1
- package/assets/skills-zh/comet-verify/SKILL.md +30 -21
- package/dist/app/cli/comet-banner.d.ts +25 -0
- package/dist/app/cli/comet-banner.d.ts.map +1 -0
- package/dist/app/cli/comet-banner.js +265 -0
- package/dist/app/cli/comet-banner.js.map +1 -0
- package/dist/app/cli/index.js +36 -2
- package/dist/app/cli/index.js.map +1 -1
- package/dist/app/commands/classic.d.ts +4 -0
- package/dist/app/commands/classic.d.ts.map +1 -0
- package/dist/app/commands/classic.js +11 -0
- package/dist/app/commands/classic.js.map +1 -0
- package/dist/app/commands/doctor.d.ts.map +1 -1
- package/dist/app/commands/doctor.js +39 -24
- package/dist/app/commands/doctor.js.map +1 -1
- package/dist/app/commands/eval.d.ts.map +1 -1
- package/dist/app/commands/eval.js +57 -20
- package/dist/app/commands/eval.js.map +1 -1
- package/dist/app/commands/i18n.d.ts +1 -1
- package/dist/app/commands/i18n.d.ts.map +1 -1
- package/dist/app/commands/i18n.js +12 -4
- package/dist/app/commands/i18n.js.map +1 -1
- package/dist/app/commands/init.d.ts.map +1 -1
- package/dist/app/commands/init.js +16 -13
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/project-scope-selection.d.ts +13 -0
- package/dist/app/commands/project-scope-selection.d.ts.map +1 -0
- package/dist/app/commands/project-scope-selection.js +33 -0
- package/dist/app/commands/project-scope-selection.js.map +1 -0
- package/dist/app/commands/resume-probe.d.ts +11 -0
- package/dist/app/commands/resume-probe.d.ts.map +1 -0
- package/dist/app/commands/resume-probe.js +63 -0
- package/dist/app/commands/resume-probe.js.map +1 -0
- package/dist/app/commands/status.d.ts +31 -0
- package/dist/app/commands/status.d.ts.map +1 -1
- package/dist/app/commands/status.js +109 -20
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/uninstall.d.ts +2 -0
- package/dist/app/commands/uninstall.d.ts.map +1 -1
- package/dist/app/commands/uninstall.js +217 -40
- package/dist/app/commands/uninstall.js.map +1 -1
- package/dist/app/commands/update.d.ts +6 -0
- package/dist/app/commands/update.d.ts.map +1 -1
- package/dist/app/commands/update.js +361 -68
- package/dist/app/commands/update.js.map +1 -1
- package/dist/config/repository-layout.json +4 -2
- package/dist/domains/bundle/bundle-platform.d.ts.map +1 -1
- package/dist/domains/bundle/bundle-platform.js +3 -1
- package/dist/domains/bundle/bundle-platform.js.map +1 -1
- package/dist/domains/bundle/eval-manifest-runtime.d.ts +6 -0
- package/dist/domains/bundle/eval-manifest-runtime.d.ts.map +1 -0
- package/dist/domains/bundle/eval-manifest-runtime.js +58 -0
- package/dist/domains/bundle/eval-manifest-runtime.js.map +1 -0
- package/dist/domains/bundle/types.d.ts +1 -1
- package/dist/domains/bundle/types.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-archive.d.ts +1 -0
- package/dist/domains/comet-classic/classic-archive.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-archive.js +35 -38
- package/dist/domains/comet-classic/classic-archive.js.map +1 -1
- package/dist/domains/comet-classic/classic-cli.d.ts +1 -1
- package/dist/domains/comet-classic/classic-cli.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-cli.js +3 -0
- package/dist/domains/comet-classic/classic-cli.js.map +1 -1
- package/dist/domains/comet-classic/classic-command-checks.d.ts +20 -0
- package/dist/domains/comet-classic/classic-command-checks.d.ts.map +1 -0
- package/dist/domains/comet-classic/classic-command-checks.js +97 -0
- package/dist/domains/comet-classic/classic-command-checks.js.map +1 -0
- package/dist/domains/comet-classic/classic-current-change.d.ts +19 -0
- package/dist/domains/comet-classic/classic-current-change.d.ts.map +1 -0
- package/dist/domains/comet-classic/classic-current-change.js +132 -0
- package/dist/domains/comet-classic/classic-current-change.js.map +1 -0
- package/dist/domains/comet-classic/classic-diagnostics.d.ts +2 -1
- package/dist/domains/comet-classic/classic-diagnostics.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-diagnostics.js +54 -1
- package/dist/domains/comet-classic/classic-diagnostics.js.map +1 -1
- package/dist/domains/comet-classic/classic-evidence.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-evidence.js +4 -0
- package/dist/domains/comet-classic/classic-evidence.js.map +1 -1
- package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-guard.js +68 -69
- package/dist/domains/comet-classic/classic-guard.js.map +1 -1
- package/dist/domains/comet-classic/classic-hook-guard.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-hook-guard.js +66 -23
- package/dist/domains/comet-classic/classic-hook-guard.js.map +1 -1
- package/dist/domains/comet-classic/classic-project-config.d.ts +16 -0
- package/dist/domains/comet-classic/classic-project-config.d.ts.map +1 -0
- package/dist/domains/comet-classic/classic-project-config.js +40 -0
- package/dist/domains/comet-classic/classic-project-config.js.map +1 -0
- package/dist/domains/comet-classic/classic-resolver.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-resolver.js +3 -3
- package/dist/domains/comet-classic/classic-resolver.js.map +1 -1
- package/dist/domains/comet-classic/classic-resume-probe-command.d.ts +3 -0
- package/dist/domains/comet-classic/classic-resume-probe-command.d.ts.map +1 -0
- package/dist/domains/comet-classic/classic-resume-probe-command.js +58 -0
- package/dist/domains/comet-classic/classic-resume-probe-command.js.map +1 -0
- package/dist/domains/comet-classic/classic-resume-probe-entry.d.ts +2 -0
- package/dist/domains/comet-classic/classic-resume-probe-entry.d.ts.map +1 -0
- package/dist/domains/comet-classic/classic-resume-probe-entry.js +4 -0
- package/dist/domains/comet-classic/classic-resume-probe-entry.js.map +1 -0
- package/dist/domains/comet-classic/classic-resume-probe.d.ts +29 -0
- package/dist/domains/comet-classic/classic-resume-probe.d.ts.map +1 -0
- package/dist/domains/comet-classic/classic-resume-probe.js +386 -0
- package/dist/domains/comet-classic/classic-resume-probe.js.map +1 -0
- package/dist/domains/comet-classic/classic-runtime-run.d.ts +2 -1
- package/dist/domains/comet-classic/classic-runtime-run.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-runtime-run.js +38 -0
- package/dist/domains/comet-classic/classic-runtime-run.js.map +1 -1
- package/dist/domains/comet-classic/classic-state-command.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-state-command.js +128 -12
- package/dist/domains/comet-classic/classic-state-command.js.map +1 -1
- package/dist/domains/comet-classic/classic-state.d.ts +3 -1
- package/dist/domains/comet-classic/classic-state.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-state.js +4 -0
- package/dist/domains/comet-classic/classic-state.js.map +1 -1
- package/dist/domains/comet-classic/classic-store.d.ts +4 -1
- package/dist/domains/comet-classic/classic-store.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-store.js +4 -3
- package/dist/domains/comet-classic/classic-store.js.map +1 -1
- package/dist/domains/comet-classic/classic-transitions.d.ts +1 -1
- package/dist/domains/comet-classic/classic-transitions.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-transitions.js +20 -1
- package/dist/domains/comet-classic/classic-transitions.js.map +1 -1
- package/dist/domains/comet-classic/classic-validate-command.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-validate-command.js +1 -0
- package/dist/domains/comet-classic/classic-validate-command.js.map +1 -1
- package/dist/domains/comet-classic/index.d.ts +3 -0
- package/dist/domains/comet-classic/index.d.ts.map +1 -1
- package/dist/domains/comet-classic/index.js +3 -0
- package/dist/domains/comet-classic/index.js.map +1 -1
- package/dist/domains/dashboard/collector.js +10 -2
- package/dist/domains/dashboard/collector.js.map +1 -1
- package/dist/domains/dashboard/web/assets/{index-CjU3VXVu.js → index-CIvN-PL8.js} +11 -11
- package/dist/domains/dashboard/web/assets/{index-BcO3_QlT.css → index-DcyRPBnT.css} +1 -1
- package/dist/domains/dashboard/web/index.html +2 -2
- package/dist/domains/engine/types.d.ts +1 -1
- package/dist/domains/engine/types.d.ts.map +1 -1
- package/dist/domains/integrations/openspec.d.ts.map +1 -1
- package/dist/domains/integrations/openspec.js +6 -6
- package/dist/domains/integrations/openspec.js.map +1 -1
- package/dist/domains/skill/find.d.ts.map +1 -1
- package/dist/domains/skill/find.js +1 -6
- package/dist/domains/skill/find.js.map +1 -1
- package/dist/domains/skill/managed-markdown.d.ts +14 -0
- package/dist/domains/skill/managed-markdown.d.ts.map +1 -0
- package/dist/domains/skill/managed-markdown.js +126 -0
- package/dist/domains/skill/managed-markdown.js.map +1 -0
- package/dist/domains/skill/platform-install.d.ts +4 -3
- package/dist/domains/skill/platform-install.d.ts.map +1 -1
- package/dist/domains/skill/platform-install.js +96 -26
- package/dist/domains/skill/platform-install.js.map +1 -1
- package/dist/domains/skill/project-instructions.d.ts +22 -0
- package/dist/domains/skill/project-instructions.d.ts.map +1 -0
- package/dist/domains/skill/project-instructions.js +61 -0
- package/dist/domains/skill/project-instructions.js.map +1 -0
- package/dist/domains/skill/uninstall.d.ts +3 -1
- package/dist/domains/skill/uninstall.d.ts.map +1 -1
- package/dist/domains/skill/uninstall.js +86 -39
- package/dist/domains/skill/uninstall.js.map +1 -1
- package/dist/platform/install/detect.d.ts +2 -1
- package/dist/platform/install/detect.d.ts.map +1 -1
- package/dist/platform/install/detect.js +10 -1
- package/dist/platform/install/detect.js.map +1 -1
- package/dist/platform/install/platforms.d.ts +5 -1
- package/dist/platform/install/platforms.d.ts.map +1 -1
- package/dist/platform/install/platforms.js +21 -4
- package/dist/platform/install/platforms.js.map +1 -1
- package/dist/platform/install/project-registry.d.ts +37 -0
- package/dist/platform/install/project-registry.d.ts.map +1 -0
- package/dist/platform/install/project-registry.js +205 -0
- package/dist/platform/install/project-registry.js.map +1 -0
- package/eval/.env +28 -0
- package/eval/.env.example +52 -0
- package/eval/CLAUDE.md +43 -0
- package/eval/README.md +538 -0
- package/eval/langsmith/.env.example +16 -0
- package/eval/langsmith/README.md +93 -0
- package/eval/langsmith/skills/README.md +5 -0
- package/eval/langsmith/tasks/README.md +5 -0
- package/eval/langsmith/tests/conftest.py +187 -0
- package/eval/langsmith/tests/tasks/test_tasks.py +287 -0
- package/eval/langsmith/treatments/README.md +5 -0
- package/eval/local/README.md +80 -0
- package/eval/local/regression_baseline.json +13 -0
- package/eval/local/report-style-demo-assets/quality_cost.en.svg +41 -0
- package/eval/local/report-style-demo-assets/quality_cost.zh.svg +41 -0
- package/eval/local/report-style-demo-assets/rubric_delta.en.svg +56 -0
- package/eval/local/report-style-demo-assets/rubric_delta.zh.svg +56 -0
- package/eval/local/report-style-demo-assets/task_outcomes.en.svg +46 -0
- package/eval/local/report-style-demo-assets/task_outcomes.zh.svg +46 -0
- package/eval/local/report-style-demo.html +867 -0
- package/eval/local/scripts/compare_baselines.py +1018 -0
- package/eval/local/scripts/generate_report_style_demo_charts.py +322 -0
- package/eval/local/scripts/regression_check.py +171 -0
- package/eval/local/scripts/render_readme_benchmark_figures.py +416 -0
- package/eval/local/scripts/rescore_rubric.py +120 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/SKILL.md +282 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/auto-transition.md +27 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/comet-yaml-fields.md +68 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/context-recovery.md +35 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/debug-gate.md +17 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/decision-point.md +20 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/dirty-worktree.md +59 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/file-structure.md +28 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/reference/subagent-dispatch.md +113 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/rules/comet-phase-guard.md +125 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-archive.sh +311 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-env.sh +110 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-guard.sh +778 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-handoff.sh +390 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-hook-guard.sh +336 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-state.sh +1338 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039/scripts/comet-yaml-validate.sh +225 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039-archive/SKILL.md +100 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039-build/SKILL.md +304 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039-design/SKILL.md +263 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039-hotfix/SKILL.md +204 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039-open/SKILL.md +220 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039-tweak/SKILL.md +180 -0
- package/eval/local/skills/benchmarks/039-release/comet-classic-039-verify/SKILL.md +234 -0
- package/eval/local/skills/benchmarks/040-beta/comet/SKILL.md +261 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/auto-transition.md +27 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/comet-yaml-fields.md +73 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/context-recovery.md +45 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/debug-gate.md +33 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/decision-point.md +31 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/dirty-worktree.md +59 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/file-structure.md +28 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/intent-frame.md +74 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/scripts.md +69 -0
- package/eval/local/skills/benchmarks/040-beta/comet/reference/subagent-dispatch.md +169 -0
- package/eval/local/skills/benchmarks/040-beta/comet/rules/comet-phase-guard.md +130 -0
- package/eval/local/skills/benchmarks/040-beta/comet/runtime/classic/checks.yaml +6 -0
- package/eval/local/skills/benchmarks/040-beta/comet/runtime/classic/guardrails.yaml +13 -0
- package/eval/local/skills/benchmarks/040-beta/comet/runtime/classic/skill.yaml +122 -0
- package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-archive.mjs +9515 -0
- package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-env.mjs +15 -0
- package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-guard.mjs +10173 -0
- package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-handoff.mjs +9491 -0
- package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-hook-guard.mjs +9175 -0
- package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-intent.mjs +404 -0
- package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-state.mjs +9273 -0
- package/eval/local/skills/benchmarks/040-beta/comet/scripts/comet-yaml-validate.mjs +7627 -0
- package/eval/local/skills/benchmarks/040-beta/comet-archive/SKILL.md +110 -0
- package/eval/local/skills/benchmarks/040-beta/comet-build/SKILL.md +315 -0
- package/eval/local/skills/benchmarks/040-beta/comet-design/SKILL.md +260 -0
- package/eval/local/skills/benchmarks/040-beta/comet-hotfix/SKILL.md +190 -0
- package/eval/local/skills/benchmarks/040-beta/comet-open/SKILL.md +210 -0
- package/eval/local/skills/benchmarks/040-beta/comet-tweak/SKILL.md +183 -0
- package/eval/local/skills/benchmarks/040-beta/comet-verify/SKILL.md +230 -0
- package/eval/local/skills/benchmarks/dependency/claude-md/comet-workflow/CLAUDE.md +12 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-apply-change/SKILL.md +156 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-archive-change/SKILL.md +114 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-bulk-archive-change/SKILL.md +246 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-continue-change/SKILL.md +118 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-explore/SKILL.md +288 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-ff-change/SKILL.md +101 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-new-change/SKILL.md +74 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-onboard/SKILL.md +554 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-propose/SKILL.md +110 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-sync-specs/SKILL.md +138 -0
- package/eval/local/skills/benchmarks/dependency/openspec/openspec-verify-change/SKILL.md +168 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/SKILL.md +159 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/frame-template.html +213 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/helper.js +167 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/server.cjs +723 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/start-server.sh +209 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/scripts/stop-server.sh +120 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/brainstorming/visual-companion.md +291 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/dispatching-parallel-agents/SKILL.md +185 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/executing-plans/SKILL.md +70 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/finishing-a-development-branch/SKILL.md +241 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/receiving-code-review/SKILL.md +213 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/requesting-code-review/SKILL.md +103 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/requesting-code-review/code-reviewer.md +172 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/SKILL.md +418 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/implementer-prompt.md +139 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/scripts/review-package +44 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/scripts/sdd-workspace +22 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/scripts/task-brief +40 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/subagent-driven-development/task-reviewer-prompt.md +188 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/CREATION-LOG.md +119 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/SKILL.md +296 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/condition-based-waiting.md +115 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/defense-in-depth.md +122 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/find-polluter.sh +63 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/root-cause-tracing.md +169 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/test-academic.md +14 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/test-pressure-1.md +58 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/test-pressure-2.md +68 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/systematic-debugging/test-pressure-3.md +69 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/test-driven-development/SKILL.md +371 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/test-driven-development/testing-anti-patterns.md +299 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/using-git-worktrees/SKILL.md +202 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/verification-before-completion/SKILL.md +139 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-plans/SKILL.md +174 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/SKILL.md +689 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/anthropic-best-practices.md +1150 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/graphviz-conventions.dot +172 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/persuasion-principles.md +187 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/render-graphs.js +168 -0
- package/eval/local/skills/benchmarks/dependency/superpowers/writing-skills/testing-skills-with-subagents.md +384 -0
- package/eval/local/tasks/authoring-skill-smoke/environment/Dockerfile +2 -0
- package/eval/local/tasks/authoring-skill-smoke/instruction.md +4 -0
- package/eval/local/tasks/authoring-skill-smoke/task.toml +21 -0
- package/eval/local/tasks/authoring-skill-smoke/validation/test_authoring_skill_smoke.py +54 -0
- package/eval/local/tasks/comet-agent-memory-routing/environment/Dockerfile +28 -0
- package/eval/local/tasks/comet-agent-memory-routing/environment/agent_system.py +40 -0
- package/eval/local/tasks/comet-agent-memory-routing/environment/test_agent_system.py +29 -0
- package/eval/local/tasks/comet-agent-memory-routing/instruction.md +20 -0
- package/eval/local/tasks/comet-agent-memory-routing/task.toml +24 -0
- package/eval/local/tasks/comet-agent-memory-routing/validation/test_agent_memory_routing.py +24 -0
- package/eval/local/tasks/comet-api-cache-ttl/environment/Dockerfile +39 -0
- package/eval/local/tasks/comet-api-cache-ttl/environment/cache.py +39 -0
- package/eval/local/tasks/comet-api-cache-ttl/environment/test_cache.py +81 -0
- package/eval/local/tasks/comet-api-cache-ttl/instruction.md +33 -0
- package/eval/local/tasks/comet-api-cache-ttl/task.toml +24 -0
- package/eval/local/tasks/comet-api-cache-ttl/validation/test_api_cache_ttl.py +58 -0
- package/eval/local/tasks/comet-cross-file-refactor/environment/Dockerfile +15 -0
- package/eval/local/tasks/comet-cross-file-refactor/environment/cli.py +13 -0
- package/eval/local/tasks/comet-cross-file-refactor/environment/counting.py +13 -0
- package/eval/local/tasks/comet-cross-file-refactor/environment/test_textkit.py +21 -0
- package/eval/local/tasks/comet-cross-file-refactor/instruction.md +16 -0
- package/eval/local/tasks/comet-cross-file-refactor/task.toml +20 -0
- package/eval/local/tasks/comet-cross-file-refactor/validation/test_cross_file_refactor.py +20 -0
- package/eval/local/tasks/comet-dependency-confusion/environment/Dockerfile +28 -0
- package/eval/local/tasks/comet-dependency-confusion/environment/app.py +8 -0
- package/eval/local/tasks/comet-dependency-confusion/environment/settings_loader.py +14 -0
- package/eval/local/tasks/comet-dependency-confusion/environment/test_settings_service.py +19 -0
- package/eval/local/tasks/comet-dependency-confusion/instruction.md +18 -0
- package/eval/local/tasks/comet-dependency-confusion/task.toml +24 -0
- package/eval/local/tasks/comet-dependency-confusion/validation/test_dependency_confusion.py +24 -0
- package/eval/local/tasks/comet-fix-median/environment/Dockerfile +50 -0
- package/eval/local/tasks/comet-fix-median/environment/stats.py +54 -0
- package/eval/local/tasks/comet-fix-median/environment/test_stats.py +68 -0
- package/eval/local/tasks/comet-fix-median/instruction.md +25 -0
- package/eval/local/tasks/comet-fix-median/task.toml +24 -0
- package/eval/local/tasks/comet-fix-median/validation/test_fix_median.py +60 -0
- package/eval/local/tasks/comet-framework-selection/environment/Dockerfile +28 -0
- package/eval/local/tasks/comet-framework-selection/environment/architecture.py +12 -0
- package/eval/local/tasks/comet-framework-selection/environment/test_architecture.py +30 -0
- package/eval/local/tasks/comet-framework-selection/instruction.md +22 -0
- package/eval/local/tasks/comet-framework-selection/task.toml +24 -0
- package/eval/local/tasks/comet-framework-selection/validation/test_framework_selection.py +24 -0
- package/eval/local/tasks/comet-full-workflow/environment/Dockerfile +53 -0
- package/eval/local/tasks/comet-full-workflow/environment/test_wordcount.py +29 -0
- package/eval/local/tasks/comet-full-workflow/environment/wordcount.py +40 -0
- package/eval/local/tasks/comet-full-workflow/instruction.md +18 -0
- package/eval/local/tasks/comet-full-workflow/task.toml +24 -0
- package/eval/local/tasks/comet-full-workflow/validation/test_full_workflow.py +171 -0
- package/eval/local/tasks/comet-graph-execution-review/environment/Dockerfile +28 -0
- package/eval/local/tasks/comet-graph-execution-review/environment/pipeline.py +19 -0
- package/eval/local/tasks/comet-graph-execution-review/environment/test_pipeline.py +53 -0
- package/eval/local/tasks/comet-graph-execution-review/instruction.md +21 -0
- package/eval/local/tasks/comet-graph-execution-review/task.toml +24 -0
- package/eval/local/tasks/comet-graph-execution-review/validation/test_graph_execution_review.py +24 -0
- package/eval/local/tasks/comet-human-approval-flow/environment/Dockerfile +15 -0
- package/eval/local/tasks/comet-human-approval-flow/environment/approvals.py +17 -0
- package/eval/local/tasks/comet-human-approval-flow/environment/test_approvals.py +30 -0
- package/eval/local/tasks/comet-human-approval-flow/instruction.md +15 -0
- package/eval/local/tasks/comet-human-approval-flow/task.toml +20 -0
- package/eval/local/tasks/comet-human-approval-flow/validation/test_human_approval_flow.py +20 -0
- package/eval/local/tasks/comet-layered-streaming-fix/environment/Dockerfile +20 -0
- package/eval/local/tasks/comet-layered-streaming-fix/environment/chat_app.py +19 -0
- package/eval/local/tasks/comet-layered-streaming-fix/environment/test_chat_app.py +15 -0
- package/eval/local/tasks/comet-layered-streaming-fix/instruction.md +14 -0
- package/eval/local/tasks/comet-layered-streaming-fix/task.toml +20 -0
- package/eval/local/tasks/comet-layered-streaming-fix/validation/test_layered_streaming_fix.py +20 -0
- package/eval/local/tasks/comet-noise-distractor/environment/Dockerfile +15 -0
- package/eval/local/tasks/comet-noise-distractor/environment/distractor.md +4 -0
- package/eval/local/tasks/comet-noise-distractor/environment/invoice.py +5 -0
- package/eval/local/tasks/comet-noise-distractor/environment/test_invoice.py +18 -0
- package/eval/local/tasks/comet-noise-distractor/instruction.md +17 -0
- package/eval/local/tasks/comet-noise-distractor/task.toml +20 -0
- package/eval/local/tasks/comet-noise-distractor/validation/test_noise_distractor.py +20 -0
- package/eval/local/tasks/comet-observability-env-template/environment/Dockerfile +15 -0
- package/eval/local/tasks/comet-observability-env-template/environment/env_writer.py +17 -0
- package/eval/local/tasks/comet-observability-env-template/environment/test_env_writer.py +17 -0
- package/eval/local/tasks/comet-observability-env-template/instruction.md +20 -0
- package/eval/local/tasks/comet-observability-env-template/task.toml +20 -0
- package/eval/local/tasks/comet-observability-env-template/validation/test_observability_env_template.py +46 -0
- package/eval/local/tasks/comet-perf-dedupe/environment/Dockerfile +39 -0
- package/eval/local/tasks/comet-perf-dedupe/environment/dedupe.py +22 -0
- package/eval/local/tasks/comet-perf-dedupe/environment/test_dedupe.py +43 -0
- package/eval/local/tasks/comet-perf-dedupe/instruction.md +25 -0
- package/eval/local/tasks/comet-perf-dedupe/task.toml +24 -0
- package/eval/local/tasks/comet-perf-dedupe/validation/test_perf_dedupe.py +40 -0
- package/eval/local/tasks/comet-persistence-threading/environment/Dockerfile +15 -0
- package/eval/local/tasks/comet-persistence-threading/environment/session_store.py +15 -0
- package/eval/local/tasks/comet-persistence-threading/environment/test_session_store.py +21 -0
- package/eval/local/tasks/comet-persistence-threading/instruction.md +14 -0
- package/eval/local/tasks/comet-persistence-threading/task.toml +20 -0
- package/eval/local/tasks/comet-persistence-threading/validation/test_persistence_threading.py +20 -0
- package/eval/local/tasks/comet-refactor-counter/environment/Dockerfile +39 -0
- package/eval/local/tasks/comet-refactor-counter/environment/test_text_processor.py +57 -0
- package/eval/local/tasks/comet-refactor-counter/environment/text_processor.py +49 -0
- package/eval/local/tasks/comet-refactor-counter/instruction.md +31 -0
- package/eval/local/tasks/comet-refactor-counter/task.toml +24 -0
- package/eval/local/tasks/comet-refactor-counter/validation/test_refactor_counter.py +75 -0
- package/eval/local/tasks/comet-robust-config/environment/Dockerfile +39 -0
- package/eval/local/tasks/comet-robust-config/environment/config_loader.py +56 -0
- package/eval/local/tasks/comet-robust-config/environment/test_config_loader.py +72 -0
- package/eval/local/tasks/comet-robust-config/instruction.md +30 -0
- package/eval/local/tasks/comet-robust-config/task.toml +24 -0
- package/eval/local/tasks/comet-robust-config/validation/test_robust_config.py +37 -0
- package/eval/local/tasks/generic-skill-smoke/environment/Dockerfile +2 -0
- package/eval/local/tasks/generic-skill-smoke/instruction.md +7 -0
- package/eval/local/tasks/generic-skill-smoke/task.toml +21 -0
- package/eval/local/tasks/generic-skill-smoke/validation/test_generic_skill_smoke.py +36 -0
- package/eval/local/tasks/index.yaml +155 -0
- package/eval/local/tasks/workflow-overlay-contract/environment/Dockerfile +5 -0
- package/eval/local/tasks/workflow-overlay-contract/instruction.md +5 -0
- package/eval/local/tasks/workflow-overlay-contract/task.toml +21 -0
- package/eval/local/tasks/workflow-overlay-contract/validation/test_workflow_overlay_contract.py +235 -0
- package/eval/local/tasks/workflow-route-conformance/environment/Dockerfile +2 -0
- package/eval/local/tasks/workflow-route-conformance/instruction.md +5 -0
- package/eval/local/tasks/workflow-route-conformance/task.toml +21 -0
- package/eval/local/tasks/workflow-route-conformance/validation/test_workflow_route_conformance.py +84 -0
- package/eval/local/tests/conftest.py +1148 -0
- package/eval/local/tests/scaffold/test_attribution.py +48 -0
- package/eval/local/tests/scaffold/test_compare_baselines.py +999 -0
- package/eval/local/tests/scaffold/test_conftest_helpers.py +534 -0
- package/eval/local/tests/scaffold/test_evidence.py +15 -0
- package/eval/local/tests/scaffold/test_langsmith_conftest.py +244 -0
- package/eval/local/tests/scaffold/test_logging.py +243 -0
- package/eval/local/tests/scaffold/test_manifests.py +132 -0
- package/eval/local/tests/scaffold/test_profiles.py +866 -0
- package/eval/local/tests/scaffold/test_regression_check.py +19 -0
- package/eval/local/tests/scaffold/test_report_style_demo_charts.py +45 -0
- package/eval/local/tests/scaffold/test_sample_quality.py +211 -0
- package/eval/local/tests/scaffold/test_tasks.py +168 -0
- package/eval/local/tests/scaffold/test_treatments.py +244 -0
- package/eval/local/tests/scaffold/test_utils.py +139 -0
- package/eval/local/tests/tasks/test_tasks.py +445 -0
- package/eval/local/tests/tasks/test_validation_scripts.py +143 -0
- package/eval/local/treatments/comet/comet_full_039.yaml +141 -0
- package/eval/local/treatments/comet/comet_full_040_beta.yaml +140 -0
- package/eval/local/treatments/common/control.yaml +4 -0
- package/eval/pyproject.toml +41 -0
- package/eval/report-html-config.json +6 -0
- package/eval/scaffold/__init__.py +67 -0
- package/eval/scaffold/python/__init__.py +95 -0
- package/eval/scaffold/python/attribution.py +43 -0
- package/eval/scaffold/python/evidence.py +38 -0
- package/eval/scaffold/python/external_data_handler.py +18 -0
- package/eval/scaffold/python/generic_llm_judge.py +235 -0
- package/eval/scaffold/python/judge_config.py +168 -0
- package/eval/scaffold/python/llm_judge.py +191 -0
- package/eval/scaffold/python/logging.py +705 -0
- package/eval/scaffold/python/manifests.py +129 -0
- package/eval/scaffold/python/paper_charts.py +25 -0
- package/eval/scaffold/python/pass_at_k.py +107 -0
- package/eval/scaffold/python/paths.py +56 -0
- package/eval/scaffold/python/profiles.py +127 -0
- package/eval/scaffold/python/report_outputs.py +1391 -0
- package/eval/scaffold/python/sample_quality.py +339 -0
- package/eval/scaffold/python/schema.py +39 -0
- package/eval/scaffold/python/skill_parser.py +469 -0
- package/eval/scaffold/python/tasks.py +330 -0
- package/eval/scaffold/python/treatments.py +271 -0
- package/eval/scaffold/python/utils.py +366 -0
- package/eval/scaffold/python/validation/__init__.py +21 -0
- package/eval/scaffold/python/validation/authoring_rubric.py +326 -0
- package/eval/scaffold/python/validation/comet_workflow.py +187 -0
- package/eval/scaffold/python/validation/core.py +264 -0
- package/eval/scaffold/python/validation/dataset.py +337 -0
- package/eval/scaffold/python/validation/docker.py +106 -0
- package/eval/scaffold/python/validation/evaluator.py +549 -0
- package/eval/scaffold/python/validation/generic_rubric.py +169 -0
- package/eval/scaffold/python/validation/rubric.py +740 -0
- package/eval/scaffold/python/validation/runner.py +237 -0
- package/eval/scaffold/python/validation/scripts.py +58 -0
- package/eval/scaffold/python/validation/tracing.py +313 -0
- package/eval/scaffold/shell/common.sh +126 -0
- package/eval/scaffold/shell/docker.sh +482 -0
- package/eval/scaffold/shell/run-claude-loop.sh +181 -0
- package/eval/scaffold/shell/setup.sh +259 -0
- package/eval/simulator-instruction.md +9 -0
- 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())
|