@rpamis/comet 0.4.0-beta.6 → 0.4.0-beta.7
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 +209 -74
- package/assets/manifest.json +14 -5
- package/assets/skills/comet/SKILL.md +17 -282
- package/assets/skills/comet/reference/comet-yaml-fields.md +1 -1
- package/assets/skills/comet/reference/context-recovery.md +3 -3
- package/assets/skills/comet/reference/intent-frame.md +2 -2
- package/assets/skills/comet/reference/scripts.md +2 -2
- package/assets/skills/comet/rules/comet-workflow-guard.en.md +32 -0
- package/assets/skills/comet/rules/comet-workflow-guard.md +32 -0
- package/assets/skills/comet/scripts/comet-entry-runtime.mjs +7834 -0
- package/assets/skills/comet/scripts/comet-hook-router.mjs +10307 -0
- package/assets/skills/comet/scripts/comet-runtime.mjs +612 -499
- package/assets/skills/comet-any/SKILL.md +4 -4
- package/assets/skills/comet-any/reference/authoring-subagents.md +1 -1
- package/assets/skills/comet-any/reference/bundle-authoring.md +1 -1
- package/assets/skills/comet-any/reference/subagents/reference-author.md +1 -1
- package/assets/skills/comet-any/reference/subagents/script-author.md +1 -1
- package/assets/skills/comet-any/reference/subagents/skill-core-author.md +1 -1
- package/assets/skills/comet-any/reference/subagents/skill-reviewer.md +2 -2
- package/assets/skills/comet-any/reference/subagents/workflow-entry-author.md +3 -3
- package/assets/skills/comet-archive/SKILL.md +1 -2
- package/assets/skills/comet-build/SKILL.md +1 -1
- package/assets/skills/comet-classic/SKILL.md +293 -0
- package/assets/skills/comet-design/SKILL.md +1 -1
- package/assets/skills/comet-hotfix/SKILL.md +4 -4
- package/assets/skills/comet-native/SKILL.md +257 -0
- package/assets/skills/comet-native/reference/artifacts.md +204 -0
- package/assets/skills/comet-native/reference/commands.md +118 -0
- package/assets/skills/comet-native/reference/recovery.md +143 -0
- package/assets/skills/comet-native/scripts/comet-native-hook-guard.mjs +4 -0
- package/assets/skills/comet-native/scripts/comet-native-runtime.mjs +28898 -0
- package/assets/skills/comet-open/SKILL.md +4 -4
- package/assets/skills/comet-tweak/SKILL.md +7 -7
- package/assets/skills-zh/comet/SKILL.md +17 -270
- package/assets/skills-zh/comet/reference/comet-yaml-fields.md +1 -1
- package/assets/skills-zh/comet/reference/context-recovery.md +3 -3
- package/assets/skills-zh/comet/reference/intent-frame.md +2 -2
- package/assets/skills-zh/comet/reference/scripts.md +2 -2
- package/assets/skills-zh/comet-any/SKILL.md +4 -4
- package/assets/skills-zh/comet-any/reference/authoring-subagents.md +1 -1
- package/assets/skills-zh/comet-any/reference/bundle-authoring.md +1 -1
- package/assets/skills-zh/comet-any/reference/subagents/reference-author.md +1 -1
- package/assets/skills-zh/comet-any/reference/subagents/script-author.md +1 -1
- package/assets/skills-zh/comet-any/reference/subagents/skill-core-author.md +1 -1
- package/assets/skills-zh/comet-any/reference/subagents/skill-reviewer.md +2 -2
- package/assets/skills-zh/comet-any/reference/subagents/workflow-entry-author.md +3 -3
- package/assets/skills-zh/comet-archive/SKILL.md +1 -2
- package/assets/skills-zh/comet-build/SKILL.md +1 -1
- package/assets/skills-zh/comet-classic/SKILL.md +281 -0
- package/assets/skills-zh/comet-design/SKILL.md +1 -1
- package/assets/skills-zh/comet-hotfix/SKILL.md +4 -4
- package/assets/skills-zh/comet-native/SKILL.md +257 -0
- package/assets/skills-zh/comet-native/reference/artifacts.md +206 -0
- package/assets/skills-zh/comet-native/reference/commands.md +118 -0
- package/assets/skills-zh/comet-native/reference/recovery.md +143 -0
- package/assets/skills-zh/comet-open/SKILL.md +4 -4
- package/assets/skills-zh/comet-tweak/SKILL.md +7 -7
- package/dist/app/cli/index.js +59 -13
- package/dist/app/cli/index.js.map +1 -1
- package/dist/app/commands/command-result.d.ts +6 -0
- package/dist/app/commands/command-result.d.ts.map +1 -0
- package/dist/app/commands/command-result.js +4 -0
- package/dist/app/commands/command-result.js.map +1 -0
- package/dist/app/commands/doctor.d.ts +1 -0
- package/dist/app/commands/doctor.d.ts.map +1 -1
- package/dist/app/commands/doctor.js +201 -26
- package/dist/app/commands/doctor.js.map +1 -1
- package/dist/app/commands/eval.d.ts +3 -1
- package/dist/app/commands/eval.d.ts.map +1 -1
- package/dist/app/commands/eval.js +17 -7
- 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 +6 -2
- package/dist/app/commands/i18n.js.map +1 -1
- package/dist/app/commands/init.d.ts +10 -2
- package/dist/app/commands/init.d.ts.map +1 -1
- package/dist/app/commands/init.js +331 -50
- package/dist/app/commands/init.js.map +1 -1
- package/dist/app/commands/native.d.ts +2 -0
- package/dist/app/commands/native.d.ts.map +1 -0
- package/dist/app/commands/native.js +10 -0
- package/dist/app/commands/native.js.map +1 -0
- package/dist/app/commands/project-scope-selection.d.ts.map +1 -1
- package/dist/app/commands/project-scope-selection.js +2 -0
- package/dist/app/commands/project-scope-selection.js.map +1 -1
- package/dist/app/commands/resume-probe.d.ts.map +1 -1
- package/dist/app/commands/resume-probe.js +8 -2
- package/dist/app/commands/resume-probe.js.map +1 -1
- package/dist/app/commands/status.d.ts +0 -32
- package/dist/app/commands/status.d.ts.map +1 -1
- package/dist/app/commands/status.js +44 -177
- package/dist/app/commands/status.js.map +1 -1
- package/dist/app/commands/update.d.ts +20 -4
- package/dist/app/commands/update.d.ts.map +1 -1
- package/dist/app/commands/update.js +698 -90
- package/dist/app/commands/update.js.map +1 -1
- package/dist/app/commands/workflow.d.ts +6 -0
- package/dist/app/commands/workflow.d.ts.map +1 -0
- package/dist/app/commands/workflow.js +11 -0
- package/dist/app/commands/workflow.js.map +1 -0
- package/dist/config/repository-layout.json +21 -1
- package/dist/domains/comet-classic/classic-archive.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-archive.js +3 -0
- package/dist/domains/comet-classic/classic-archive.js.map +1 -1
- package/dist/domains/comet-classic/classic-current-change.d.ts +3 -5
- package/dist/domains/comet-classic/classic-current-change.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-current-change.js +31 -57
- package/dist/domains/comet-classic/classic-current-change.js.map +1 -1
- package/dist/domains/comet-classic/classic-hook-guard.d.ts +9 -0
- package/dist/domains/comet-classic/classic-hook-guard.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-hook-guard.js +84 -11
- package/dist/domains/comet-classic/classic-hook-guard.js.map +1 -1
- package/dist/domains/comet-classic/classic-project-config.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-project-config.js +12 -8
- package/dist/domains/comet-classic/classic-project-config.js.map +1 -1
- package/dist/domains/comet-classic/classic-runtime-run.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-runtime-run.js +9 -1
- package/dist/domains/comet-classic/classic-runtime-run.js.map +1 -1
- package/dist/domains/comet-entry/current-selection-repair.d.ts +17 -0
- package/dist/domains/comet-entry/current-selection-repair.d.ts.map +1 -0
- package/dist/domains/comet-entry/current-selection-repair.js +19 -0
- package/dist/domains/comet-entry/current-selection-repair.js.map +1 -0
- package/dist/domains/comet-entry/current-selection.d.ts +28 -0
- package/dist/domains/comet-entry/current-selection.d.ts.map +1 -0
- package/dist/domains/comet-entry/current-selection.js +116 -0
- package/dist/domains/comet-entry/current-selection.js.map +1 -0
- package/dist/domains/comet-entry/entry-runtime-entry.d.ts +2 -0
- package/dist/domains/comet-entry/entry-runtime-entry.d.ts.map +1 -0
- package/dist/domains/comet-entry/entry-runtime-entry.js +3 -0
- package/dist/domains/comet-entry/entry-runtime-entry.js.map +1 -0
- package/dist/domains/comet-entry/entry-runtime.d.ts +7 -0
- package/dist/domains/comet-entry/entry-runtime.d.ts.map +1 -0
- package/dist/domains/comet-entry/entry-runtime.js +55 -0
- package/dist/domains/comet-entry/entry-runtime.js.map +1 -0
- package/dist/domains/comet-entry/hook-adapter.d.ts +6 -0
- package/dist/domains/comet-entry/hook-adapter.d.ts.map +1 -0
- package/dist/domains/comet-entry/hook-adapter.js +202 -0
- package/dist/domains/comet-entry/hook-adapter.js.map +1 -0
- package/dist/domains/comet-entry/hook-router-entry.d.ts +8 -0
- package/dist/domains/comet-entry/hook-router-entry.d.ts.map +1 -0
- package/dist/domains/comet-entry/hook-router-entry.js +92 -0
- package/dist/domains/comet-entry/hook-router-entry.js.map +1 -0
- package/dist/domains/comet-entry/hook-router.d.ts +32 -0
- package/dist/domains/comet-entry/hook-router.d.ts.map +1 -0
- package/dist/domains/comet-entry/hook-router.js +133 -0
- package/dist/domains/comet-entry/hook-router.js.map +1 -0
- package/dist/domains/comet-entry/hook-types.d.ts +20 -0
- package/dist/domains/comet-entry/hook-types.d.ts.map +1 -0
- package/dist/domains/comet-entry/hook-types.js +2 -0
- package/dist/domains/comet-entry/hook-types.js.map +1 -0
- package/dist/domains/comet-entry/init-workflow.d.ts +16 -0
- package/dist/domains/comet-entry/init-workflow.d.ts.map +1 -0
- package/dist/domains/comet-entry/init-workflow.js +103 -0
- package/dist/domains/comet-entry/init-workflow.js.map +1 -0
- package/dist/domains/comet-entry/project-status.d.ts +3 -0
- package/dist/domains/comet-entry/project-status.d.ts.map +1 -0
- package/dist/domains/comet-entry/project-status.js +222 -0
- package/dist/domains/comet-entry/project-status.js.map +1 -0
- package/dist/domains/comet-entry/resolve-entry.d.ts +3 -0
- package/dist/domains/comet-entry/resolve-entry.d.ts.map +1 -0
- package/dist/domains/comet-entry/resolve-entry.js +22 -0
- package/dist/domains/comet-entry/resolve-entry.js.map +1 -0
- package/dist/domains/comet-entry/resume-probe.d.ts +34 -0
- package/dist/domains/comet-entry/resume-probe.d.ts.map +1 -0
- package/dist/domains/comet-entry/resume-probe.js +458 -0
- package/dist/domains/comet-entry/resume-probe.js.map +1 -0
- package/dist/domains/comet-entry/types.d.ts +59 -0
- package/dist/domains/comet-entry/types.d.ts.map +1 -0
- package/dist/domains/comet-entry/types.js +2 -0
- package/dist/domains/comet-entry/types.js.map +1 -0
- package/dist/domains/comet-entry/workflow-resolution.d.ts +8 -0
- package/dist/domains/comet-entry/workflow-resolution.d.ts.map +1 -0
- package/dist/domains/comet-entry/workflow-resolution.js +16 -0
- package/dist/domains/comet-entry/workflow-resolution.js.map +1 -0
- package/dist/domains/comet-native/index.d.ts +35 -0
- package/dist/domains/comet-native/index.d.ts.map +1 -0
- package/dist/domains/comet-native/index.js +35 -0
- package/dist/domains/comet-native/index.js.map +1 -0
- package/dist/domains/comet-native/native-acceptance.d.ts +42 -0
- package/dist/domains/comet-native/native-acceptance.d.ts.map +1 -0
- package/dist/domains/comet-native/native-acceptance.js +503 -0
- package/dist/domains/comet-native/native-acceptance.js.map +1 -0
- package/dist/domains/comet-native/native-archive-content.d.ts +26 -0
- package/dist/domains/comet-native/native-archive-content.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive-content.js +159 -0
- package/dist/domains/comet-native/native-archive-content.js.map +1 -0
- package/dist/domains/comet-native/native-archive-inspection.d.ts +9 -0
- package/dist/domains/comet-native/native-archive-inspection.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive-inspection.js +94 -0
- package/dist/domains/comet-native/native-archive-inspection.js.map +1 -0
- package/dist/domains/comet-native/native-archive-preflight.d.ts +52 -0
- package/dist/domains/comet-native/native-archive-preflight.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive-preflight.js +214 -0
- package/dist/domains/comet-native/native-archive-preflight.js.map +1 -0
- package/dist/domains/comet-native/native-archive-transaction.d.ts +19 -0
- package/dist/domains/comet-native/native-archive-transaction.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive-transaction.js +1181 -0
- package/dist/domains/comet-native/native-archive-transaction.js.map +1 -0
- package/dist/domains/comet-native/native-archive.d.ts +36 -0
- package/dist/domains/comet-native/native-archive.d.ts.map +1 -0
- package/dist/domains/comet-native/native-archive.js +403 -0
- package/dist/domains/comet-native/native-archive.js.map +1 -0
- package/dist/domains/comet-native/native-artifacts.d.ts +10 -0
- package/dist/domains/comet-native/native-artifacts.d.ts.map +1 -0
- package/dist/domains/comet-native/native-artifacts.js +204 -0
- package/dist/domains/comet-native/native-artifacts.js.map +1 -0
- package/dist/domains/comet-native/native-atomic-file.d.ts +10 -0
- package/dist/domains/comet-native/native-atomic-file.d.ts.map +1 -0
- package/dist/domains/comet-native/native-atomic-file.js +175 -0
- package/dist/domains/comet-native/native-atomic-file.js.map +1 -0
- package/dist/domains/comet-native/native-bounded-file.d.ts +19 -0
- package/dist/domains/comet-native/native-bounded-file.d.ts.map +1 -0
- package/dist/domains/comet-native/native-bounded-file.js +180 -0
- package/dist/domains/comet-native/native-bounded-file.js.map +1 -0
- package/dist/domains/comet-native/native-build-evidence.d.ts +42 -0
- package/dist/domains/comet-native/native-build-evidence.d.ts.map +1 -0
- package/dist/domains/comet-native/native-build-evidence.js +257 -0
- package/dist/domains/comet-native/native-build-evidence.js.map +1 -0
- package/dist/domains/comet-native/native-canonical-hash.d.ts +10 -0
- package/dist/domains/comet-native/native-canonical-hash.d.ts.map +1 -0
- package/dist/domains/comet-native/native-canonical-hash.js +100 -0
- package/dist/domains/comet-native/native-canonical-hash.js.map +1 -0
- package/dist/domains/comet-native/native-change-recovery.d.ts +11 -0
- package/dist/domains/comet-native/native-change-recovery.d.ts.map +1 -0
- package/dist/domains/comet-native/native-change-recovery.js +15 -0
- package/dist/domains/comet-native/native-change-recovery.js.map +1 -0
- package/dist/domains/comet-native/native-change.d.ts +61 -0
- package/dist/domains/comet-native/native-change.d.ts.map +1 -0
- package/dist/domains/comet-native/native-change.js +696 -0
- package/dist/domains/comet-native/native-change.js.map +1 -0
- package/dist/domains/comet-native/native-check-receipt-model.d.ts +63 -0
- package/dist/domains/comet-native/native-check-receipt-model.d.ts.map +1 -0
- package/dist/domains/comet-native/native-check-receipt-model.js +347 -0
- package/dist/domains/comet-native/native-check-receipt-model.js.map +1 -0
- package/dist/domains/comet-native/native-check-receipt-storage.d.ts +10 -0
- package/dist/domains/comet-native/native-check-receipt-storage.d.ts.map +1 -0
- package/dist/domains/comet-native/native-check-receipt-storage.js +196 -0
- package/dist/domains/comet-native/native-check-receipt-storage.js.map +1 -0
- package/dist/domains/comet-native/native-check-receipt.d.ts +19 -0
- package/dist/domains/comet-native/native-check-receipt.d.ts.map +1 -0
- package/dist/domains/comet-native/native-check-receipt.js +446 -0
- package/dist/domains/comet-native/native-check-receipt.js.map +1 -0
- package/dist/domains/comet-native/native-check.d.ts +18 -0
- package/dist/domains/comet-native/native-check.d.ts.map +1 -0
- package/dist/domains/comet-native/native-check.js +29 -0
- package/dist/domains/comet-native/native-check.js.map +1 -0
- package/dist/domains/comet-native/native-checkpoint-journal.d.ts +13 -0
- package/dist/domains/comet-native/native-checkpoint-journal.d.ts.map +1 -0
- package/dist/domains/comet-native/native-checkpoint-journal.js +53 -0
- package/dist/domains/comet-native/native-checkpoint-journal.js.map +1 -0
- package/dist/domains/comet-native/native-checkpoint-storage.d.ts +43 -0
- package/dist/domains/comet-native/native-checkpoint-storage.d.ts.map +1 -0
- package/dist/domains/comet-native/native-checkpoint-storage.js +487 -0
- package/dist/domains/comet-native/native-checkpoint-storage.js.map +1 -0
- package/dist/domains/comet-native/native-cli-entry.d.ts +2 -0
- package/dist/domains/comet-native/native-cli-entry.d.ts.map +1 -0
- package/dist/domains/comet-native/native-cli-entry.js +18 -0
- package/dist/domains/comet-native/native-cli-entry.js.map +1 -0
- package/dist/domains/comet-native/native-cli.d.ts +7 -0
- package/dist/domains/comet-native/native-cli.d.ts.map +1 -0
- package/dist/domains/comet-native/native-cli.js +642 -0
- package/dist/domains/comet-native/native-cli.js.map +1 -0
- package/dist/domains/comet-native/native-config.d.ts +15 -0
- package/dist/domains/comet-native/native-config.d.ts.map +1 -0
- package/dist/domains/comet-native/native-config.js +281 -0
- package/dist/domains/comet-native/native-config.js.map +1 -0
- package/dist/domains/comet-native/native-conflict-inspection.d.ts +17 -0
- package/dist/domains/comet-native/native-conflict-inspection.d.ts.map +1 -0
- package/dist/domains/comet-native/native-conflict-inspection.js +90 -0
- package/dist/domains/comet-native/native-conflict-inspection.js.map +1 -0
- package/dist/domains/comet-native/native-conflict-radar.d.ts +75 -0
- package/dist/domains/comet-native/native-conflict-radar.d.ts.map +1 -0
- package/dist/domains/comet-native/native-conflict-radar.js +326 -0
- package/dist/domains/comet-native/native-conflict-radar.js.map +1 -0
- package/dist/domains/comet-native/native-continuation.d.ts +9 -0
- package/dist/domains/comet-native/native-continuation.d.ts.map +1 -0
- package/dist/domains/comet-native/native-continuation.js +146 -0
- package/dist/domains/comet-native/native-continuation.js.map +1 -0
- package/dist/domains/comet-native/native-contract-files.d.ts +24 -0
- package/dist/domains/comet-native/native-contract-files.d.ts.map +1 -0
- package/dist/domains/comet-native/native-contract-files.js +66 -0
- package/dist/domains/comet-native/native-contract-files.js.map +1 -0
- package/dist/domains/comet-native/native-contract.d.ts +41 -0
- package/dist/domains/comet-native/native-contract.d.ts.map +1 -0
- package/dist/domains/comet-native/native-contract.js +141 -0
- package/dist/domains/comet-native/native-contract.js.map +1 -0
- package/dist/domains/comet-native/native-diagnostics.d.ts +18 -0
- package/dist/domains/comet-native/native-diagnostics.d.ts.map +1 -0
- package/dist/domains/comet-native/native-diagnostics.js +522 -0
- package/dist/domains/comet-native/native-diagnostics.js.map +1 -0
- package/dist/domains/comet-native/native-doctor.d.ts +12 -0
- package/dist/domains/comet-native/native-doctor.d.ts.map +1 -0
- package/dist/domains/comet-native/native-doctor.js +768 -0
- package/dist/domains/comet-native/native-doctor.js.map +1 -0
- package/dist/domains/comet-native/native-evidence-retention.d.ts +37 -0
- package/dist/domains/comet-native/native-evidence-retention.d.ts.map +1 -0
- package/dist/domains/comet-native/native-evidence-retention.js +885 -0
- package/dist/domains/comet-native/native-evidence-retention.js.map +1 -0
- package/dist/domains/comet-native/native-evidence-storage.d.ts +41 -0
- package/dist/domains/comet-native/native-evidence-storage.d.ts.map +1 -0
- package/dist/domains/comet-native/native-evidence-storage.js +351 -0
- package/dist/domains/comet-native/native-evidence-storage.js.map +1 -0
- package/dist/domains/comet-native/native-findings.d.ts +8 -0
- package/dist/domains/comet-native/native-findings.d.ts.map +1 -0
- package/dist/domains/comet-native/native-findings.js +238 -0
- package/dist/domains/comet-native/native-findings.js.map +1 -0
- package/dist/domains/comet-native/native-guards.d.ts +7 -0
- package/dist/domains/comet-native/native-guards.d.ts.map +1 -0
- package/dist/domains/comet-native/native-guards.js +126 -0
- package/dist/domains/comet-native/native-guards.js.map +1 -0
- package/dist/domains/comet-native/native-hash.d.ts +3 -0
- package/dist/domains/comet-native/native-hash.d.ts.map +1 -0
- package/dist/domains/comet-native/native-hash.js +12 -0
- package/dist/domains/comet-native/native-hash.js.map +1 -0
- package/dist/domains/comet-native/native-hook-guard.d.ts +17 -0
- package/dist/domains/comet-native/native-hook-guard.d.ts.map +1 -0
- package/dist/domains/comet-native/native-hook-guard.js +154 -0
- package/dist/domains/comet-native/native-hook-guard.js.map +1 -0
- package/dist/domains/comet-native/native-lock.d.ts +47 -0
- package/dist/domains/comet-native/native-lock.d.ts.map +1 -0
- package/dist/domains/comet-native/native-lock.js +395 -0
- package/dist/domains/comet-native/native-lock.js.map +1 -0
- package/dist/domains/comet-native/native-mutation-lock.d.ts +5 -0
- package/dist/domains/comet-native/native-mutation-lock.d.ts.map +1 -0
- package/dist/domains/comet-native/native-mutation-lock.js +66 -0
- package/dist/domains/comet-native/native-mutation-lock.js.map +1 -0
- package/dist/domains/comet-native/native-paths.d.ts +10 -0
- package/dist/domains/comet-native/native-paths.d.ts.map +1 -0
- package/dist/domains/comet-native/native-paths.js +171 -0
- package/dist/domains/comet-native/native-paths.js.map +1 -0
- package/dist/domains/comet-native/native-progress-checkpoint.d.ts +13 -0
- package/dist/domains/comet-native/native-progress-checkpoint.d.ts.map +1 -0
- package/dist/domains/comet-native/native-progress-checkpoint.js +104 -0
- package/dist/domains/comet-native/native-progress-checkpoint.js.map +1 -0
- package/dist/domains/comet-native/native-protected-file.d.ts +113 -0
- package/dist/domains/comet-native/native-protected-file.d.ts.map +1 -0
- package/dist/domains/comet-native/native-protected-file.js +509 -0
- package/dist/domains/comet-native/native-protected-file.js.map +1 -0
- package/dist/domains/comet-native/native-redaction.d.ts +2 -0
- package/dist/domains/comet-native/native-redaction.d.ts.map +1 -0
- package/dist/domains/comet-native/native-redaction.js +18 -0
- package/dist/domains/comet-native/native-redaction.js.map +1 -0
- package/dist/domains/comet-native/native-repair-integration.d.ts +42 -0
- package/dist/domains/comet-native/native-repair-integration.d.ts.map +1 -0
- package/dist/domains/comet-native/native-repair-integration.js +184 -0
- package/dist/domains/comet-native/native-repair-integration.js.map +1 -0
- package/dist/domains/comet-native/native-repair-runtime.d.ts +73 -0
- package/dist/domains/comet-native/native-repair-runtime.d.ts.map +1 -0
- package/dist/domains/comet-native/native-repair-runtime.js +509 -0
- package/dist/domains/comet-native/native-repair-runtime.js.map +1 -0
- package/dist/domains/comet-native/native-repair-stagnation.d.ts +85 -0
- package/dist/domains/comet-native/native-repair-stagnation.d.ts.map +1 -0
- package/dist/domains/comet-native/native-repair-stagnation.js +256 -0
- package/dist/domains/comet-native/native-repair-stagnation.js.map +1 -0
- package/dist/domains/comet-native/native-resume-view.d.ts +14 -0
- package/dist/domains/comet-native/native-resume-view.d.ts.map +1 -0
- package/dist/domains/comet-native/native-resume-view.js +105 -0
- package/dist/domains/comet-native/native-resume-view.js.map +1 -0
- package/dist/domains/comet-native/native-revision.d.ts +20 -0
- package/dist/domains/comet-native/native-revision.d.ts.map +1 -0
- package/dist/domains/comet-native/native-revision.js +32 -0
- package/dist/domains/comet-native/native-revision.js.map +1 -0
- package/dist/domains/comet-native/native-root-move.d.ts +20 -0
- package/dist/domains/comet-native/native-root-move.d.ts.map +1 -0
- package/dist/domains/comet-native/native-root-move.js +812 -0
- package/dist/domains/comet-native/native-root-move.js.map +1 -0
- package/dist/domains/comet-native/native-run-consistency.d.ts +3 -0
- package/dist/domains/comet-native/native-run-consistency.d.ts.map +1 -0
- package/dist/domains/comet-native/native-run-consistency.js +136 -0
- package/dist/domains/comet-native/native-run-consistency.js.map +1 -0
- package/dist/domains/comet-native/native-run-store.d.ts +41 -0
- package/dist/domains/comet-native/native-run-store.d.ts.map +1 -0
- package/dist/domains/comet-native/native-run-store.js +451 -0
- package/dist/domains/comet-native/native-run-store.js.map +1 -0
- package/dist/domains/comet-native/native-runtime-package.d.ts +16 -0
- package/dist/domains/comet-native/native-runtime-package.d.ts.map +1 -0
- package/dist/domains/comet-native/native-runtime-package.js +72 -0
- package/dist/domains/comet-native/native-runtime-package.js.map +1 -0
- package/dist/domains/comet-native/native-schema-migration.d.ts +11 -0
- package/dist/domains/comet-native/native-schema-migration.d.ts.map +1 -0
- package/dist/domains/comet-native/native-schema-migration.js +899 -0
- package/dist/domains/comet-native/native-schema-migration.js.map +1 -0
- package/dist/domains/comet-native/native-selection.d.ts +12 -0
- package/dist/domains/comet-native/native-selection.d.ts.map +1 -0
- package/dist/domains/comet-native/native-selection.js +52 -0
- package/dist/domains/comet-native/native-selection.js.map +1 -0
- package/dist/domains/comet-native/native-sensitive-paths.d.ts +7 -0
- package/dist/domains/comet-native/native-sensitive-paths.d.ts.map +1 -0
- package/dist/domains/comet-native/native-sensitive-paths.js +69 -0
- package/dist/domains/comet-native/native-sensitive-paths.js.map +1 -0
- package/dist/domains/comet-native/native-snapshot.d.ts +74 -0
- package/dist/domains/comet-native/native-snapshot.d.ts.map +1 -0
- package/dist/domains/comet-native/native-snapshot.js +2308 -0
- package/dist/domains/comet-native/native-snapshot.js.map +1 -0
- package/dist/domains/comet-native/native-specs.d.ts +12 -0
- package/dist/domains/comet-native/native-specs.d.ts.map +1 -0
- package/dist/domains/comet-native/native-specs.js +270 -0
- package/dist/domains/comet-native/native-specs.js.map +1 -0
- package/dist/domains/comet-native/native-trajectory-limits.d.ts +7 -0
- package/dist/domains/comet-native/native-trajectory-limits.d.ts.map +1 -0
- package/dist/domains/comet-native/native-trajectory-limits.js +13 -0
- package/dist/domains/comet-native/native-trajectory-limits.js.map +1 -0
- package/dist/domains/comet-native/native-trajectory-recovery.d.ts +37 -0
- package/dist/domains/comet-native/native-trajectory-recovery.d.ts.map +1 -0
- package/dist/domains/comet-native/native-trajectory-recovery.js +133 -0
- package/dist/domains/comet-native/native-trajectory-recovery.js.map +1 -0
- package/dist/domains/comet-native/native-trajectory.d.ts +16 -0
- package/dist/domains/comet-native/native-trajectory.d.ts.map +1 -0
- package/dist/domains/comet-native/native-trajectory.js +27 -0
- package/dist/domains/comet-native/native-trajectory.js.map +1 -0
- package/dist/domains/comet-native/native-transaction.d.ts +45 -0
- package/dist/domains/comet-native/native-transaction.d.ts.map +1 -0
- package/dist/domains/comet-native/native-transaction.js +846 -0
- package/dist/domains/comet-native/native-transaction.js.map +1 -0
- package/dist/domains/comet-native/native-transition-evidence.d.ts +4 -0
- package/dist/domains/comet-native/native-transition-evidence.d.ts.map +1 -0
- package/dist/domains/comet-native/native-transition-evidence.js +20 -0
- package/dist/domains/comet-native/native-transition-evidence.js.map +1 -0
- package/dist/domains/comet-native/native-transition-journal.d.ts +30 -0
- package/dist/domains/comet-native/native-transition-journal.d.ts.map +1 -0
- package/dist/domains/comet-native/native-transition-journal.js +819 -0
- package/dist/domains/comet-native/native-transition-journal.js.map +1 -0
- package/dist/domains/comet-native/native-transitions.d.ts +13 -0
- package/dist/domains/comet-native/native-transitions.d.ts.map +1 -0
- package/dist/domains/comet-native/native-transitions.js +632 -0
- package/dist/domains/comet-native/native-transitions.js.map +1 -0
- package/dist/domains/comet-native/native-types.d.ts +547 -0
- package/dist/domains/comet-native/native-types.d.ts.map +1 -0
- package/dist/domains/comet-native/native-types.js +8 -0
- package/dist/domains/comet-native/native-types.js.map +1 -0
- package/dist/domains/comet-native/native-verification-evidence.d.ts +86 -0
- package/dist/domains/comet-native/native-verification-evidence.d.ts.map +1 -0
- package/dist/domains/comet-native/native-verification-evidence.js +480 -0
- package/dist/domains/comet-native/native-verification-evidence.js.map +1 -0
- package/dist/domains/comet-native/native-verification-runtime.d.ts +58 -0
- package/dist/domains/comet-native/native-verification-runtime.d.ts.map +1 -0
- package/dist/domains/comet-native/native-verification-runtime.js +321 -0
- package/dist/domains/comet-native/native-verification-runtime.js.map +1 -0
- package/dist/domains/comet-native/native-verification-scope.d.ts +117 -0
- package/dist/domains/comet-native/native-verification-scope.d.ts.map +1 -0
- package/dist/domains/comet-native/native-verification-scope.js +1040 -0
- package/dist/domains/comet-native/native-verification-scope.js.map +1 -0
- package/dist/domains/comet-native/native-workspace.d.ts +46 -0
- package/dist/domains/comet-native/native-workspace.d.ts.map +1 -0
- package/dist/domains/comet-native/native-workspace.js +276 -0
- package/dist/domains/comet-native/native-workspace.js.map +1 -0
- package/dist/domains/dashboard/collector.d.ts.map +1 -1
- package/dist/domains/dashboard/collector.js +9 -1
- package/dist/domains/dashboard/collector.js.map +1 -1
- package/dist/domains/dashboard/native-adapter.d.ts +157 -0
- package/dist/domains/dashboard/native-adapter.d.ts.map +1 -0
- package/dist/domains/dashboard/native-adapter.js +302 -0
- package/dist/domains/dashboard/native-adapter.js.map +1 -0
- package/dist/domains/dashboard/native-collector.d.ts +6 -0
- package/dist/domains/dashboard/native-collector.d.ts.map +1 -0
- package/dist/domains/dashboard/native-collector.js +285 -0
- package/dist/domains/dashboard/native-collector.js.map +1 -0
- package/dist/domains/dashboard/types.d.ts +5 -0
- package/dist/domains/dashboard/types.d.ts.map +1 -1
- package/dist/domains/dashboard/web/assets/{arc-TGVUG2WN.js → arc-CxdiLJ50.js} +1 -1
- package/dist/domains/dashboard/web/assets/{architectureDiagram-3BPJPVTR-ASHmiHoP.js → architectureDiagram-3BPJPVTR-D5YCQu4e.js} +1 -1
- package/dist/domains/dashboard/web/assets/{blockDiagram-GPEHLZMM-CwASxBKe.js → blockDiagram-GPEHLZMM-8NfdCOQ8.js} +1 -1
- package/dist/domains/dashboard/web/assets/{c4Diagram-AAUBKEIU-BU8z_WNk.js → c4Diagram-AAUBKEIU-B3cFnNT7.js} +1 -1
- package/dist/domains/dashboard/web/assets/channel-B6AFly29.js +1 -0
- package/dist/domains/dashboard/web/assets/{chunk-2J33WTMH-CBGlUx4_.js → chunk-2J33WTMH-CBNfW-Dr.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-4BX2VUAB-CS-iGcFz.js → chunk-4BX2VUAB-vWyemRBr.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-55IACEB6-BUtCVJat.js → chunk-55IACEB6-H6haOo2C.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-727SXJPM-C94tj_pf.js → chunk-727SXJPM-D02UjGND.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-AQP2D5EJ-BFOKcpdG.js → chunk-AQP2D5EJ-wKsbOw_T.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-FMBD7UC4-Dmx8B-st.js → chunk-FMBD7UC4-eYFEZimP.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-ND2GUHAM-DYV00CNn.js → chunk-ND2GUHAM-fMaIDDI2.js} +1 -1
- package/dist/domains/dashboard/web/assets/{chunk-QZHKN3VN-DMJJyQnw.js → chunk-QZHKN3VN-C9rhF6Hm.js} +1 -1
- package/dist/domains/dashboard/web/assets/classDiagram-4FO5ZUOK-BcnrxOTt.js +1 -0
- package/dist/domains/dashboard/web/assets/classDiagram-v2-Q7XG4LA2-BcnrxOTt.js +1 -0
- package/dist/domains/dashboard/web/assets/{cose-bilkent-S5V4N54A-P4nE2rea.js → cose-bilkent-S5V4N54A-CbaShPub.js} +1 -1
- package/dist/domains/dashboard/web/assets/{cynefin-VYW2F7L2-C-R1HXDv.js → cynefin-VYW2F7L2-DN4emAQv.js} +1 -1
- package/dist/domains/dashboard/web/assets/{dagre-BM42HDAG-BH8d-C9z.js → dagre-BM42HDAG-BpSzbk_V.js} +1 -1
- package/dist/domains/dashboard/web/assets/demo-BuVau32v.js +25 -0
- package/dist/domains/dashboard/web/assets/{diagram-2AECGRRQ-DpXTgQIH.js → diagram-2AECGRRQ-CfXwGGpQ.js} +1 -1
- package/dist/domains/dashboard/web/assets/{diagram-5GNKFQAL-DSiFJzSw.js → diagram-5GNKFQAL-DMp0Zmot.js} +1 -1
- package/dist/domains/dashboard/web/assets/{diagram-KO2AKTUF-DULMkben.js → diagram-KO2AKTUF-C7ZvofsM.js} +1 -1
- package/dist/domains/dashboard/web/assets/{diagram-LMA3HP47-Pi8m_NO9.js → diagram-LMA3HP47-BjmxFnap.js} +1 -1
- package/dist/domains/dashboard/web/assets/{diagram-OG6HWLK6-BSe6bCWm.js → diagram-OG6HWLK6-DFBG1Iqy.js} +1 -1
- package/dist/domains/dashboard/web/assets/{erDiagram-TEJ5UH35-zb-FEcbq.js → erDiagram-TEJ5UH35-MpNT7dzt.js} +1 -1
- package/dist/domains/dashboard/web/assets/{flowDiagram-I6XJVG4X-Dr-Gk0z2.js → flowDiagram-I6XJVG4X-_1wAR6c1.js} +1 -1
- package/dist/domains/dashboard/web/assets/{ganttDiagram-6RSMTGT7-qs6Zb2A6.js → ganttDiagram-6RSMTGT7-DJKjDeWl.js} +1 -1
- package/dist/domains/dashboard/web/assets/{gitGraphDiagram-PVQCEYII-BQ314orT.js → gitGraphDiagram-PVQCEYII-BL7yUEn-.js} +1 -1
- package/dist/domains/dashboard/web/assets/index-CjE9mHGb.js +24 -0
- package/dist/domains/dashboard/web/assets/index-D-AGxvI4.css +1 -0
- package/dist/domains/dashboard/web/assets/{infoDiagram-5YYISTIA-O0bQp98A.js → infoDiagram-5YYISTIA-Bt5fqrOd.js} +1 -1
- package/dist/domains/dashboard/web/assets/{ishikawaDiagram-YF4QCWOH-BPC7wbKh.js → ishikawaDiagram-YF4QCWOH-COCMcjBM.js} +1 -1
- package/dist/domains/dashboard/web/assets/{journeyDiagram-JHISSGLW-ohViOd4b.js → journeyDiagram-JHISSGLW-DGcpcWny.js} +1 -1
- package/dist/domains/dashboard/web/assets/{kanban-definition-UN3LZRKU-CHleNdbG.js → kanban-definition-UN3LZRKU-QSBNsWMP.js} +1 -1
- package/dist/domains/dashboard/web/assets/{linear-CyqiW--Q.js → linear-9uyeeBZf.js} +1 -1
- package/dist/domains/dashboard/web/assets/{mermaid.core-qS9XvtaK.js → mermaid.core-CkNdB471.js} +4 -4
- package/dist/domains/dashboard/web/assets/{mindmap-definition-RKZ34NQL-oaGkqO9l.js → mindmap-definition-RKZ34NQL-CiNxI0gP.js} +1 -1
- package/dist/domains/dashboard/web/assets/{pieDiagram-4H26LBE5-CPcB-AFh.js → pieDiagram-4H26LBE5-BLJ6QnRO.js} +1 -1
- package/dist/domains/dashboard/web/assets/{quadrantDiagram-W4KKPZXB-LFy52Qtj.js → quadrantDiagram-W4KKPZXB-CflW-eFH.js} +1 -1
- package/dist/domains/dashboard/web/assets/{requirementDiagram-4Y6WPE33-BdjeaYP_.js → requirementDiagram-4Y6WPE33-B9HLmRWH.js} +1 -1
- package/dist/domains/dashboard/web/assets/{sankeyDiagram-5OEKKPKP-DDJ4styL.js → sankeyDiagram-5OEKKPKP-6xnco5k5.js} +1 -1
- package/dist/domains/dashboard/web/assets/{sequenceDiagram-3UESZ5HK-BL5bFNoz.js → sequenceDiagram-3UESZ5HK-Dp1rujNZ.js} +1 -1
- package/dist/domains/dashboard/web/assets/{stateDiagram-AJRCARHV-BNUfikui.js → stateDiagram-AJRCARHV-C1Xc90ek.js} +1 -1
- package/dist/domains/dashboard/web/assets/stateDiagram-v2-BHNVJYJU-p-qmzXyE.js +1 -0
- package/dist/domains/dashboard/web/assets/{timeline-definition-PNZ67QCA--ps2uu6L.js → timeline-definition-PNZ67QCA-g9s-m5Z4.js} +1 -1
- package/dist/domains/dashboard/web/assets/{vennDiagram-CIIHVFJN-C2q8q26c.js → vennDiagram-CIIHVFJN-rkEyhyCc.js} +1 -1
- package/dist/domains/dashboard/web/assets/{wardleyDiagram-YWT4CUSO-BUH9gXqd.js → wardleyDiagram-YWT4CUSO-CJLrWGXc.js} +1 -1
- package/dist/domains/dashboard/web/assets/{xychartDiagram-2RQKCTM6-B-DielEy.js → xychartDiagram-2RQKCTM6-BxJvPNqo.js} +1 -1
- package/dist/domains/dashboard/web/index.html +2 -2
- package/dist/domains/engine/storage-layout.d.ts +14 -0
- package/dist/domains/engine/storage-layout.d.ts.map +1 -0
- package/dist/domains/engine/storage-layout.js +32 -0
- package/dist/domains/engine/storage-layout.js.map +1 -0
- package/dist/domains/engine/storage-run.d.ts +9 -0
- package/dist/domains/engine/storage-run.d.ts.map +1 -0
- package/dist/domains/engine/storage-run.js +116 -0
- package/dist/domains/engine/storage-run.js.map +1 -0
- package/dist/domains/factory/package.d.ts.map +1 -1
- package/dist/domains/factory/package.js +5 -3
- package/dist/domains/factory/package.js.map +1 -1
- package/dist/domains/integrations/openspec.d.ts.map +1 -1
- package/dist/domains/integrations/openspec.js +8 -6
- package/dist/domains/integrations/openspec.js.map +1 -1
- package/dist/domains/skill/platform-inspect.d.ts +6 -2
- package/dist/domains/skill/platform-inspect.d.ts.map +1 -1
- package/dist/domains/skill/platform-inspect.js +52 -8
- package/dist/domains/skill/platform-inspect.js.map +1 -1
- package/dist/domains/skill/platform-install.d.ts +18 -7
- package/dist/domains/skill/platform-install.d.ts.map +1 -1
- package/dist/domains/skill/platform-install.js +338 -71
- package/dist/domains/skill/platform-install.js.map +1 -1
- package/dist/domains/skill/project-instructions.d.ts.map +1 -1
- package/dist/domains/skill/project-instructions.js +12 -6
- package/dist/domains/skill/project-instructions.js.map +1 -1
- package/dist/domains/skill/uninstall.d.ts.map +1 -1
- package/dist/domains/skill/uninstall.js +15 -3
- package/dist/domains/skill/uninstall.js.map +1 -1
- package/dist/domains/workflow-contract/builtins.d.ts +6 -0
- package/dist/domains/workflow-contract/builtins.d.ts.map +1 -1
- package/dist/domains/workflow-contract/builtins.js +137 -0
- package/dist/domains/workflow-contract/builtins.js.map +1 -1
- package/dist/domains/workflow-contract/index.d.ts +3 -1
- package/dist/domains/workflow-contract/index.d.ts.map +1 -1
- package/dist/domains/workflow-contract/index.js +2 -1
- package/dist/domains/workflow-contract/index.js.map +1 -1
- package/dist/domains/workflow-contract/normalize.d.ts.map +1 -1
- package/dist/domains/workflow-contract/normalize.js +28 -10
- package/dist/domains/workflow-contract/normalize.js.map +1 -1
- package/dist/domains/workflow-contract/project-config.d.ts +7 -0
- package/dist/domains/workflow-contract/project-config.d.ts.map +1 -0
- package/dist/domains/workflow-contract/project-config.js +110 -0
- package/dist/domains/workflow-contract/project-config.js.map +1 -0
- package/dist/domains/workflow-contract/types.d.ts +4 -2
- package/dist/domains/workflow-contract/types.d.ts.map +1 -1
- package/dist/domains/workflow-contract/validation.d.ts.map +1 -1
- package/dist/domains/workflow-contract/validation.js +14 -5
- package/dist/domains/workflow-contract/validation.js.map +1 -1
- package/dist/platform/install/detect.d.ts +3 -1
- package/dist/platform/install/detect.d.ts.map +1 -1
- package/dist/platform/install/detect.js +4 -1
- package/dist/platform/install/detect.js.map +1 -1
- package/dist/platform/install/project-registry.js +2 -2
- package/dist/platform/install/project-registry.js.map +1 -1
- package/dist/platform/paths/repository-layout.d.ts +16 -0
- package/dist/platform/paths/repository-layout.d.ts.map +1 -1
- package/dist/platform/paths/repository-layout.js.map +1 -1
- package/eval/local/README.md +50 -0
- package/eval/local/scripts/compare_baselines.py +127 -1
- package/eval/local/scripts/regression_check.py +3 -4
- package/eval/local/tasks/comet-native-clarification/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-clarification/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-clarification/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-clarification/instruction.md +7 -0
- package/eval/local/tasks/comet-native-clarification/task.toml +40 -0
- package/eval/local/tasks/comet-native-clarification/validation/test_native_clarification.py +193 -0
- package/eval/local/tasks/comet-native-clarification-modes/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-clarification-modes/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-clarification-modes/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-clarification-modes/instruction.md +13 -0
- package/eval/local/tasks/comet-native-clarification-modes/task.toml +39 -0
- package/eval/local/tasks/comet-native-clarification-modes/validation/test_native_clarification_modes.py +235 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/.comet/config.yaml +6 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/Dockerfile +28 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/docs/comet/changes/add-character-counting/brief.md +17 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/docs/comet/changes/add-character-counting/comet-state.yaml +12 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/docs/comet/changes/add-character-counting/runtime/transition.json +69 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/docs/comet/changes/add-character-counting/specs/character-counting/spec.md +3 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/test_wordcount.py +20 -0
- package/eval/local/tasks/comet-native-interrupted-transition/environment/wordcount.py +27 -0
- package/eval/local/tasks/comet-native-interrupted-transition/instruction.md +7 -0
- package/eval/local/tasks/comet-native-interrupted-transition/task.toml +35 -0
- package/eval/local/tasks/comet-native-interrupted-transition/validation/test_native_interrupted_transition.py +63 -0
- package/eval/local/tasks/comet-native-repository-fact/environment/Dockerfile +26 -0
- package/eval/local/tasks/comet-native-repository-fact/environment/PRODUCT_RULES.md +3 -0
- package/eval/local/tasks/comet-native-repository-fact/environment/test_wordcount.py +20 -0
- package/eval/local/tasks/comet-native-repository-fact/environment/wordcount.py +27 -0
- package/eval/local/tasks/comet-native-repository-fact/instruction.md +7 -0
- package/eval/local/tasks/comet-native-repository-fact/task.toml +35 -0
- package/eval/local/tasks/comet-native-repository-fact/validation/test_native_repository_fact.py +53 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/instruction.md +9 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/task.toml +40 -0
- package/eval/local/tasks/comet-native-wave-b-decision-resume/validation/test_native_wave_b_decision_resume.py +208 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/instruction.md +11 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/task.toml +36 -0
- package/eval/local/tasks/comet-native-wave-c-verification-integrity/validation/test_native_wave_c_verification_integrity.py +344 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/instruction.md +11 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/task.toml +37 -0
- package/eval/local/tasks/comet-native-wave-d-stagnation-stop/validation/test_native_wave_d_stagnation_stop.py +284 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/environment/.include-trusted-native-runtime +1 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/instruction.md +9 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/task.toml +37 -0
- package/eval/local/tasks/comet-native-wave-e-parallel-safety/validation/test_native_wave_e_parallel_safety.py +309 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/.include-current-comet-cli +1 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/Dockerfile +31 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/current-comet-package.json +18 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/current-comet.sh +14 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/test_wordcount.py +27 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/instruction.md +7 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/task.toml +36 -0
- package/eval/local/tasks/comet-native-wave-f-dashboard-readonly/validation/test_native_wave_f_dashboard_readonly.py +469 -0
- package/eval/local/tasks/comet-native-workflow/environment/Dockerfile +27 -0
- package/eval/local/tasks/comet-native-workflow/environment/test_wordcount.py +25 -0
- package/eval/local/tasks/comet-native-workflow/environment/wordcount.py +36 -0
- package/eval/local/tasks/comet-native-workflow/instruction.md +14 -0
- package/eval/local/tasks/comet-native-workflow/task.toml +36 -0
- package/eval/local/tasks/comet-native-workflow/validation/test_native_workflow.py +149 -0
- package/eval/local/tasks/index.yaml +61 -0
- package/eval/local/tests/conftest.py +407 -22
- package/eval/local/tests/scaffold/test_aligned_comparison.py +1012 -0
- package/eval/local/tests/scaffold/test_conftest_helpers.py +187 -7
- package/eval/local/tests/scaffold/test_logging.py +135 -1
- package/eval/local/tests/scaffold/test_manifests.py +2 -0
- package/eval/local/tests/scaffold/test_native_aligned_eval.py +346 -0
- package/eval/local/tests/scaffold/test_native_wave_evaluations.py +1889 -0
- package/eval/local/tests/scaffold/test_profiles.py +33 -0
- package/eval/local/tests/scaffold/test_sample_quality.py +57 -0
- package/eval/local/tests/scaffold/test_tasks.py +377 -1
- package/eval/local/tests/scaffold/test_treatments.py +59 -13
- package/eval/local/tests/scaffold/test_utils.py +277 -4
- package/eval/local/tests/tasks/__init__.py +1 -0
- package/eval/local/tests/tasks/test_tasks.py +104 -78
- package/eval/local/tests/tasks/test_validation_scripts.py +37 -0
- package/eval/local/treatments/comet/comet_native_phase1.yaml +66 -0
- package/eval/scaffold/python/aligned_comparison.py +1631 -0
- package/eval/scaffold/python/generic_llm_judge.py +3 -1
- package/eval/scaffold/python/llm_judge.py +0 -3
- package/eval/scaffold/python/logging.py +115 -16
- package/eval/scaffold/python/manifests.py +2 -0
- package/eval/scaffold/python/native_eval.py +138 -0
- package/eval/scaffold/python/pass_at_k.py +0 -1
- package/eval/scaffold/python/report_outputs.py +226 -44
- package/eval/scaffold/python/sample_quality.py +45 -2
- package/eval/scaffold/python/tasks.py +11 -0
- package/eval/scaffold/python/utils.py +108 -20
- package/eval/scaffold/python/validation/__init__.py +15 -5
- package/eval/scaffold/python/validation/comet_workflow.py +10 -5
- package/eval/scaffold/python/validation/generic_rubric.py +1 -2
- package/eval/scaffold/python/validation/native_wave.py +2154 -0
- package/eval/scaffold/python/validation/native_workflow.py +252 -0
- package/eval/scaffold/python/validation/rubric.py +0 -2
- package/eval/scaffold/shell/completion-point.sh +18 -0
- package/eval/scaffold/shell/decision-point.sh +35 -0
- package/eval/scaffold/shell/docker.sh +150 -9
- package/eval/scaffold/shell/run-claude-loop.sh +57 -27
- package/package.json +19 -5
- package/dist/domains/dashboard/web/assets/channel-kp6Ng2JJ.js +0 -1
- package/dist/domains/dashboard/web/assets/classDiagram-4FO5ZUOK-yBDjgMY_.js +0 -1
- package/dist/domains/dashboard/web/assets/classDiagram-v2-Q7XG4LA2-yBDjgMY_.js +0 -1
- package/dist/domains/dashboard/web/assets/demo-DQyfozbi.js +0 -1
- package/dist/domains/dashboard/web/assets/index-BLAfkNTu.css +0 -1
- package/dist/domains/dashboard/web/assets/index-DtLUOcAW.js +0 -24
- package/dist/domains/dashboard/web/assets/stateDiagram-v2-BHNVJYJU-DGrsmWts.js +0 -1
- package/eval/.env.example +0 -52
- package/eval/langsmith/.env.example +0 -16
|
@@ -0,0 +1,1631 @@
|
|
|
1
|
+
"""Strict, task-macro comparison for two eval experiments.
|
|
2
|
+
|
|
3
|
+
The legacy comparison report pools every run in one experiment. That is useful
|
|
4
|
+
for a quick dashboard, but it is not a valid pass@k comparison when treatments
|
|
5
|
+
were executed in separate experiments or when tasks have different numbers of
|
|
6
|
+
repetitions. This module pairs runs by ``(task, repetition, case hash)`` and
|
|
7
|
+
then computes each task's metric before taking the macro average.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import stat
|
|
17
|
+
import statistics
|
|
18
|
+
from collections import Counter, defaultdict
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Iterable, Sequence
|
|
22
|
+
|
|
23
|
+
from scaffold.python.logging import extract_events, parse_output
|
|
24
|
+
from scaffold.python.pass_at_k import compute_pass_metrics
|
|
25
|
+
from scaffold.python.sample_quality import quality_from_report
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
CASE_MANIFEST_SCHEMA_V1 = "comet.eval.case-manifest.v1"
|
|
29
|
+
CASE_MANIFEST_SCHEMA = "comet.eval.case-manifest.v2"
|
|
30
|
+
EXECUTION_IDENTITY_SCHEMA = "comet.eval.execution-identity.v1"
|
|
31
|
+
EXPECTED_CASE_MATRIX_SCHEMA = "comet.eval.expected-case-matrix.v1"
|
|
32
|
+
EXPECTED_CASE_MATRIX_FILENAME = "expected-case-matrix.json"
|
|
33
|
+
DURATION_SOURCE = "raw_stdout:extract_events"
|
|
34
|
+
MAX_RAW_STDOUT_BYTES = 64 * 1024 * 1024
|
|
35
|
+
MAX_EXPECTED_CASE_MATRIX_BYTES = 8 * 1024 * 1024
|
|
36
|
+
_EVAL_ROOT = Path(__file__).resolve().parents[2]
|
|
37
|
+
_REPORT_SUFFIX_RE = r"(?:-r(?P<rep>\d+))?$"
|
|
38
|
+
_CASE_IDENTIFIER_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,199}")
|
|
39
|
+
_HASHED_CASE_PARTS_V1 = (
|
|
40
|
+
"task_hash",
|
|
41
|
+
"instruction_hash",
|
|
42
|
+
"validator_hash",
|
|
43
|
+
"environment_hash",
|
|
44
|
+
"data_hash",
|
|
45
|
+
"prompt_hash",
|
|
46
|
+
)
|
|
47
|
+
_HASHED_CASE_PARTS_V2 = (
|
|
48
|
+
*_HASHED_CASE_PARTS_V1,
|
|
49
|
+
"runner_hash",
|
|
50
|
+
"controller_hash",
|
|
51
|
+
"execution_hash",
|
|
52
|
+
)
|
|
53
|
+
_EXECUTION_IDENTITY_HASH_KEYS = (
|
|
54
|
+
"image_id_hash",
|
|
55
|
+
"image_repo_digests_hash",
|
|
56
|
+
"image_ref_hash",
|
|
57
|
+
"claude_tool_version_hash",
|
|
58
|
+
"model_selection_hash",
|
|
59
|
+
"interaction_hash",
|
|
60
|
+
)
|
|
61
|
+
_RUNNER_FILES = (
|
|
62
|
+
"scaffold/shell/docker.sh",
|
|
63
|
+
"scaffold/shell/common.sh",
|
|
64
|
+
"scaffold/shell/run-claude-loop.sh",
|
|
65
|
+
"scaffold/shell/decision-point.sh",
|
|
66
|
+
"scaffold/shell/completion-point.sh",
|
|
67
|
+
)
|
|
68
|
+
_CONTROLLER_FILES = (
|
|
69
|
+
"local/tests/tasks/test_tasks.py",
|
|
70
|
+
"local/tests/conftest.py",
|
|
71
|
+
"scaffold/python/tasks.py",
|
|
72
|
+
"scaffold/python/treatments.py",
|
|
73
|
+
"scaffold/python/profiles.py",
|
|
74
|
+
"scaffold/python/manifests.py",
|
|
75
|
+
"scaffold/python/utils.py",
|
|
76
|
+
"scaffold/python/logging.py",
|
|
77
|
+
"scaffold/python/aligned_comparison.py",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class CaseManifest:
|
|
83
|
+
schema: str
|
|
84
|
+
task: str
|
|
85
|
+
case_hash: str
|
|
86
|
+
core_hash: str
|
|
87
|
+
task_hash: str
|
|
88
|
+
instruction_hash: str
|
|
89
|
+
validator_hash: str
|
|
90
|
+
environment_hash: str
|
|
91
|
+
data_hash: str
|
|
92
|
+
prompt_hash: str
|
|
93
|
+
runner_hash: str | None
|
|
94
|
+
controller_hash: str | None
|
|
95
|
+
execution_hash: str | None
|
|
96
|
+
execution_identity: dict[str, str] | None
|
|
97
|
+
source: str
|
|
98
|
+
run_bound: bool
|
|
99
|
+
execution_bound: bool
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class ExpectedCase:
|
|
104
|
+
task: str
|
|
105
|
+
treatment: str
|
|
106
|
+
rep: int
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True)
|
|
110
|
+
class ExpectedCaseMatrix:
|
|
111
|
+
matrix_hash: str
|
|
112
|
+
cases: tuple[ExpectedCase, ...]
|
|
113
|
+
|
|
114
|
+
def keys_for(self, treatment: str) -> frozenset[tuple[str, int]]:
|
|
115
|
+
return frozenset(
|
|
116
|
+
(case.task, case.rep) for case in self.cases if case.treatment == treatment
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True)
|
|
121
|
+
class ExpectedCaseMatrixLoad:
|
|
122
|
+
matrix: ExpectedCaseMatrix | None
|
|
123
|
+
error: str | None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(frozen=True)
|
|
127
|
+
class RunRecord:
|
|
128
|
+
task: str
|
|
129
|
+
rep: int
|
|
130
|
+
treatment: str
|
|
131
|
+
report: dict[str, Any]
|
|
132
|
+
report_path: Path
|
|
133
|
+
manifest: CaseManifest
|
|
134
|
+
manifest_valid: bool
|
|
135
|
+
include_in_analysis: bool
|
|
136
|
+
quality_reason: str
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class AlignmentIssue:
|
|
141
|
+
task: str
|
|
142
|
+
rep: int
|
|
143
|
+
reason: str
|
|
144
|
+
detail: str
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass(frozen=True)
|
|
148
|
+
class AlignedPair:
|
|
149
|
+
task: str
|
|
150
|
+
rep: int
|
|
151
|
+
case_hash: str
|
|
152
|
+
candidate: RunRecord
|
|
153
|
+
baseline: RunRecord
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass(frozen=True)
|
|
157
|
+
class AlignmentResult:
|
|
158
|
+
candidate_records: tuple[RunRecord, ...]
|
|
159
|
+
baseline_records: tuple[RunRecord, ...]
|
|
160
|
+
pairs: tuple[AlignedPair, ...]
|
|
161
|
+
issues: tuple[AlignmentIssue, ...]
|
|
162
|
+
tasks: tuple[str, ...]
|
|
163
|
+
expected_keys: int
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _sha256_bytes(value: bytes) -> str:
|
|
167
|
+
return "sha256:" + hashlib.sha256(value).hexdigest()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _hash_tree(path: Path) -> str:
|
|
171
|
+
"""Hash a file/tree without following symlinks."""
|
|
172
|
+
digest = hashlib.sha256()
|
|
173
|
+
if not path.exists() and not path.is_symlink():
|
|
174
|
+
digest.update(b"missing\0")
|
|
175
|
+
return "sha256:" + digest.hexdigest()
|
|
176
|
+
|
|
177
|
+
paths = [path] if path.is_file() or path.is_symlink() else sorted(path.rglob("*"))
|
|
178
|
+
for item in paths:
|
|
179
|
+
if item.name.startswith(".env"):
|
|
180
|
+
continue
|
|
181
|
+
if item.name == "__pycache__" or "__pycache__" in item.parts:
|
|
182
|
+
continue
|
|
183
|
+
if item.suffix in {".pyc", ".pyo"}:
|
|
184
|
+
continue
|
|
185
|
+
relative = item.name if item == path else item.relative_to(path).as_posix()
|
|
186
|
+
if item.is_symlink():
|
|
187
|
+
digest.update(f"L\0{relative}\0{item.readlink()}\0".encode("utf-8"))
|
|
188
|
+
elif item.is_file():
|
|
189
|
+
digest.update(f"F\0{relative}\0".encode("utf-8"))
|
|
190
|
+
digest.update(item.read_bytes())
|
|
191
|
+
digest.update(b"\0")
|
|
192
|
+
return "sha256:" + digest.hexdigest()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _case_hash(task: str, components: dict[str, str], *, schema: str) -> str:
|
|
196
|
+
payload = {
|
|
197
|
+
"schema": schema,
|
|
198
|
+
"task": task,
|
|
199
|
+
**components,
|
|
200
|
+
}
|
|
201
|
+
return _sha256_bytes(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8"))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _case_core_hash(task: str, components: dict[str, str]) -> str:
|
|
205
|
+
return _case_hash(
|
|
206
|
+
task,
|
|
207
|
+
{key: components[key] for key in _HASHED_CASE_PARTS_V1},
|
|
208
|
+
schema="comet.eval.case-core.v1",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _hash_named_files(paths: Sequence[str]) -> str:
|
|
213
|
+
components = {path: _hash_tree(_EVAL_ROOT / path) for path in paths}
|
|
214
|
+
return _sha256_bytes(
|
|
215
|
+
json.dumps(components, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _is_hash(value: Any) -> bool:
|
|
220
|
+
return isinstance(value, str) and re.fullmatch(r"sha256:[0-9a-f]{64}", value) is not None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _hash_private_value(value: Any) -> str:
|
|
224
|
+
"""Hash configuration without persisting its potentially sensitive text."""
|
|
225
|
+
return _sha256_bytes(
|
|
226
|
+
json.dumps(value, sort_keys=True, separators=(",", ":"), default=str).encode("utf-8")
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def build_execution_identity(
|
|
231
|
+
docker_identity: dict[str, Any],
|
|
232
|
+
*,
|
|
233
|
+
model: str | None,
|
|
234
|
+
model_config: dict[str, str | None] | None = None,
|
|
235
|
+
interaction: Any,
|
|
236
|
+
) -> dict[str, str]:
|
|
237
|
+
"""Build the safe, report-bound execution identity.
|
|
238
|
+
|
|
239
|
+
Docker supplies hashes derived from the immutable image and the actual
|
|
240
|
+
``claude --version`` output. Model and interaction text are hashed by the
|
|
241
|
+
controller; only bounded enums and digests enter reports.
|
|
242
|
+
"""
|
|
243
|
+
if docker_identity.get("schema") != EXECUTION_IDENTITY_SCHEMA:
|
|
244
|
+
raise ValueError("Docker execution identity schema is invalid")
|
|
245
|
+
allowed = {"schema", "runtime_image_id", *_EXECUTION_IDENTITY_HASH_KEYS[:4]}
|
|
246
|
+
unknown = set(docker_identity) - allowed
|
|
247
|
+
if unknown:
|
|
248
|
+
raise ValueError(f"Docker execution identity has unknown keys: {sorted(unknown)}")
|
|
249
|
+
runtime_image_id = docker_identity.get("runtime_image_id")
|
|
250
|
+
if not isinstance(runtime_image_id, str) or not re.fullmatch(
|
|
251
|
+
r"sha256:[0-9a-f]{64}", runtime_image_id
|
|
252
|
+
):
|
|
253
|
+
raise ValueError("Docker execution identity does not include an immutable image ID")
|
|
254
|
+
for key in _EXECUTION_IDENTITY_HASH_KEYS[:4]:
|
|
255
|
+
if not _is_hash(docker_identity.get(key)):
|
|
256
|
+
raise ValueError(f"Docker execution identity {key} is invalid")
|
|
257
|
+
if docker_identity["image_id_hash"] != _sha256_bytes(runtime_image_id.encode("utf-8")):
|
|
258
|
+
raise ValueError("Docker execution identity image hash does not match its runtime image")
|
|
259
|
+
|
|
260
|
+
mode = getattr(interaction, "mode", None) or "none"
|
|
261
|
+
if mode not in {"none", "auto_user"}:
|
|
262
|
+
mode = "custom"
|
|
263
|
+
max_turns = getattr(interaction, "max_turns", None)
|
|
264
|
+
if isinstance(max_turns, bool) or not isinstance(max_turns, int) or not 1 <= max_turns <= 1000:
|
|
265
|
+
raise ValueError("Interaction max_turns must be between 1 and 1000")
|
|
266
|
+
interaction_payload = {
|
|
267
|
+
"mode": mode,
|
|
268
|
+
"max_turns": max_turns,
|
|
269
|
+
"simulator_prompt": getattr(interaction, "simulator_prompt", None),
|
|
270
|
+
"decision_patterns": list(getattr(interaction, "decision_patterns", ()) or ()),
|
|
271
|
+
"decision_reply": getattr(interaction, "decision_reply", None),
|
|
272
|
+
"continue_prompt": getattr(interaction, "continue_prompt", None),
|
|
273
|
+
"fresh_resume_marker": getattr(interaction, "fresh_resume_marker", None),
|
|
274
|
+
}
|
|
275
|
+
model_source = "explicit" if model else "runtime-default"
|
|
276
|
+
normalized_model_config = {
|
|
277
|
+
key: value
|
|
278
|
+
for key, value in sorted((model_config or {}).items())
|
|
279
|
+
if isinstance(key, str) and isinstance(value, (str, type(None)))
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
"schema": EXECUTION_IDENTITY_SCHEMA,
|
|
283
|
+
**{key: docker_identity[key] for key in _EXECUTION_IDENTITY_HASH_KEYS[:4]},
|
|
284
|
+
"model_selection_hash": _hash_private_value(
|
|
285
|
+
{
|
|
286
|
+
"source": model_source,
|
|
287
|
+
"selection": model or "runtime-default",
|
|
288
|
+
"runtime_config": normalized_model_config,
|
|
289
|
+
}
|
|
290
|
+
),
|
|
291
|
+
"interaction_hash": _hash_private_value(interaction_payload),
|
|
292
|
+
"model_source": model_source,
|
|
293
|
+
"interaction_mode": mode,
|
|
294
|
+
"interaction_max_turns": str(max_turns),
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _validated_execution_identity(raw: Any) -> dict[str, str] | None:
|
|
299
|
+
if not isinstance(raw, dict):
|
|
300
|
+
return None
|
|
301
|
+
allowed = {
|
|
302
|
+
"schema",
|
|
303
|
+
*_EXECUTION_IDENTITY_HASH_KEYS,
|
|
304
|
+
"model_source",
|
|
305
|
+
"interaction_mode",
|
|
306
|
+
"interaction_max_turns",
|
|
307
|
+
}
|
|
308
|
+
if set(raw) != allowed or raw.get("schema") != EXECUTION_IDENTITY_SCHEMA:
|
|
309
|
+
return None
|
|
310
|
+
if not all(_is_hash(raw.get(key)) for key in _EXECUTION_IDENTITY_HASH_KEYS):
|
|
311
|
+
return None
|
|
312
|
+
if raw.get("model_source") not in {"explicit", "runtime-default"}:
|
|
313
|
+
return None
|
|
314
|
+
if raw.get("interaction_mode") not in {"none", "auto_user", "custom"}:
|
|
315
|
+
return None
|
|
316
|
+
try:
|
|
317
|
+
max_turns = int(raw.get("interaction_max_turns", ""))
|
|
318
|
+
except (TypeError, ValueError):
|
|
319
|
+
return None
|
|
320
|
+
if not 1 <= max_turns <= 1000 or str(max_turns) != raw.get("interaction_max_turns"):
|
|
321
|
+
return None
|
|
322
|
+
return {key: raw[key] for key in sorted(allowed)}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def build_case_manifest(
|
|
326
|
+
task: str,
|
|
327
|
+
tasks_dir: Path,
|
|
328
|
+
*,
|
|
329
|
+
execution_identity: dict[str, str] | None = None,
|
|
330
|
+
) -> CaseManifest:
|
|
331
|
+
"""Build a manifest for the canonical task and, for new runs, its execution."""
|
|
332
|
+
task_dir = tasks_dir / task
|
|
333
|
+
instruction_path = task_dir / "instruction.md"
|
|
334
|
+
task_hash = _hash_tree(task_dir / "task.toml")
|
|
335
|
+
instruction_hash = _hash_tree(instruction_path)
|
|
336
|
+
validator_components = {
|
|
337
|
+
"task_validation": _hash_tree(task_dir / "validation"),
|
|
338
|
+
"shared_validation": _hash_tree(_EVAL_ROOT / "scaffold" / "python" / "validation"),
|
|
339
|
+
"native_adapter": _hash_tree(_EVAL_ROOT / "scaffold" / "python" / "native_eval.py"),
|
|
340
|
+
}
|
|
341
|
+
validator_hash = _sha256_bytes(
|
|
342
|
+
json.dumps(validator_components, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
343
|
+
)
|
|
344
|
+
environment_hash = _hash_tree(task_dir / "environment")
|
|
345
|
+
data_hash = _hash_tree(task_dir / "data")
|
|
346
|
+
instruction_bytes = instruction_path.read_bytes() if instruction_path.is_file() else b"missing"
|
|
347
|
+
prompt_components = {
|
|
348
|
+
"instruction": _sha256_bytes(b"canonical-task-prompt-v1\0" + instruction_bytes),
|
|
349
|
+
"task_renderer": _hash_tree(_EVAL_ROOT / "scaffold" / "python" / "tasks.py"),
|
|
350
|
+
"native_adapter": validator_components["native_adapter"],
|
|
351
|
+
}
|
|
352
|
+
prompt_hash = _sha256_bytes(
|
|
353
|
+
json.dumps(prompt_components, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
354
|
+
)
|
|
355
|
+
base_components = {
|
|
356
|
+
"task_hash": task_hash,
|
|
357
|
+
"instruction_hash": instruction_hash,
|
|
358
|
+
"validator_hash": validator_hash,
|
|
359
|
+
"environment_hash": environment_hash,
|
|
360
|
+
"data_hash": data_hash,
|
|
361
|
+
"prompt_hash": prompt_hash,
|
|
362
|
+
}
|
|
363
|
+
if execution_identity is None:
|
|
364
|
+
schema = CASE_MANIFEST_SCHEMA_V1
|
|
365
|
+
components = base_components
|
|
366
|
+
runner_hash = controller_hash = execution_hash = None
|
|
367
|
+
validated_identity = None
|
|
368
|
+
source = "current-task-tree-fallback"
|
|
369
|
+
run_bound = False
|
|
370
|
+
execution_bound = False
|
|
371
|
+
else:
|
|
372
|
+
validated_identity = _validated_execution_identity(execution_identity)
|
|
373
|
+
if validated_identity is None:
|
|
374
|
+
raise ValueError("execution identity is not a safe canonical payload")
|
|
375
|
+
schema = CASE_MANIFEST_SCHEMA
|
|
376
|
+
runner_hash = _hash_named_files(_RUNNER_FILES)
|
|
377
|
+
controller_hash = _hash_named_files(_CONTROLLER_FILES)
|
|
378
|
+
execution_hash = _hash_private_value(validated_identity)
|
|
379
|
+
components = {
|
|
380
|
+
**base_components,
|
|
381
|
+
"runner_hash": runner_hash,
|
|
382
|
+
"controller_hash": controller_hash,
|
|
383
|
+
"execution_hash": execution_hash,
|
|
384
|
+
}
|
|
385
|
+
source = "report-bound-v2"
|
|
386
|
+
run_bound = True
|
|
387
|
+
execution_bound = True
|
|
388
|
+
case_hash = _case_hash(task, components, schema=schema)
|
|
389
|
+
core_hash = _case_core_hash(task, base_components)
|
|
390
|
+
return CaseManifest(
|
|
391
|
+
schema=schema,
|
|
392
|
+
task=task,
|
|
393
|
+
case_hash=case_hash,
|
|
394
|
+
core_hash=core_hash,
|
|
395
|
+
task_hash=task_hash,
|
|
396
|
+
instruction_hash=instruction_hash,
|
|
397
|
+
validator_hash=validator_hash,
|
|
398
|
+
environment_hash=environment_hash,
|
|
399
|
+
data_hash=data_hash,
|
|
400
|
+
prompt_hash=prompt_hash,
|
|
401
|
+
runner_hash=runner_hash,
|
|
402
|
+
controller_hash=controller_hash,
|
|
403
|
+
execution_hash=execution_hash,
|
|
404
|
+
execution_identity=validated_identity,
|
|
405
|
+
source=source,
|
|
406
|
+
run_bound=run_bound,
|
|
407
|
+
execution_bound=execution_bound,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def case_manifest_payload(manifest: CaseManifest) -> dict[str, Any]:
|
|
412
|
+
"""Serialize a case manifest into the report-bound wire format."""
|
|
413
|
+
payload: dict[str, Any] = {
|
|
414
|
+
"schema": manifest.schema,
|
|
415
|
+
"task": manifest.task,
|
|
416
|
+
"case_hash": manifest.case_hash,
|
|
417
|
+
**{key: getattr(manifest, key) for key in _HASHED_CASE_PARTS_V1},
|
|
418
|
+
}
|
|
419
|
+
if manifest.schema == CASE_MANIFEST_SCHEMA:
|
|
420
|
+
payload.update(
|
|
421
|
+
{
|
|
422
|
+
key: getattr(manifest, key)
|
|
423
|
+
for key in ("runner_hash", "controller_hash", "execution_hash")
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
payload["execution_identity"] = manifest.execution_identity
|
|
427
|
+
return payload
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _embedded_case_manifest(report: dict[str, Any], task: str) -> CaseManifest | None:
|
|
431
|
+
events = report.get("events_summary") or {}
|
|
432
|
+
raw = report.get("case_manifest") or events.get("case_manifest")
|
|
433
|
+
if not isinstance(raw, dict):
|
|
434
|
+
return None
|
|
435
|
+
schema = raw.get("schema")
|
|
436
|
+
if schema not in {CASE_MANIFEST_SCHEMA_V1, CASE_MANIFEST_SCHEMA} or raw.get("task") != task:
|
|
437
|
+
return None
|
|
438
|
+
hashed_parts = (
|
|
439
|
+
_HASHED_CASE_PARTS_V2 if schema == CASE_MANIFEST_SCHEMA else _HASHED_CASE_PARTS_V1
|
|
440
|
+
)
|
|
441
|
+
expected_keys = {"schema", "task", "case_hash", *hashed_parts}
|
|
442
|
+
if schema == CASE_MANIFEST_SCHEMA:
|
|
443
|
+
expected_keys.add("execution_identity")
|
|
444
|
+
if set(raw) != expected_keys:
|
|
445
|
+
return None
|
|
446
|
+
if not all(_is_hash(raw.get(key)) for key in hashed_parts):
|
|
447
|
+
return None
|
|
448
|
+
case_hash = raw.get("case_hash")
|
|
449
|
+
if not isinstance(case_hash, str) or not case_hash.startswith("sha256:"):
|
|
450
|
+
return None
|
|
451
|
+
components = {key: raw[key] for key in hashed_parts}
|
|
452
|
+
if case_hash != _case_hash(task, components, schema=schema):
|
|
453
|
+
return None
|
|
454
|
+
execution_identity = (
|
|
455
|
+
_validated_execution_identity(raw.get("execution_identity"))
|
|
456
|
+
if schema == CASE_MANIFEST_SCHEMA
|
|
457
|
+
else None
|
|
458
|
+
)
|
|
459
|
+
if schema == CASE_MANIFEST_SCHEMA and (
|
|
460
|
+
execution_identity is None
|
|
461
|
+
or raw["execution_hash"] != _hash_private_value(execution_identity)
|
|
462
|
+
):
|
|
463
|
+
return None
|
|
464
|
+
return CaseManifest(
|
|
465
|
+
schema=schema,
|
|
466
|
+
task=task,
|
|
467
|
+
case_hash=case_hash,
|
|
468
|
+
core_hash=_case_core_hash(task, components),
|
|
469
|
+
task_hash=raw["task_hash"],
|
|
470
|
+
instruction_hash=raw["instruction_hash"],
|
|
471
|
+
validator_hash=raw["validator_hash"],
|
|
472
|
+
environment_hash=raw["environment_hash"],
|
|
473
|
+
data_hash=raw["data_hash"],
|
|
474
|
+
prompt_hash=raw["prompt_hash"],
|
|
475
|
+
runner_hash=raw.get("runner_hash"),
|
|
476
|
+
controller_hash=raw.get("controller_hash"),
|
|
477
|
+
execution_hash=raw.get("execution_hash"),
|
|
478
|
+
execution_identity=execution_identity,
|
|
479
|
+
source="report-bound-v2" if schema == CASE_MANIFEST_SCHEMA else "report-bound-v1",
|
|
480
|
+
run_bound=True,
|
|
481
|
+
execution_bound=schema == CASE_MANIFEST_SCHEMA,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def expected_case_matrix_payload(
|
|
486
|
+
cases: Iterable[tuple[str, str, int] | ExpectedCase],
|
|
487
|
+
) -> dict[str, Any]:
|
|
488
|
+
"""Create a deterministic, controller-owned expected-case matrix."""
|
|
489
|
+
normalized: set[tuple[str, str, int]] = set()
|
|
490
|
+
for raw in cases:
|
|
491
|
+
if isinstance(raw, ExpectedCase):
|
|
492
|
+
task, treatment, rep = raw.task, raw.treatment, raw.rep
|
|
493
|
+
else:
|
|
494
|
+
task, treatment, rep = raw
|
|
495
|
+
if (
|
|
496
|
+
not isinstance(task, str)
|
|
497
|
+
or _CASE_IDENTIFIER_RE.fullmatch(task) is None
|
|
498
|
+
or not isinstance(treatment, str)
|
|
499
|
+
or _CASE_IDENTIFIER_RE.fullmatch(treatment) is None
|
|
500
|
+
):
|
|
501
|
+
raise ValueError("Expected case task and treatment must be non-empty strings")
|
|
502
|
+
if isinstance(rep, bool) or not isinstance(rep, int) or rep < 1:
|
|
503
|
+
raise ValueError("Expected case repetition must be a positive integer")
|
|
504
|
+
key = (task, treatment, rep)
|
|
505
|
+
if key in normalized:
|
|
506
|
+
raise ValueError("Expected case matrix contains a duplicate case")
|
|
507
|
+
normalized.add(key)
|
|
508
|
+
serialized = [
|
|
509
|
+
{"task": task, "treatment": treatment, "rep": rep}
|
|
510
|
+
for task, treatment, rep in sorted(normalized)
|
|
511
|
+
]
|
|
512
|
+
hash_payload = {"schema": EXPECTED_CASE_MATRIX_SCHEMA, "cases": serialized}
|
|
513
|
+
matrix_hash = _sha256_bytes(
|
|
514
|
+
json.dumps(hash_payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
515
|
+
)
|
|
516
|
+
return {**hash_payload, "matrix_hash": matrix_hash}
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def parse_expected_case_matrix(raw: Any) -> ExpectedCaseMatrix:
|
|
520
|
+
if not isinstance(raw, dict) or set(raw) != {"schema", "matrix_hash", "cases"}:
|
|
521
|
+
raise ValueError("expected case matrix has an invalid object shape")
|
|
522
|
+
if raw.get("schema") != EXPECTED_CASE_MATRIX_SCHEMA:
|
|
523
|
+
raise ValueError("expected case matrix schema is invalid")
|
|
524
|
+
cases_raw = raw.get("cases")
|
|
525
|
+
if not isinstance(cases_raw, list):
|
|
526
|
+
raise ValueError("expected case matrix cases must be a list")
|
|
527
|
+
cases: list[ExpectedCase] = []
|
|
528
|
+
seen: set[tuple[str, str, int]] = set()
|
|
529
|
+
for item in cases_raw:
|
|
530
|
+
if not isinstance(item, dict) or set(item) != {"task", "treatment", "rep"}:
|
|
531
|
+
raise ValueError("expected case matrix contains an invalid case")
|
|
532
|
+
task = item.get("task")
|
|
533
|
+
treatment = item.get("treatment")
|
|
534
|
+
rep = item.get("rep")
|
|
535
|
+
if (
|
|
536
|
+
not isinstance(task, str)
|
|
537
|
+
or _CASE_IDENTIFIER_RE.fullmatch(task) is None
|
|
538
|
+
or not isinstance(treatment, str)
|
|
539
|
+
or _CASE_IDENTIFIER_RE.fullmatch(treatment) is None
|
|
540
|
+
):
|
|
541
|
+
raise ValueError("expected case matrix contains an invalid task or treatment")
|
|
542
|
+
if isinstance(rep, bool) or not isinstance(rep, int) or rep < 1:
|
|
543
|
+
raise ValueError("expected case matrix contains an invalid repetition")
|
|
544
|
+
key = (task, treatment, rep)
|
|
545
|
+
if key in seen:
|
|
546
|
+
raise ValueError("expected case matrix contains a duplicate case")
|
|
547
|
+
seen.add(key)
|
|
548
|
+
cases.append(ExpectedCase(task=task, treatment=treatment, rep=rep))
|
|
549
|
+
if [(case.task, case.treatment, case.rep) for case in cases] != sorted(seen):
|
|
550
|
+
raise ValueError("expected case matrix cases are not canonically ordered")
|
|
551
|
+
canonical = expected_case_matrix_payload(cases)
|
|
552
|
+
if raw.get("matrix_hash") != canonical["matrix_hash"]:
|
|
553
|
+
raise ValueError("expected case matrix hash does not match its cases")
|
|
554
|
+
return ExpectedCaseMatrix(matrix_hash=canonical["matrix_hash"], cases=tuple(cases))
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def load_expected_case_matrix(experiment_dir: Path) -> ExpectedCaseMatrixLoad:
|
|
558
|
+
path = experiment_dir / EXPECTED_CASE_MATRIX_FILENAME
|
|
559
|
+
try:
|
|
560
|
+
before = path.lstat()
|
|
561
|
+
except FileNotFoundError:
|
|
562
|
+
return ExpectedCaseMatrixLoad(matrix=None, error=None)
|
|
563
|
+
except OSError:
|
|
564
|
+
return ExpectedCaseMatrixLoad(matrix=None, error="matrix metadata cannot be read")
|
|
565
|
+
if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
|
|
566
|
+
return ExpectedCaseMatrixLoad(matrix=None, error="matrix is not a regular file")
|
|
567
|
+
if before.st_size > MAX_EXPECTED_CASE_MATRIX_BYTES:
|
|
568
|
+
return ExpectedCaseMatrixLoad(matrix=None, error="matrix exceeds the size limit")
|
|
569
|
+
try:
|
|
570
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
571
|
+
after = path.lstat()
|
|
572
|
+
if _path_identity(before) != _path_identity(after):
|
|
573
|
+
return ExpectedCaseMatrixLoad(matrix=None, error="matrix changed during read")
|
|
574
|
+
return ExpectedCaseMatrixLoad(matrix=parse_expected_case_matrix(raw), error=None)
|
|
575
|
+
except OSError:
|
|
576
|
+
return ExpectedCaseMatrixLoad(matrix=None, error="matrix cannot be read")
|
|
577
|
+
except UnicodeError:
|
|
578
|
+
return ExpectedCaseMatrixLoad(matrix=None, error="matrix is not valid UTF-8")
|
|
579
|
+
except json.JSONDecodeError:
|
|
580
|
+
return ExpectedCaseMatrixLoad(matrix=None, error="matrix is not valid JSON")
|
|
581
|
+
except ValueError as error:
|
|
582
|
+
return ExpectedCaseMatrixLoad(matrix=None, error=str(error))
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def _report_identity(
|
|
586
|
+
report: dict[str, Any],
|
|
587
|
+
treatment: str,
|
|
588
|
+
report_path: Path | None = None,
|
|
589
|
+
) -> tuple[str, int] | None:
|
|
590
|
+
name = str(report.get("name") or "")
|
|
591
|
+
match = re.match(
|
|
592
|
+
rf"^(?P<task>.+)-{re.escape(treatment)}{_REPORT_SUFFIX_RE}",
|
|
593
|
+
name,
|
|
594
|
+
)
|
|
595
|
+
if not match:
|
|
596
|
+
return None
|
|
597
|
+
combined_sample = (
|
|
598
|
+
re.search(r"_sample(?P<rep>\d+)_from_", report_path.stem)
|
|
599
|
+
if report_path is not None
|
|
600
|
+
else None
|
|
601
|
+
)
|
|
602
|
+
rep_text = combined_sample.group("rep") if combined_sample else match.group("rep")
|
|
603
|
+
raw_rep = rep_text if rep_text is not None else report.get("rep", 1)
|
|
604
|
+
if isinstance(raw_rep, bool):
|
|
605
|
+
return None
|
|
606
|
+
try:
|
|
607
|
+
rep = int(raw_rep)
|
|
608
|
+
except (TypeError, ValueError):
|
|
609
|
+
return None
|
|
610
|
+
if rep < 1:
|
|
611
|
+
return None
|
|
612
|
+
return match.group("task"), rep
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def load_treatment_records(
|
|
616
|
+
experiment_dir: Path,
|
|
617
|
+
treatment: str,
|
|
618
|
+
*,
|
|
619
|
+
tasks_dir: Path,
|
|
620
|
+
) -> tuple[RunRecord, ...]:
|
|
621
|
+
reports_dir = experiment_dir / "reports"
|
|
622
|
+
manifests: dict[str, CaseManifest] = {}
|
|
623
|
+
records: list[RunRecord] = []
|
|
624
|
+
if not reports_dir.is_dir():
|
|
625
|
+
return ()
|
|
626
|
+
for path in sorted(reports_dir.glob("*.json")):
|
|
627
|
+
try:
|
|
628
|
+
report = json.loads(path.read_text(encoding="utf-8"))
|
|
629
|
+
except (OSError, json.JSONDecodeError):
|
|
630
|
+
continue
|
|
631
|
+
identity = _report_identity(report, treatment, path)
|
|
632
|
+
if identity is None:
|
|
633
|
+
continue
|
|
634
|
+
task, rep = identity
|
|
635
|
+
events = report.get("events_summary") or {}
|
|
636
|
+
raw_manifest = report.get("case_manifest") or events.get("case_manifest")
|
|
637
|
+
manifest = _embedded_case_manifest(report, task)
|
|
638
|
+
manifest_valid = manifest is not None or raw_manifest is None
|
|
639
|
+
if manifest is None:
|
|
640
|
+
manifest = manifests.setdefault(task, build_case_manifest(task, tasks_dir))
|
|
641
|
+
quality = quality_from_report(report, experiment_dir=experiment_dir)
|
|
642
|
+
records.append(
|
|
643
|
+
RunRecord(
|
|
644
|
+
task=task,
|
|
645
|
+
rep=rep,
|
|
646
|
+
treatment=treatment,
|
|
647
|
+
report=report,
|
|
648
|
+
report_path=path,
|
|
649
|
+
manifest=manifest,
|
|
650
|
+
manifest_valid=manifest_valid,
|
|
651
|
+
include_in_analysis=quality.include_in_analysis,
|
|
652
|
+
quality_reason=quality.reason_code,
|
|
653
|
+
)
|
|
654
|
+
)
|
|
655
|
+
return tuple(records)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def align_records(
|
|
659
|
+
candidate_records: Sequence[RunRecord],
|
|
660
|
+
baseline_records: Sequence[RunRecord],
|
|
661
|
+
*,
|
|
662
|
+
candidate_expected: frozenset[tuple[str, int]] | None = None,
|
|
663
|
+
baseline_expected: frozenset[tuple[str, int]] | None = None,
|
|
664
|
+
initial_issues: Sequence[AlignmentIssue] = (),
|
|
665
|
+
) -> AlignmentResult:
|
|
666
|
+
candidate_by_key: dict[tuple[str, int], list[RunRecord]] = defaultdict(list)
|
|
667
|
+
baseline_by_key: dict[tuple[str, int], list[RunRecord]] = defaultdict(list)
|
|
668
|
+
for record in candidate_records:
|
|
669
|
+
candidate_by_key[(record.task, record.rep)].append(record)
|
|
670
|
+
for record in baseline_records:
|
|
671
|
+
baseline_by_key[(record.task, record.rep)].append(record)
|
|
672
|
+
|
|
673
|
+
candidate_observed = set(candidate_by_key)
|
|
674
|
+
baseline_observed = set(baseline_by_key)
|
|
675
|
+
effective_candidate_expected = (
|
|
676
|
+
candidate_observed if candidate_expected is None else set(candidate_expected)
|
|
677
|
+
)
|
|
678
|
+
effective_baseline_expected = (
|
|
679
|
+
baseline_observed if baseline_expected is None else set(baseline_expected)
|
|
680
|
+
)
|
|
681
|
+
keys = sorted(
|
|
682
|
+
candidate_observed
|
|
683
|
+
| baseline_observed
|
|
684
|
+
| effective_candidate_expected
|
|
685
|
+
| effective_baseline_expected
|
|
686
|
+
)
|
|
687
|
+
tasks = tuple(sorted({task for task, _rep in keys}))
|
|
688
|
+
issues: list[AlignmentIssue] = list(initial_issues)
|
|
689
|
+
pairs: list[AlignedPair] = []
|
|
690
|
+
for task, rep in keys:
|
|
691
|
+
key = (task, rep)
|
|
692
|
+
candidate_matches = candidate_by_key.get((task, rep), [])
|
|
693
|
+
baseline_matches = baseline_by_key.get((task, rep), [])
|
|
694
|
+
matrix_mismatch = (
|
|
695
|
+
candidate_expected is not None
|
|
696
|
+
and baseline_expected is not None
|
|
697
|
+
and (key in effective_candidate_expected) != (key in effective_baseline_expected)
|
|
698
|
+
)
|
|
699
|
+
if matrix_mismatch:
|
|
700
|
+
omitted = (
|
|
701
|
+
"baseline matrix omits case"
|
|
702
|
+
if key in effective_candidate_expected
|
|
703
|
+
else "candidate matrix omits case"
|
|
704
|
+
)
|
|
705
|
+
issues.append(AlignmentIssue(task, rep, "expected-matrix-mismatch", omitted))
|
|
706
|
+
unexpected: list[str] = []
|
|
707
|
+
if candidate_expected is not None and candidate_matches and key not in candidate_expected:
|
|
708
|
+
unexpected.append("candidate")
|
|
709
|
+
if baseline_expected is not None and baseline_matches and key not in baseline_expected:
|
|
710
|
+
unexpected.append("baseline")
|
|
711
|
+
if unexpected:
|
|
712
|
+
issues.append(AlignmentIssue(task, rep, "unexpected-run", ", ".join(unexpected)))
|
|
713
|
+
continue
|
|
714
|
+
if len(candidate_matches) != 1 or len(baseline_matches) != 1:
|
|
715
|
+
if len(candidate_matches) > 1 or len(baseline_matches) > 1:
|
|
716
|
+
detail = f"candidate={len(candidate_matches)}, baseline={len(baseline_matches)}"
|
|
717
|
+
issues.append(AlignmentIssue(task, rep, "duplicate-run", detail))
|
|
718
|
+
elif not candidate_matches or not baseline_matches:
|
|
719
|
+
missing = []
|
|
720
|
+
if not candidate_matches:
|
|
721
|
+
missing.append("candidate")
|
|
722
|
+
if not baseline_matches:
|
|
723
|
+
missing.append("baseline")
|
|
724
|
+
issues.append(
|
|
725
|
+
AlignmentIssue(
|
|
726
|
+
task,
|
|
727
|
+
rep,
|
|
728
|
+
"missing-repetition",
|
|
729
|
+
f"missing {' and '.join(missing)} run",
|
|
730
|
+
)
|
|
731
|
+
)
|
|
732
|
+
continue
|
|
733
|
+
|
|
734
|
+
candidate = candidate_matches[0]
|
|
735
|
+
baseline = baseline_matches[0]
|
|
736
|
+
invalid_manifests = []
|
|
737
|
+
if not candidate.manifest_valid:
|
|
738
|
+
invalid_manifests.append("candidate")
|
|
739
|
+
if not baseline.manifest_valid:
|
|
740
|
+
invalid_manifests.append("baseline")
|
|
741
|
+
if invalid_manifests:
|
|
742
|
+
issues.append(
|
|
743
|
+
AlignmentIssue(
|
|
744
|
+
task,
|
|
745
|
+
rep,
|
|
746
|
+
"invalid-case-manifest",
|
|
747
|
+
", ".join(invalid_manifests),
|
|
748
|
+
)
|
|
749
|
+
)
|
|
750
|
+
continue
|
|
751
|
+
excluded = []
|
|
752
|
+
if not candidate.include_in_analysis:
|
|
753
|
+
excluded.append(f"candidate:{candidate.quality_reason}")
|
|
754
|
+
if not baseline.include_in_analysis:
|
|
755
|
+
excluded.append(f"baseline:{baseline.quality_reason}")
|
|
756
|
+
if excluded:
|
|
757
|
+
issues.append(AlignmentIssue(task, rep, "quality-excluded", ", ".join(excluded)))
|
|
758
|
+
continue
|
|
759
|
+
exact_execution_identity = (
|
|
760
|
+
candidate.manifest.execution_bound and baseline.manifest.execution_bound
|
|
761
|
+
)
|
|
762
|
+
comparison_hash = (
|
|
763
|
+
candidate.manifest.case_hash
|
|
764
|
+
if exact_execution_identity
|
|
765
|
+
else candidate.manifest.core_hash
|
|
766
|
+
)
|
|
767
|
+
baseline_comparison_hash = (
|
|
768
|
+
baseline.manifest.case_hash if exact_execution_identity else baseline.manifest.core_hash
|
|
769
|
+
)
|
|
770
|
+
if comparison_hash != baseline_comparison_hash:
|
|
771
|
+
issues.append(
|
|
772
|
+
AlignmentIssue(
|
|
773
|
+
task,
|
|
774
|
+
rep,
|
|
775
|
+
"case-hash-mismatch",
|
|
776
|
+
f"candidate={comparison_hash}, baseline={baseline_comparison_hash}",
|
|
777
|
+
)
|
|
778
|
+
)
|
|
779
|
+
continue
|
|
780
|
+
pairs.append(
|
|
781
|
+
AlignedPair(
|
|
782
|
+
task=task,
|
|
783
|
+
rep=rep,
|
|
784
|
+
case_hash=comparison_hash,
|
|
785
|
+
candidate=candidate,
|
|
786
|
+
baseline=baseline,
|
|
787
|
+
)
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
case_hashes_by_task: dict[str, set[str]] = defaultdict(set)
|
|
791
|
+
for pair in pairs:
|
|
792
|
+
case_hashes_by_task[pair.task].add(pair.case_hash)
|
|
793
|
+
drifting_tasks = {
|
|
794
|
+
task: hashes for task, hashes in case_hashes_by_task.items() if len(hashes) > 1
|
|
795
|
+
}
|
|
796
|
+
if drifting_tasks:
|
|
797
|
+
pairs = [pair for pair in pairs if pair.task not in drifting_tasks]
|
|
798
|
+
for task, hashes in sorted(drifting_tasks.items()):
|
|
799
|
+
issues.append(
|
|
800
|
+
AlignmentIssue(
|
|
801
|
+
task,
|
|
802
|
+
0,
|
|
803
|
+
"case-hash-drift",
|
|
804
|
+
", ".join(sorted(hashes)),
|
|
805
|
+
)
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
return AlignmentResult(
|
|
809
|
+
candidate_records=tuple(candidate_records),
|
|
810
|
+
baseline_records=tuple(baseline_records),
|
|
811
|
+
pairs=tuple(pairs),
|
|
812
|
+
issues=tuple(issues),
|
|
813
|
+
tasks=tasks,
|
|
814
|
+
expected_keys=len(keys),
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def _run_passed(report: dict[str, Any]) -> bool:
|
|
819
|
+
failed = report.get("checks_failed")
|
|
820
|
+
return report.get("passed") is True and isinstance(failed, list) and len(failed) == 0
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def task_macro_pass_metrics(
|
|
824
|
+
results_by_task: dict[str, Sequence[bool]],
|
|
825
|
+
ks: Sequence[int],
|
|
826
|
+
*,
|
|
827
|
+
total_tasks: int,
|
|
828
|
+
) -> dict[int, dict[str, float | int | None]]:
|
|
829
|
+
"""Compute task-level pass metrics and macro-average eligible tasks.
|
|
830
|
+
|
|
831
|
+
A task with fewer than ``k`` aligned repetitions is not silently evaluated
|
|
832
|
+
at a smaller k. It is excluded for that k and reported through coverage.
|
|
833
|
+
"""
|
|
834
|
+
output: dict[int, dict[str, float | int | None]] = {}
|
|
835
|
+
for k in ks:
|
|
836
|
+
per_task = [
|
|
837
|
+
compute_pass_metrics(values, k)
|
|
838
|
+
for values in results_by_task.values()
|
|
839
|
+
if len(values) >= k
|
|
840
|
+
]
|
|
841
|
+
output[k] = {
|
|
842
|
+
"pass_at_k": (
|
|
843
|
+
statistics.fmean(float(item["pass_at_k"]) for item in per_task)
|
|
844
|
+
if per_task
|
|
845
|
+
else None
|
|
846
|
+
),
|
|
847
|
+
"pass_pow_k": (
|
|
848
|
+
statistics.fmean(float(item["pass_pow_k"]) for item in per_task)
|
|
849
|
+
if per_task
|
|
850
|
+
else None
|
|
851
|
+
),
|
|
852
|
+
"eligible_tasks": len(per_task),
|
|
853
|
+
"total_tasks": total_tasks,
|
|
854
|
+
}
|
|
855
|
+
return output
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _artifact_path(
|
|
859
|
+
report: dict[str, Any],
|
|
860
|
+
experiment_dir: Path,
|
|
861
|
+
key: str,
|
|
862
|
+
*,
|
|
863
|
+
task: str,
|
|
864
|
+
treatment: str,
|
|
865
|
+
rep: int,
|
|
866
|
+
report_path: Path,
|
|
867
|
+
) -> Path | None:
|
|
868
|
+
reference = ((report.get("events_summary") or {}).get("artifact_references") or {}).get(key)
|
|
869
|
+
if not isinstance(reference, str) or not reference:
|
|
870
|
+
return None
|
|
871
|
+
comparison_root = experiment_dir.resolve()
|
|
872
|
+
raw_path = Path(reference)
|
|
873
|
+
candidate = Path(
|
|
874
|
+
os.path.abspath(raw_path if raw_path.is_absolute() else comparison_root / raw_path)
|
|
875
|
+
)
|
|
876
|
+
allowed_root = comparison_root
|
|
877
|
+
try:
|
|
878
|
+
candidate.relative_to(allowed_root)
|
|
879
|
+
except ValueError:
|
|
880
|
+
combined = re.search(
|
|
881
|
+
r"_sample(?P<rep>\d+)_from_(?P<source>[A-Za-z0-9._-]+)$",
|
|
882
|
+
report_path.stem,
|
|
883
|
+
)
|
|
884
|
+
if combined is None or int(combined.group("rep")) != rep:
|
|
885
|
+
return None
|
|
886
|
+
source = combined.group("source")
|
|
887
|
+
if source in {".", ".."}:
|
|
888
|
+
return None
|
|
889
|
+
allowed_root = comparison_root.parent / source
|
|
890
|
+
try:
|
|
891
|
+
candidate.relative_to(allowed_root)
|
|
892
|
+
except ValueError:
|
|
893
|
+
return None
|
|
894
|
+
|
|
895
|
+
report_name = report.get("name")
|
|
896
|
+
report_rep = report.get("rep", 1)
|
|
897
|
+
if not isinstance(report_name, str) or not report_name:
|
|
898
|
+
return None
|
|
899
|
+
if isinstance(report_rep, bool):
|
|
900
|
+
return None
|
|
901
|
+
try:
|
|
902
|
+
raw_rep = int(report_rep)
|
|
903
|
+
except (TypeError, ValueError):
|
|
904
|
+
return None
|
|
905
|
+
if raw_rep < 1:
|
|
906
|
+
return None
|
|
907
|
+
identity = _report_identity(report, treatment, report_path)
|
|
908
|
+
if identity != (task, rep):
|
|
909
|
+
return None
|
|
910
|
+
expected_names = {
|
|
911
|
+
f"{report_name}_rep{raw_rep}_stdout.json",
|
|
912
|
+
f"{report_name.replace('-', '_')}_rep{raw_rep}_stdout.json",
|
|
913
|
+
}
|
|
914
|
+
if candidate.name not in expected_names:
|
|
915
|
+
return None
|
|
916
|
+
try:
|
|
917
|
+
root_stat = allowed_root.lstat()
|
|
918
|
+
if stat.S_ISLNK(root_stat.st_mode) or not stat.S_ISDIR(root_stat.st_mode):
|
|
919
|
+
return None
|
|
920
|
+
except OSError:
|
|
921
|
+
return None
|
|
922
|
+
return candidate
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
def _path_identity(value: os.stat_result) -> tuple[int, int, int, int]:
|
|
926
|
+
return (value.st_dev, value.st_ino, value.st_mtime_ns, value.st_size)
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def _directory_chain(root: Path, parent: Path) -> tuple[tuple[Path, os.stat_result], ...]:
|
|
930
|
+
relative = parent.relative_to(root)
|
|
931
|
+
paths = [root]
|
|
932
|
+
cursor = root
|
|
933
|
+
for part in relative.parts:
|
|
934
|
+
cursor = cursor / part
|
|
935
|
+
paths.append(cursor)
|
|
936
|
+
chain: list[tuple[Path, os.stat_result]] = []
|
|
937
|
+
for path in paths:
|
|
938
|
+
current = path.lstat()
|
|
939
|
+
if stat.S_ISLNK(current.st_mode) or not stat.S_ISDIR(current.st_mode):
|
|
940
|
+
raise OSError(f"raw stdout parent is not a real directory: {path}")
|
|
941
|
+
chain.append((path, current))
|
|
942
|
+
return tuple(chain)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def _verify_directory_chain(chain: Sequence[tuple[Path, os.stat_result]]) -> None:
|
|
946
|
+
for path, expected in chain:
|
|
947
|
+
current = path.lstat()
|
|
948
|
+
if (
|
|
949
|
+
stat.S_ISLNK(current.st_mode)
|
|
950
|
+
or not stat.S_ISDIR(current.st_mode)
|
|
951
|
+
or _path_identity(current)[:3] != _path_identity(expected)[:3]
|
|
952
|
+
):
|
|
953
|
+
raise OSError(f"raw stdout parent changed during read: {path}")
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _read_raw_stdout(path: Path, allowed_root: Path) -> str | None:
|
|
957
|
+
try:
|
|
958
|
+
root = allowed_root.resolve(strict=True)
|
|
959
|
+
chain = _directory_chain(root, path.parent)
|
|
960
|
+
before = path.lstat()
|
|
961
|
+
if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
|
|
962
|
+
return None
|
|
963
|
+
if before.st_size > MAX_RAW_STDOUT_BYTES:
|
|
964
|
+
return None
|
|
965
|
+
_verify_directory_chain(chain)
|
|
966
|
+
flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
|
967
|
+
descriptor = os.open(path, flags)
|
|
968
|
+
try:
|
|
969
|
+
opened = os.fstat(descriptor)
|
|
970
|
+
after_open = path.lstat()
|
|
971
|
+
if (
|
|
972
|
+
not stat.S_ISREG(opened.st_mode)
|
|
973
|
+
or stat.S_ISLNK(after_open.st_mode)
|
|
974
|
+
or not stat.S_ISREG(after_open.st_mode)
|
|
975
|
+
or _path_identity(opened) != _path_identity(before)
|
|
976
|
+
or _path_identity(after_open) != _path_identity(before)
|
|
977
|
+
):
|
|
978
|
+
return None
|
|
979
|
+
_verify_directory_chain(chain)
|
|
980
|
+
chunks: list[bytes] = []
|
|
981
|
+
remaining = MAX_RAW_STDOUT_BYTES + 1
|
|
982
|
+
while remaining > 0:
|
|
983
|
+
chunk = os.read(descriptor, min(64 * 1024, remaining))
|
|
984
|
+
if not chunk:
|
|
985
|
+
break
|
|
986
|
+
chunks.append(chunk)
|
|
987
|
+
remaining -= len(chunk)
|
|
988
|
+
if remaining == 0:
|
|
989
|
+
return None
|
|
990
|
+
after_read = os.fstat(descriptor)
|
|
991
|
+
after_path = path.lstat()
|
|
992
|
+
_verify_directory_chain(chain)
|
|
993
|
+
if (
|
|
994
|
+
stat.S_ISLNK(after_path.st_mode)
|
|
995
|
+
or not stat.S_ISREG(after_path.st_mode)
|
|
996
|
+
or _path_identity(after_read) != _path_identity(opened)
|
|
997
|
+
or _path_identity(after_path) != _path_identity(opened)
|
|
998
|
+
):
|
|
999
|
+
return None
|
|
1000
|
+
return b"".join(chunks).decode("utf-8", errors="replace")
|
|
1001
|
+
finally:
|
|
1002
|
+
os.close(descriptor)
|
|
1003
|
+
except OSError:
|
|
1004
|
+
return None
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def recompute_duration(
|
|
1008
|
+
report: dict[str, Any],
|
|
1009
|
+
experiment_dir: Path,
|
|
1010
|
+
*,
|
|
1011
|
+
task: str,
|
|
1012
|
+
treatment: str,
|
|
1013
|
+
rep: int,
|
|
1014
|
+
report_path: Path,
|
|
1015
|
+
) -> float | None:
|
|
1016
|
+
"""Recompute additive duration from raw stdout; never use stale report duration."""
|
|
1017
|
+
telemetry = recompute_telemetry(
|
|
1018
|
+
report,
|
|
1019
|
+
experiment_dir,
|
|
1020
|
+
task=task,
|
|
1021
|
+
treatment=treatment,
|
|
1022
|
+
rep=rep,
|
|
1023
|
+
report_path=report_path,
|
|
1024
|
+
)
|
|
1025
|
+
if telemetry is None:
|
|
1026
|
+
return None
|
|
1027
|
+
duration = telemetry.get("duration_seconds")
|
|
1028
|
+
if isinstance(duration, bool) or not isinstance(duration, (int, float)):
|
|
1029
|
+
return None
|
|
1030
|
+
return float(duration)
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def recompute_telemetry(
|
|
1034
|
+
report: dict[str, Any],
|
|
1035
|
+
experiment_dir: Path,
|
|
1036
|
+
*,
|
|
1037
|
+
task: str,
|
|
1038
|
+
treatment: str,
|
|
1039
|
+
rep: int,
|
|
1040
|
+
report_path: Path,
|
|
1041
|
+
) -> dict[str, Any] | None:
|
|
1042
|
+
"""Recompute cumulative task telemetry from the controller-bound raw stdout."""
|
|
1043
|
+
path = _artifact_path(
|
|
1044
|
+
report,
|
|
1045
|
+
experiment_dir,
|
|
1046
|
+
"raw_stdout",
|
|
1047
|
+
task=task,
|
|
1048
|
+
treatment=treatment,
|
|
1049
|
+
rep=rep,
|
|
1050
|
+
report_path=report_path,
|
|
1051
|
+
)
|
|
1052
|
+
if path is None:
|
|
1053
|
+
return None
|
|
1054
|
+
try:
|
|
1055
|
+
path.relative_to(experiment_dir.resolve())
|
|
1056
|
+
allowed_root = experiment_dir
|
|
1057
|
+
except ValueError:
|
|
1058
|
+
combined = re.search(
|
|
1059
|
+
r"_sample(?P<rep>\d+)_from_(?P<source>[A-Za-z0-9._-]+)$",
|
|
1060
|
+
report_path.stem,
|
|
1061
|
+
)
|
|
1062
|
+
if combined is None or int(combined.group("rep")) != rep:
|
|
1063
|
+
return None
|
|
1064
|
+
allowed_root = experiment_dir.resolve().parent / combined.group("source")
|
|
1065
|
+
stdout = _read_raw_stdout(path, allowed_root)
|
|
1066
|
+
if stdout is None:
|
|
1067
|
+
return None
|
|
1068
|
+
telemetry = extract_events(parse_output(stdout))
|
|
1069
|
+
if telemetry.get("subject_invocations") == 0:
|
|
1070
|
+
return None
|
|
1071
|
+
return telemetry
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
def _hash_short(value: str) -> str:
|
|
1075
|
+
return value.removeprefix("sha256:")[:12]
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def _format_metric(value: float | int | None) -> str:
|
|
1079
|
+
return "N/A" if value is None else f"{float(value):.2f}"
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def _format_seconds(value: float | None) -> str:
|
|
1083
|
+
return "N/A" if value is None else f"{value:,.0f}s"
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
def _experiment_label(directory: Path) -> str:
|
|
1087
|
+
"""Render a stable public identifier without leaking a controller-local path."""
|
|
1088
|
+
return directory.name
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
def _manifest_by_task(records: Iterable[RunRecord]) -> dict[str, CaseManifest]:
|
|
1092
|
+
manifests: dict[str, CaseManifest] = {}
|
|
1093
|
+
for record in records:
|
|
1094
|
+
manifests.setdefault(record.task, record.manifest)
|
|
1095
|
+
return manifests
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def _results_by_task(
|
|
1099
|
+
pairs: Sequence[AlignedPair],
|
|
1100
|
+
side: str,
|
|
1101
|
+
) -> dict[str, list[bool]]:
|
|
1102
|
+
results: dict[str, list[bool]] = defaultdict(list)
|
|
1103
|
+
for pair in sorted(pairs, key=lambda item: (item.task, item.rep)):
|
|
1104
|
+
record = pair.candidate if side == "candidate" else pair.baseline
|
|
1105
|
+
results[pair.task].append(_run_passed(record.report))
|
|
1106
|
+
return dict(results)
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
def _duration_summary(
|
|
1110
|
+
pairs: Sequence[AlignedPair],
|
|
1111
|
+
side: str,
|
|
1112
|
+
experiment_dir: Path,
|
|
1113
|
+
) -> tuple[int, int, float | None, float | None, tuple[tuple[str, int], ...]]:
|
|
1114
|
+
values: list[float] = []
|
|
1115
|
+
missing: list[tuple[str, int]] = []
|
|
1116
|
+
for pair in pairs:
|
|
1117
|
+
record = pair.candidate if side == "candidate" else pair.baseline
|
|
1118
|
+
duration = recompute_duration(
|
|
1119
|
+
record.report,
|
|
1120
|
+
experiment_dir,
|
|
1121
|
+
task=record.task,
|
|
1122
|
+
treatment=record.treatment,
|
|
1123
|
+
rep=record.rep,
|
|
1124
|
+
report_path=record.report_path,
|
|
1125
|
+
)
|
|
1126
|
+
if duration is not None:
|
|
1127
|
+
values.append(duration)
|
|
1128
|
+
else:
|
|
1129
|
+
missing.append((pair.task, pair.rep))
|
|
1130
|
+
total = sum(values) if values else None
|
|
1131
|
+
average = statistics.fmean(values) if values else None
|
|
1132
|
+
return len(pairs), len(values), total, average, tuple(missing)
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
_EFFICIENCY_METRICS = (
|
|
1136
|
+
("subject_invocations", "Model starts/resumes", "count"),
|
|
1137
|
+
("num_turns", "Agent turns", "count"),
|
|
1138
|
+
("tool_calls", "Tool calls", "tool-count"),
|
|
1139
|
+
("duration_seconds", "Wall duration", "seconds"),
|
|
1140
|
+
("input_tokens", "Non-cache input tokens", "tokens"),
|
|
1141
|
+
("output_tokens", "Output tokens", "tokens"),
|
|
1142
|
+
("cache_read_input_tokens", "Cache-read input tokens", "tokens"),
|
|
1143
|
+
("total_tokens", "Total tokens incl. cache", "tokens"),
|
|
1144
|
+
("total_cost_usd", "Model cost", "usd"),
|
|
1145
|
+
("peak_context_input_tokens", "Peak context input", "tokens"),
|
|
1146
|
+
("p95_context_input_tokens", "P95 context input", "tokens"),
|
|
1147
|
+
("peak_context_occupancy_pct", "Peak context occupancy", "percent"),
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
def _telemetry_value(events: dict[str, Any], key: str, kind: str) -> float | None:
|
|
1152
|
+
value: Any = events.get(key)
|
|
1153
|
+
if kind == "tool-count":
|
|
1154
|
+
value = len(value) if isinstance(value, list) else value
|
|
1155
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
1156
|
+
return None
|
|
1157
|
+
return float(value)
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def _format_efficiency(value: float | None, kind: str) -> str:
|
|
1161
|
+
if value is None:
|
|
1162
|
+
return "N/A"
|
|
1163
|
+
if kind == "seconds":
|
|
1164
|
+
return f"{value:,.0f}s"
|
|
1165
|
+
if kind == "tokens":
|
|
1166
|
+
return f"{value:,.0f}"
|
|
1167
|
+
if kind == "usd":
|
|
1168
|
+
return f"${value:,.3f}"
|
|
1169
|
+
if kind == "percent":
|
|
1170
|
+
return f"{value:,.1f}%"
|
|
1171
|
+
return f"{value:,.2f}"
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
def _format_reduction(candidate: float | None, baseline: float | None) -> str:
|
|
1175
|
+
if candidate is None or baseline in (None, 0):
|
|
1176
|
+
return "N/A"
|
|
1177
|
+
change = (baseline - candidate) / baseline * 100
|
|
1178
|
+
return f"{abs(change):.1f}% {'less' if change >= 0 else 'more'}"
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def _telemetry_by_pair(
|
|
1182
|
+
pairs: Sequence[AlignedPair],
|
|
1183
|
+
candidate_dir: Path,
|
|
1184
|
+
baseline_dir: Path,
|
|
1185
|
+
) -> list[tuple[AlignedPair, dict[str, Any] | None, dict[str, Any] | None]]:
|
|
1186
|
+
output = []
|
|
1187
|
+
for pair in pairs:
|
|
1188
|
+
sides = []
|
|
1189
|
+
for record, directory in (
|
|
1190
|
+
(pair.candidate, candidate_dir),
|
|
1191
|
+
(pair.baseline, baseline_dir),
|
|
1192
|
+
):
|
|
1193
|
+
sides.append(
|
|
1194
|
+
recompute_telemetry(
|
|
1195
|
+
record.report,
|
|
1196
|
+
directory,
|
|
1197
|
+
task=record.task,
|
|
1198
|
+
treatment=record.treatment,
|
|
1199
|
+
rep=record.rep,
|
|
1200
|
+
report_path=record.report_path,
|
|
1201
|
+
)
|
|
1202
|
+
)
|
|
1203
|
+
output.append((pair, sides[0], sides[1]))
|
|
1204
|
+
return output
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
def _render_efficiency_table(
|
|
1208
|
+
telemetry: Sequence[tuple[AlignedPair, dict[str, Any] | None, dict[str, Any] | None]],
|
|
1209
|
+
*,
|
|
1210
|
+
successes_only: bool,
|
|
1211
|
+
) -> list[str]:
|
|
1212
|
+
selected = [
|
|
1213
|
+
item
|
|
1214
|
+
for item in telemetry
|
|
1215
|
+
if not successes_only
|
|
1216
|
+
or (_run_passed(item[0].candidate.report) and _run_passed(item[0].baseline.report))
|
|
1217
|
+
]
|
|
1218
|
+
lines = [
|
|
1219
|
+
"| Metric | Complete pairs | Candidate average | Baseline average | Candidate delta |",
|
|
1220
|
+
"|--------|----------------|-------------------|------------------|-----------------|",
|
|
1221
|
+
]
|
|
1222
|
+
for key, label, kind in _EFFICIENCY_METRICS:
|
|
1223
|
+
values = []
|
|
1224
|
+
for _pair, candidate, baseline in selected:
|
|
1225
|
+
if candidate is None or baseline is None:
|
|
1226
|
+
continue
|
|
1227
|
+
candidate_value = _telemetry_value(candidate, key, kind)
|
|
1228
|
+
baseline_value = _telemetry_value(baseline, key, kind)
|
|
1229
|
+
if candidate_value is not None and baseline_value is not None:
|
|
1230
|
+
values.append((candidate_value, baseline_value))
|
|
1231
|
+
candidate_average = statistics.fmean(value[0] for value in values) if values else None
|
|
1232
|
+
baseline_average = statistics.fmean(value[1] for value in values) if values else None
|
|
1233
|
+
lines.append(
|
|
1234
|
+
f"| {label} | {len(values)}/{len(selected)} | "
|
|
1235
|
+
f"{_format_efficiency(candidate_average, kind)} | "
|
|
1236
|
+
f"{_format_efficiency(baseline_average, kind)} | "
|
|
1237
|
+
f"{_format_reduction(candidate_average, baseline_average)} |"
|
|
1238
|
+
)
|
|
1239
|
+
return lines
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
def build_aligned_report(
|
|
1243
|
+
candidate_dir: Path,
|
|
1244
|
+
baseline_dir: Path,
|
|
1245
|
+
*,
|
|
1246
|
+
candidate_treatment: str,
|
|
1247
|
+
baseline_treatment: str,
|
|
1248
|
+
tasks_dir: Path,
|
|
1249
|
+
ks: Sequence[int] = (1, 2, 3),
|
|
1250
|
+
) -> str:
|
|
1251
|
+
"""Build a strict two-experiment comparison report."""
|
|
1252
|
+
if not ks or any(isinstance(k, bool) or k < 1 for k in ks):
|
|
1253
|
+
raise ValueError("ks must contain positive integers")
|
|
1254
|
+
normalized_ks = tuple(sorted(set(int(k) for k in ks)))
|
|
1255
|
+
candidate_records = load_treatment_records(
|
|
1256
|
+
candidate_dir,
|
|
1257
|
+
candidate_treatment,
|
|
1258
|
+
tasks_dir=tasks_dir,
|
|
1259
|
+
)
|
|
1260
|
+
baseline_records = load_treatment_records(
|
|
1261
|
+
baseline_dir,
|
|
1262
|
+
baseline_treatment,
|
|
1263
|
+
tasks_dir=tasks_dir,
|
|
1264
|
+
)
|
|
1265
|
+
candidate_matrix_load = load_expected_case_matrix(candidate_dir)
|
|
1266
|
+
baseline_matrix_load = load_expected_case_matrix(baseline_dir)
|
|
1267
|
+
if not candidate_records:
|
|
1268
|
+
raise ValueError(
|
|
1269
|
+
f"no {candidate_treatment} reports found in candidate experiment {candidate_dir}"
|
|
1270
|
+
)
|
|
1271
|
+
if not baseline_records:
|
|
1272
|
+
raise ValueError(
|
|
1273
|
+
f"no {baseline_treatment} reports found in baseline experiment {baseline_dir}"
|
|
1274
|
+
)
|
|
1275
|
+
matrix_issues: list[AlignmentIssue] = []
|
|
1276
|
+
if candidate_matrix_load.error:
|
|
1277
|
+
matrix_issues.append(
|
|
1278
|
+
AlignmentIssue(
|
|
1279
|
+
"*",
|
|
1280
|
+
0,
|
|
1281
|
+
"invalid-expected-case-matrix",
|
|
1282
|
+
f"candidate: {candidate_matrix_load.error}",
|
|
1283
|
+
)
|
|
1284
|
+
)
|
|
1285
|
+
if baseline_matrix_load.error:
|
|
1286
|
+
matrix_issues.append(
|
|
1287
|
+
AlignmentIssue(
|
|
1288
|
+
"*",
|
|
1289
|
+
0,
|
|
1290
|
+
"invalid-expected-case-matrix",
|
|
1291
|
+
f"baseline: {baseline_matrix_load.error}",
|
|
1292
|
+
)
|
|
1293
|
+
)
|
|
1294
|
+
candidate_expected = (
|
|
1295
|
+
candidate_matrix_load.matrix.keys_for(candidate_treatment)
|
|
1296
|
+
if candidate_matrix_load.matrix is not None
|
|
1297
|
+
else (frozenset() if candidate_matrix_load.error else None)
|
|
1298
|
+
)
|
|
1299
|
+
baseline_expected = (
|
|
1300
|
+
baseline_matrix_load.matrix.keys_for(baseline_treatment)
|
|
1301
|
+
if baseline_matrix_load.matrix is not None
|
|
1302
|
+
else (frozenset() if baseline_matrix_load.error else None)
|
|
1303
|
+
)
|
|
1304
|
+
if candidate_matrix_load.matrix is not None and not candidate_expected:
|
|
1305
|
+
matrix_issues.append(
|
|
1306
|
+
AlignmentIssue(
|
|
1307
|
+
"*",
|
|
1308
|
+
0,
|
|
1309
|
+
"expected-treatment-missing",
|
|
1310
|
+
f"candidate matrix has no {candidate_treatment} cases",
|
|
1311
|
+
)
|
|
1312
|
+
)
|
|
1313
|
+
if baseline_matrix_load.matrix is not None and not baseline_expected:
|
|
1314
|
+
matrix_issues.append(
|
|
1315
|
+
AlignmentIssue(
|
|
1316
|
+
"*",
|
|
1317
|
+
0,
|
|
1318
|
+
"expected-treatment-missing",
|
|
1319
|
+
f"baseline matrix has no {baseline_treatment} cases",
|
|
1320
|
+
)
|
|
1321
|
+
)
|
|
1322
|
+
alignment = align_records(
|
|
1323
|
+
candidate_records,
|
|
1324
|
+
baseline_records,
|
|
1325
|
+
candidate_expected=candidate_expected,
|
|
1326
|
+
baseline_expected=baseline_expected,
|
|
1327
|
+
initial_issues=matrix_issues,
|
|
1328
|
+
)
|
|
1329
|
+
candidate_results = _results_by_task(alignment.pairs, "candidate")
|
|
1330
|
+
baseline_results = _results_by_task(alignment.pairs, "baseline")
|
|
1331
|
+
candidate_metrics = task_macro_pass_metrics(
|
|
1332
|
+
candidate_results,
|
|
1333
|
+
normalized_ks,
|
|
1334
|
+
total_tasks=len(alignment.tasks),
|
|
1335
|
+
)
|
|
1336
|
+
baseline_metrics = task_macro_pass_metrics(
|
|
1337
|
+
baseline_results,
|
|
1338
|
+
normalized_ks,
|
|
1339
|
+
total_tasks=len(alignment.tasks),
|
|
1340
|
+
)
|
|
1341
|
+
|
|
1342
|
+
lines = [
|
|
1343
|
+
"# Comet Aligned Experiment Comparison Report",
|
|
1344
|
+
"",
|
|
1345
|
+
f"- Candidate: `{candidate_treatment}` from `{_experiment_label(candidate_dir)}`",
|
|
1346
|
+
f"- Baseline: `{baseline_treatment}` from `{_experiment_label(baseline_dir)}`",
|
|
1347
|
+
f"- Requested k: {', '.join(str(k) for k in normalized_ks)}",
|
|
1348
|
+
"",
|
|
1349
|
+
"## Alignment contract",
|
|
1350
|
+
"",
|
|
1351
|
+
"- Runs pair only when `task` and repetition match. Two v2 records must also match the full execution-bound `case_hash`; when either side is historical v1, only the shared task-core hash can be checked and the limitation is disclosed below.",
|
|
1352
|
+
"- Expected task/repetition coverage comes from each experiment's controller-written matrix when available, not from the reports that happened to survive.",
|
|
1353
|
+
"- Metrics are computed per task and then macro-averaged; runs are never pooled across tasks.",
|
|
1354
|
+
"- A task with fewer than k aligned repetitions is excluded from that k's task coverage; k is never reduced.",
|
|
1355
|
+
"- Duration is reparsed from raw stdout with the current additive result-event parser. Stored historical duration fields are not mixed into this metric.",
|
|
1356
|
+
"",
|
|
1357
|
+
"## Alignment summary",
|
|
1358
|
+
"",
|
|
1359
|
+
"| Candidate runs | Baseline runs | Expected task/rep keys | Strictly matched pairs | Tasks | Issues |",
|
|
1360
|
+
"|----------------|---------------|------------------------|------------------------|-------|--------|",
|
|
1361
|
+
f"| {len(candidate_records)} | {len(baseline_records)} | {alignment.expected_keys} | {len(alignment.pairs)} | {len(alignment.tasks)} | {len(alignment.issues)} |",
|
|
1362
|
+
"",
|
|
1363
|
+
]
|
|
1364
|
+
|
|
1365
|
+
matrix_rows = []
|
|
1366
|
+
for side, matrix_load, treatment in (
|
|
1367
|
+
("Candidate", candidate_matrix_load, candidate_treatment),
|
|
1368
|
+
("Baseline", baseline_matrix_load, baseline_treatment),
|
|
1369
|
+
):
|
|
1370
|
+
if matrix_load.matrix is None:
|
|
1371
|
+
status = "invalid (fail closed)" if matrix_load.error else "historical fallback"
|
|
1372
|
+
matrix_rows.append(f"| {side} | {status} | N/A | N/A |")
|
|
1373
|
+
else:
|
|
1374
|
+
keys = matrix_load.matrix.keys_for(treatment)
|
|
1375
|
+
matrix_rows.append(
|
|
1376
|
+
f"| {side} | report-bound | {_hash_short(matrix_load.matrix.matrix_hash)} | {len(keys)} |"
|
|
1377
|
+
)
|
|
1378
|
+
lines.extend(
|
|
1379
|
+
[
|
|
1380
|
+
"## Expected case matrix audit",
|
|
1381
|
+
"",
|
|
1382
|
+
"| Experiment | Source | Matrix hash | Target cases |",
|
|
1383
|
+
"|------------|--------|-------------|--------------|",
|
|
1384
|
+
*matrix_rows,
|
|
1385
|
+
"",
|
|
1386
|
+
]
|
|
1387
|
+
)
|
|
1388
|
+
missing_matrices = [
|
|
1389
|
+
side
|
|
1390
|
+
for side, matrix_load in (
|
|
1391
|
+
("candidate", candidate_matrix_load),
|
|
1392
|
+
("baseline", baseline_matrix_load),
|
|
1393
|
+
)
|
|
1394
|
+
if matrix_load.matrix is None and matrix_load.error is None
|
|
1395
|
+
]
|
|
1396
|
+
if missing_matrices:
|
|
1397
|
+
lines.extend(
|
|
1398
|
+
[
|
|
1399
|
+
"> Expected-matrix limitation: "
|
|
1400
|
+
+ " and ".join(missing_matrices)
|
|
1401
|
+
+ " experiment(s) predate a valid controller-written matrix. Observed-report fallback is used for that side, so a case missing from every fallback source cannot be reconstructed or claimed as historically executed.",
|
|
1402
|
+
"",
|
|
1403
|
+
]
|
|
1404
|
+
)
|
|
1405
|
+
invalid_matrices = [
|
|
1406
|
+
side
|
|
1407
|
+
for side, matrix_load in (
|
|
1408
|
+
("candidate", candidate_matrix_load),
|
|
1409
|
+
("baseline", baseline_matrix_load),
|
|
1410
|
+
)
|
|
1411
|
+
if matrix_load.error is not None
|
|
1412
|
+
]
|
|
1413
|
+
if invalid_matrices:
|
|
1414
|
+
lines.extend(
|
|
1415
|
+
[
|
|
1416
|
+
"> Invalid-matrix handling: "
|
|
1417
|
+
+ " and ".join(invalid_matrices)
|
|
1418
|
+
+ " experiment matrix failed validation. Its observed reports are excluded rather than treated as a historical fallback.",
|
|
1419
|
+
"",
|
|
1420
|
+
]
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
fallback_count = sum(
|
|
1424
|
+
1 for record in (*candidate_records, *baseline_records) if not record.manifest.run_bound
|
|
1425
|
+
)
|
|
1426
|
+
if fallback_count:
|
|
1427
|
+
lines.extend(
|
|
1428
|
+
[
|
|
1429
|
+
f"> Audit limitation: {fallback_count} run record(s) predate report-bound case manifests. Their hashes were reconstructed from the current canonical task tree and are marked `current-task-tree-fallback`; they prove comparison-time parity, not the exact historical checkout.",
|
|
1430
|
+
"",
|
|
1431
|
+
]
|
|
1432
|
+
)
|
|
1433
|
+
execution_unbound_count = sum(
|
|
1434
|
+
1
|
|
1435
|
+
for record in (*candidate_records, *baseline_records)
|
|
1436
|
+
if not record.manifest.execution_bound
|
|
1437
|
+
)
|
|
1438
|
+
if execution_unbound_count:
|
|
1439
|
+
lines.extend(
|
|
1440
|
+
[
|
|
1441
|
+
f"> Execution-identity limitation: {execution_unbound_count} run record(s) use v1 or reconstructed manifests that do not bind the runner/controller sources, immutable Docker image, Claude tool version, model selection, and interaction configuration. They must not be presented as exact historical execution identity.",
|
|
1442
|
+
"",
|
|
1443
|
+
]
|
|
1444
|
+
)
|
|
1445
|
+
|
|
1446
|
+
lines.extend(
|
|
1447
|
+
[
|
|
1448
|
+
"## pass@k / pass^k — task macro average",
|
|
1449
|
+
"",
|
|
1450
|
+
"Each value is the mean of the per-task estimator. Coverage is shown as eligible tasks / all aligned task names.",
|
|
1451
|
+
"`pass@k` uses the HumanEval at-least-one-success estimator. `pass^k` keeps Comet's observed consistency lower bound: a task contributes 1 only when all of its aligned repetitions pass.",
|
|
1452
|
+
"",
|
|
1453
|
+
"| Treatment | Metric | "
|
|
1454
|
+
+ " | ".join(f"k={k}" for k in normalized_ks)
|
|
1455
|
+
+ " | Matched pass/fail |",
|
|
1456
|
+
"|-----------|--------|"
|
|
1457
|
+
+ "|".join("-----" for _ in normalized_ks)
|
|
1458
|
+
+ "|-------------------|",
|
|
1459
|
+
]
|
|
1460
|
+
)
|
|
1461
|
+
for treatment, results, metrics in (
|
|
1462
|
+
(candidate_treatment, candidate_results, candidate_metrics),
|
|
1463
|
+
(baseline_treatment, baseline_results, baseline_metrics),
|
|
1464
|
+
):
|
|
1465
|
+
total_passes = sum(sum(1 for value in values if value) for values in results.values())
|
|
1466
|
+
total_runs = sum(len(values) for values in results.values())
|
|
1467
|
+
pass_at_cells = [
|
|
1468
|
+
f"{_format_metric(metrics[k]['pass_at_k'])} "
|
|
1469
|
+
f"({metrics[k]['eligible_tasks']}/{metrics[k]['total_tasks']} tasks)"
|
|
1470
|
+
for k in normalized_ks
|
|
1471
|
+
]
|
|
1472
|
+
pass_pow_cells = [
|
|
1473
|
+
f"{_format_metric(metrics[k]['pass_pow_k'])} "
|
|
1474
|
+
f"({metrics[k]['eligible_tasks']}/{metrics[k]['total_tasks']} tasks)"
|
|
1475
|
+
for k in normalized_ks
|
|
1476
|
+
]
|
|
1477
|
+
lines.append(
|
|
1478
|
+
f"| {treatment} | pass@k | "
|
|
1479
|
+
+ " | ".join(pass_at_cells)
|
|
1480
|
+
+ f" | {total_passes}/{total_runs} |"
|
|
1481
|
+
)
|
|
1482
|
+
lines.append(
|
|
1483
|
+
f"| {treatment} | pass^k | "
|
|
1484
|
+
+ " | ".join(pass_pow_cells)
|
|
1485
|
+
+ f" | {total_passes}/{total_runs} |"
|
|
1486
|
+
)
|
|
1487
|
+
lines.append("")
|
|
1488
|
+
|
|
1489
|
+
lines.extend(
|
|
1490
|
+
[
|
|
1491
|
+
"## Duration from raw stdout",
|
|
1492
|
+
"",
|
|
1493
|
+
"Missing raw duration is reported as missing coverage and is not replaced by, or averaged with, the stored report duration.",
|
|
1494
|
+
"",
|
|
1495
|
+
"| Treatment | Matched runs | Duration coverage | Total | Average / covered run | Source |",
|
|
1496
|
+
"|-----------|--------------|-------------------|-------|-----------------------|--------|",
|
|
1497
|
+
]
|
|
1498
|
+
)
|
|
1499
|
+
duration_missing: list[tuple[str, tuple[tuple[str, int], ...]]] = []
|
|
1500
|
+
for treatment, side, directory in (
|
|
1501
|
+
(candidate_treatment, "candidate", candidate_dir),
|
|
1502
|
+
(baseline_treatment, "baseline", baseline_dir),
|
|
1503
|
+
):
|
|
1504
|
+
matched, covered, total, average, missing = _duration_summary(
|
|
1505
|
+
alignment.pairs,
|
|
1506
|
+
side,
|
|
1507
|
+
directory,
|
|
1508
|
+
)
|
|
1509
|
+
lines.append(
|
|
1510
|
+
f"| {treatment} | {matched} | {covered}/{matched} | "
|
|
1511
|
+
f"{_format_seconds(total)} | {_format_seconds(average)} | {DURATION_SOURCE} |"
|
|
1512
|
+
)
|
|
1513
|
+
if missing:
|
|
1514
|
+
duration_missing.append((treatment, missing))
|
|
1515
|
+
lines.append("")
|
|
1516
|
+
for treatment, missing in duration_missing:
|
|
1517
|
+
references = ", ".join(f"`{task}#r{rep}`" for task, rep in missing)
|
|
1518
|
+
lines.append(f"- {treatment} missing raw duration: {references}")
|
|
1519
|
+
if duration_missing:
|
|
1520
|
+
lines.append("")
|
|
1521
|
+
|
|
1522
|
+
paired_telemetry = _telemetry_by_pair(alignment.pairs, candidate_dir, baseline_dir)
|
|
1523
|
+
successful_pairs = sum(
|
|
1524
|
+
_run_passed(pair.candidate.report) and _run_passed(pair.baseline.report)
|
|
1525
|
+
for pair in alignment.pairs
|
|
1526
|
+
)
|
|
1527
|
+
lines.extend(
|
|
1528
|
+
[
|
|
1529
|
+
"## Paired task efficiency from raw stdout",
|
|
1530
|
+
"",
|
|
1531
|
+
"Result telemetry is additive across the initial model start, deterministic user-answer resumes, and cold resumes. Token totals include cache-read tokens. Context input deduplicates streamed assistant events by message id.",
|
|
1532
|
+
"",
|
|
1533
|
+
f"### Strict-success intersection ({successful_pairs} paired runs)",
|
|
1534
|
+
"",
|
|
1535
|
+
"This is the primary completed-task efficiency view: both candidate and baseline passed the same task repetition.",
|
|
1536
|
+
"",
|
|
1537
|
+
*_render_efficiency_table(paired_telemetry, successes_only=True),
|
|
1538
|
+
"",
|
|
1539
|
+
f"### All aligned runs ({len(alignment.pairs)} paired runs)",
|
|
1540
|
+
"",
|
|
1541
|
+
*_render_efficiency_table(paired_telemetry, successes_only=False),
|
|
1542
|
+
"",
|
|
1543
|
+
"> Context rows require per-assistant-message usage from both sides. Low pair coverage means the historical CLI did not preserve enough context telemetry; those rows must not be promoted as a workflow comparison.",
|
|
1544
|
+
"",
|
|
1545
|
+
]
|
|
1546
|
+
)
|
|
1547
|
+
|
|
1548
|
+
candidate_manifests = _manifest_by_task(candidate_records)
|
|
1549
|
+
baseline_manifests = _manifest_by_task(baseline_records)
|
|
1550
|
+
lines.extend(
|
|
1551
|
+
[
|
|
1552
|
+
"## Case manifest audit",
|
|
1553
|
+
"",
|
|
1554
|
+
"Hash cells show the first 12 hexadecimal characters. Full hashes remain in the source report when report-bound; fallback hashes are reproducible from the task tree.",
|
|
1555
|
+
"",
|
|
1556
|
+
"| Task | Candidate case | Baseline case | Source | Instruction | Validator | Environment | Prompt | Runner | Controller | Execution |",
|
|
1557
|
+
"|------|----------------|---------------|--------|-------------|-----------|-------------|--------|--------|------------|-----------|",
|
|
1558
|
+
]
|
|
1559
|
+
)
|
|
1560
|
+
for task in alignment.tasks:
|
|
1561
|
+
candidate_manifest = candidate_manifests.get(task)
|
|
1562
|
+
baseline_manifest = baseline_manifests.get(task)
|
|
1563
|
+
representative = candidate_manifest or baseline_manifest
|
|
1564
|
+
if representative is None:
|
|
1565
|
+
continue
|
|
1566
|
+
source = "/".join(
|
|
1567
|
+
filter(
|
|
1568
|
+
None,
|
|
1569
|
+
(
|
|
1570
|
+
candidate_manifest.source if candidate_manifest else "candidate-missing",
|
|
1571
|
+
baseline_manifest.source if baseline_manifest else "baseline-missing",
|
|
1572
|
+
),
|
|
1573
|
+
)
|
|
1574
|
+
)
|
|
1575
|
+
lines.append(
|
|
1576
|
+
f"| {task} | "
|
|
1577
|
+
f"{_hash_short(candidate_manifest.case_hash) if candidate_manifest else '—'} | "
|
|
1578
|
+
f"{_hash_short(baseline_manifest.case_hash) if baseline_manifest else '—'} | "
|
|
1579
|
+
f"{source} | {_hash_short(representative.instruction_hash)} | "
|
|
1580
|
+
f"{_hash_short(representative.validator_hash)} | "
|
|
1581
|
+
f"{_hash_short(representative.environment_hash)} | "
|
|
1582
|
+
f"{_hash_short(representative.prompt_hash)} | "
|
|
1583
|
+
f"{_hash_short(representative.runner_hash) if representative.runner_hash else 'v1-unbound'} | "
|
|
1584
|
+
f"{_hash_short(representative.controller_hash) if representative.controller_hash else 'v1-unbound'} | "
|
|
1585
|
+
f"{_hash_short(representative.execution_hash) if representative.execution_hash else 'v1-unbound'} |"
|
|
1586
|
+
)
|
|
1587
|
+
lines.append("")
|
|
1588
|
+
|
|
1589
|
+
lines.extend(
|
|
1590
|
+
[
|
|
1591
|
+
"## Task outcomes on strictly matched repetitions",
|
|
1592
|
+
"",
|
|
1593
|
+
"| Task | Repetitions | Candidate | Baseline |",
|
|
1594
|
+
"|------|-------------|-----------|----------|",
|
|
1595
|
+
]
|
|
1596
|
+
)
|
|
1597
|
+
pairs_by_task: dict[str, list[AlignedPair]] = defaultdict(list)
|
|
1598
|
+
for pair in alignment.pairs:
|
|
1599
|
+
pairs_by_task[pair.task].append(pair)
|
|
1600
|
+
for task in alignment.tasks:
|
|
1601
|
+
pairs = sorted(pairs_by_task.get(task, []), key=lambda item: item.rep)
|
|
1602
|
+
reps = ",".join(str(pair.rep) for pair in pairs) or "—"
|
|
1603
|
+
candidate_passes = sum(_run_passed(pair.candidate.report) for pair in pairs)
|
|
1604
|
+
baseline_passes = sum(_run_passed(pair.baseline.report) for pair in pairs)
|
|
1605
|
+
lines.append(
|
|
1606
|
+
f"| {task} | {reps} | {candidate_passes}/{len(pairs)} | "
|
|
1607
|
+
f"{baseline_passes}/{len(pairs)} |"
|
|
1608
|
+
)
|
|
1609
|
+
lines.append("")
|
|
1610
|
+
|
|
1611
|
+
if alignment.issues:
|
|
1612
|
+
counts = Counter(issue.reason for issue in alignment.issues)
|
|
1613
|
+
lines.extend(
|
|
1614
|
+
[
|
|
1615
|
+
"## Alignment issues",
|
|
1616
|
+
"",
|
|
1617
|
+
"Issue counts: "
|
|
1618
|
+
+ ", ".join(f"`{reason}`={count}" for reason, count in sorted(counts.items())),
|
|
1619
|
+
"",
|
|
1620
|
+
"| Task | Rep | Reason | Detail |",
|
|
1621
|
+
"|------|-----|--------|--------|",
|
|
1622
|
+
]
|
|
1623
|
+
)
|
|
1624
|
+
for issue in alignment.issues:
|
|
1625
|
+
detail = issue.detail.replace("|", "/")
|
|
1626
|
+
lines.append(f"| {issue.task} | {issue.rep} | {issue.reason} | {detail} |")
|
|
1627
|
+
lines.append("")
|
|
1628
|
+
else:
|
|
1629
|
+
lines.extend(["## Alignment issues", "", "_None._", ""])
|
|
1630
|
+
|
|
1631
|
+
return "\n".join(lines)
|