@dailephd/my-dev-kit-lab 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +18 -3
  3. package/benchmarks/contracts/agent-success-rate-project-profiles.json +33 -0
  4. package/benchmarks/contracts/agent-success-rate-tasks.json +671 -0
  5. package/benchmarks/projects/agent-success-inventory-node/.gitattributes +2 -0
  6. package/benchmarks/projects/agent-success-inventory-node/README.md +74 -0
  7. package/benchmarks/projects/agent-success-inventory-node/package.json +14 -0
  8. package/benchmarks/projects/agent-success-inventory-node/src/fulfillmentReport.js +38 -0
  9. package/benchmarks/projects/agent-success-inventory-node/src/index.js +26 -0
  10. package/benchmarks/projects/agent-success-inventory-node/src/inventoryStore.js +114 -0
  11. package/benchmarks/projects/agent-success-inventory-node/src/quantity.js +19 -0
  12. package/benchmarks/projects/agent-success-inventory-node/src/reservationService.js +45 -0
  13. package/benchmarks/projects/agent-success-inventory-node/tests/quantity-edge.check.mjs +51 -0
  14. package/benchmarks/projects/agent-success-inventory-node/tests/quantity-primary.check.mjs +46 -0
  15. package/benchmarks/projects/agent-success-inventory-node/tests/regression.check.mjs +115 -0
  16. package/benchmarks/projects/agent-success-inventory-node/tests/report-edge.check.mjs +109 -0
  17. package/benchmarks/projects/agent-success-inventory-node/tests/report-primary.check.mjs +47 -0
  18. package/benchmarks/projects/agent-success-inventory-node/tests/reservation-edge.check.mjs +63 -0
  19. package/benchmarks/projects/agent-success-inventory-node/tests/reservation-primary.check.mjs +44 -0
  20. package/benchmarks/projects/agent-success-task-board-node/.gitattributes +2 -0
  21. package/benchmarks/projects/agent-success-task-board-node/README.md +77 -0
  22. package/benchmarks/projects/agent-success-task-board-node/package.json +14 -0
  23. package/benchmarks/projects/agent-success-task-board-node/src/index.js +24 -0
  24. package/benchmarks/projects/agent-success-task-board-node/src/projectSummary.js +15 -0
  25. package/benchmarks/projects/agent-success-task-board-node/src/taskService.js +58 -0
  26. package/benchmarks/projects/agent-success-task-board-node/src/taskStore.js +58 -0
  27. package/benchmarks/projects/agent-success-task-board-node/src/validation.js +31 -0
  28. package/benchmarks/projects/agent-success-task-board-node/tests/import-edge.check.mjs +65 -0
  29. package/benchmarks/projects/agent-success-task-board-node/tests/import-primary.check.mjs +31 -0
  30. package/benchmarks/projects/agent-success-task-board-node/tests/regression.check.mjs +107 -0
  31. package/benchmarks/projects/agent-success-task-board-node/tests/summary-edge.check.mjs +67 -0
  32. package/benchmarks/projects/agent-success-task-board-node/tests/summary-primary.check.mjs +54 -0
  33. package/benchmarks/projects/agent-success-task-board-node/tests/title-edge.check.mjs +39 -0
  34. package/benchmarks/projects/agent-success-task-board-node/tests/title-primary.check.mjs +29 -0
  35. package/dist/src/cli/help.js +38 -1
  36. package/dist/src/commands/runExperimentDescribeCommand.js +52 -1
  37. package/dist/src/commands/runExperimentRunCommand.js +118 -2
  38. package/dist/src/core/processTree.js +55 -0
  39. package/dist/src/core/runMeasuredCommand.js +104 -22
  40. package/dist/src/evaluation/agentSuccess/applyPatchToSandbox.js +37 -0
  41. package/dist/src/evaluation/agentSuccess/assessBaseline.js +23 -0
  42. package/dist/src/evaluation/agentSuccess/corpusTypes.js +74 -0
  43. package/dist/src/evaluation/agentSuccess/extractPatchCandidate.js +46 -0
  44. package/dist/src/evaluation/agentSuccess/index.js +14 -0
  45. package/dist/src/evaluation/agentSuccess/parseUnifiedDiff.js +210 -0
  46. package/dist/src/evaluation/agentSuccess/patchTypes.js +5 -0
  47. package/dist/src/evaluation/agentSuccess/readAgentSuccessCorpus.js +183 -0
  48. package/dist/src/evaluation/agentSuccess/runVerificationCheck.js +100 -0
  49. package/dist/src/evaluation/agentSuccess/taskPaths.js +34 -0
  50. package/dist/src/evaluation/agentSuccess/taskTypes.js +14 -0
  51. package/dist/src/evaluation/agentSuccess/validateAgentSuccessCorpus.js +307 -0
  52. package/dist/src/evaluation/agentSuccess/validateAgentSuccessTask.js +297 -0
  53. package/dist/src/evaluation/agentSuccess/validatePatchPolicy.js +66 -0
  54. package/dist/src/evaluation/agentSuccess/verificationTypes.js +1 -0
  55. package/dist/src/evaluation/benchmarkSandbox/createBenchmarkSandbox.js +127 -0
  56. package/dist/src/evaluation/benchmarkSandbox/errors.js +11 -0
  57. package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +60 -0
  58. package/dist/src/evaluation/benchmarkSandbox/index.js +8 -0
  59. package/dist/src/evaluation/benchmarkSandbox/minimalHostEnv.js +37 -0
  60. package/dist/src/evaluation/benchmarkSandbox/pathPolicy.js +49 -0
  61. package/dist/src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.js +74 -0
  62. package/dist/src/evaluation/benchmarkSandbox/treeSnapshot.js +57 -0
  63. package/dist/src/evaluation/benchmarkSandbox/types.js +3 -0
  64. package/dist/src/evaluation/changeSet/captureChangeSet.js +98 -0
  65. package/dist/src/evaluation/changeSet/index.js +2 -0
  66. package/dist/src/evaluation/changeSet/types.js +9 -0
  67. package/dist/src/experiments/defaultRegistry.js +2 -0
  68. package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
  69. package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
  70. package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
  71. package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
  72. package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
  73. package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
  74. package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
  75. package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
  76. package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
  77. package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
  78. package/dist/src/experiments/plugins/agentSuccessRate/index.js +20 -0
  79. package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
  80. package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
  81. package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +507 -0
  82. package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +250 -0
  83. package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
  84. package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
  85. package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
  86. package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
  87. package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
  88. package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
  89. package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
  90. package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
  91. package/dist/src/report/experiments/buildAgentSuccessRateReport.js +255 -0
  92. package/dist/src/report/experiments/buildPluginExperimentReport.js +27 -2
  93. package/dist/src/report/experiments/index.js +4 -0
  94. package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +169 -0
  95. package/dist/src/report/experiments/renderAgentSuccessRateText.js +139 -0
  96. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  97. package/dist/src/report/experiments/renderPluginExperimentReportText.js +5 -0
  98. package/docs/METRICS.md +70 -0
  99. package/package.json +1 -1
@@ -0,0 +1,425 @@
1
+ import { AGENT_SUCCESS_MEAN_SOURCES, AGENT_SUCCESS_METRIC_IDS } from "./analysisTypes.js";
2
+ import { AGENT_SUCCESS_RATE_EXECUTION_MODE, AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE, AGENT_SUCCESS_RATE_TREATMENT_IDS } from "./metadata.js";
3
+ import { availableMetric, notApplicableMetric, ratioMetric, triMetric, unavailableMetric } from "./metrics.js";
4
+ import { aggregateRepair, buildRepairComparison } from "./repairAnalysis.js";
5
+ import { classifyAgentSuccessAttempt } from "./repairPolicy.js";
6
+ export const AGENT_SUCCESS_RATE_LIMITATIONS = [
7
+ "Deterministic-fixture mode applies the same fixture patch to both treatment identities; the patch is not generated from any supplied context.",
8
+ "contextEffectEvaluated is false: raw-full-file versus context-pack differences are pipeline diagnostics, not performance evidence.",
9
+ "No coding agent runs, so no agent success rate, agent duration or provider token usage exists.",
10
+ "Verification counts are command/check level; individual test counts are not inferred from command output.",
11
+ "No weighted score, ranking, winner or significance claim is produced."
12
+ ];
13
+ export const AGENT_SUCCESS_RATE_REAL_AGENT_LIMITATIONS = [
14
+ "Real-agent mode gives each treatment one initial provider attempt and, only when repairAttempts is configured, at most that many bounded repair attempts (three attempts in total); the provider generated its own patch from only the supplied treatment context.",
15
+ "Initial-attempt success and final success are separate measurements; a repaired success is never an initial-attempt success, and repair feedback is limited to fixed outcome categories.",
16
+ "Task success is derived from trusted checks on the actual changed repository state; agent prose is never evidence of success.",
17
+ "Matched-case comparisons are descriptive. A single run supports no causal, ranking, winner or statistical-significance claim.",
18
+ "Estimated context tokens are size estimates of the supplied context and are not provider-reported token usage.",
19
+ "Cases with an unavailable treatment verdict are excluded from matched metrics and reported separately.",
20
+ "Verification counts are command/check level; individual test counts are not inferred from command output.",
21
+ "No weighted score, ranking, winner or significance claim is produced."
22
+ ];
23
+ const STATE_REASON = {
24
+ "infrastructure-failure": "execution infrastructure failed before the attempt could be evaluated.",
25
+ "baseline-invalid": "the benchmark baseline is not evaluable, so no implementation attempt was evaluated.",
26
+ "no-answer": "the provider produced no usable answer, so no implementation attempt was evaluated.",
27
+ "no-evidence": "required execution evidence is missing.",
28
+ "patch-failed": "the fixture patch was not applied, so post-edit verification did not run."
29
+ };
30
+ function and3(...values) {
31
+ if (values.includes(false))
32
+ return false;
33
+ if (values.includes("unknown"))
34
+ return "unknown";
35
+ return true;
36
+ }
37
+ function stateOf(evidence) {
38
+ if (evidence.availability === "infrastructure-failure")
39
+ return "infrastructure-failure";
40
+ if (evidence.baselineAssessment === null || evidence.availability === "baseline-invalid" || !evidence.baselineAssessment.evaluable)
41
+ return "baseline-invalid";
42
+ if (!evidence.patch.attempted || evidence.patch.outcome === null)
43
+ return evidence.realAgent ? "no-answer" : "no-evidence";
44
+ return evidence.patch.outcome === "success" ? "patched" : "patch-failed";
45
+ }
46
+ /** passed -> true, failed -> false, timeout/error/missing -> unknown. */
47
+ function checkTri(result) {
48
+ if (result === undefined)
49
+ return "unknown";
50
+ if (result.status === "passed")
51
+ return true;
52
+ if (result.status === "failed")
53
+ return false;
54
+ return "unknown";
55
+ }
56
+ /** Any failed -> false; else any missing/indeterminate -> unknown; else true. */
57
+ function allChecks(ids, results) {
58
+ return and3(...ids.map((id) => checkTri(results.get(id))));
59
+ }
60
+ function byId(results) {
61
+ return new Map((results ?? []).map((result) => [result.checkId, result]));
62
+ }
63
+ function duration(id, value) {
64
+ return value !== null && Number.isFinite(value) && value >= 0
65
+ ? availableMetric(id, value, "ms")
66
+ : unavailableMetric(id, "ms", "the duration was not measured.");
67
+ }
68
+ /**
69
+ * The single owner of per-case/per-treatment scientific values. Inputs are the validated task and normalized execution
70
+ * evidence only; agent prose and fixture notes are never consulted.
71
+ */
72
+ export function analyzeAgentSuccessTreatment(task, evidence) {
73
+ const state = stateOf(evidence);
74
+ const gate = state === "patched" ? null : state === "patch-failed" && evidence.realAgent ? "the provider patch was not applied, so post-edit verification did not run." : STATE_REASON[state];
75
+ const m = {};
76
+ const un = (id, unit, reason) => unavailableMetric(id, unit, reason);
77
+ const post = evidence.postEditVerification;
78
+ const postTask = byId(post?.taskResults);
79
+ const postRegression = byId(post?.regressionResults);
80
+ const postAll = new Map([...postTask, ...postRegression]);
81
+ const baselineRegressionPassed = new Set((evidence.baselineVerification?.regressionResults ?? []).filter((r) => r.status === "passed").map((r) => r.checkId));
82
+ const postEvidenceMissing = state === "patched" && post === null ? "post-edit verification evidence is missing." : null;
83
+ // --- verification-derived tri-states (only meaningful once a patch was applied) ---
84
+ const taskIds = task.taskChecks.map((c) => c.id);
85
+ const regressionIds = task.regressionChecks.filter((c) => baselineRegressionPassed.has(c.id)).map((c) => c.id);
86
+ const patched = state === "patched" && post !== null;
87
+ const taskChecksTri = patched ? allChecks(taskIds, postTask) : "unknown";
88
+ const regressionTri = patched ? allChecks(regressionIds, postRegression) : "unknown";
89
+ const factState = (fact) => (patched ? allChecks(fact.verificationCheckIds, postAll) : "unknown");
90
+ const requiredFacts = task.behaviorFacts.filter((fact) => fact.required);
91
+ const optionalFacts = task.behaviorFacts.filter((fact) => !fact.required);
92
+ const requiredStates = requiredFacts.map(factState);
93
+ const optionalStates = optionalFacts.map(factState);
94
+ const requiredTri = and3(...requiredStates);
95
+ const integrity = evidence.protectedIntegrity.status;
96
+ const integrityTri = integrity === "intact" ? true : integrity === "mutated" ? false : "unknown";
97
+ const patchApplied = state === "patched";
98
+ const reasonFor = (fallback) => gate ?? postEvidenceMissing ?? fallback;
99
+ // --- task success ---
100
+ m.patchApplied = state === "patched" || state === "patch-failed" ? availableMetric("patchApplied", patchApplied, "boolean") : un("patchApplied", "boolean", gate ?? STATE_REASON["no-evidence"]);
101
+ if (state === "patch-failed") {
102
+ m.taskChecksPassed = un("taskChecksPassed", "boolean", gate);
103
+ m.regressionSafe = un("regressionSafe", "boolean", gate);
104
+ m.requiredFactsSatisfied = un("requiredFactsSatisfied", "boolean", gate);
105
+ }
106
+ else {
107
+ m.taskChecksPassed = triMetric("taskChecksPassed", taskChecksTri, reasonFor("a task check was missing or indeterminate."));
108
+ m.regressionSafe = triMetric("regressionSafe", regressionTri, reasonFor("a regression check was missing or indeterminate."));
109
+ m.requiredFactsSatisfied = triMetric("requiredFactsSatisfied", requiredTri, reasonFor("a required fact's check evidence was missing or indeterminate."));
110
+ }
111
+ m.protectedIntegrity =
112
+ state === "baseline-invalid" || state === "infrastructure-failure" || state === "no-evidence" || state === "no-answer"
113
+ ? un("protectedIntegrity", "boolean", gate)
114
+ : triMetric("protectedIntegrity", integrityTri, "protected-file integrity could not be proven.");
115
+ const resolved = state === "patched" ? and3(true, taskChecksTri, requiredTri) : state === "patch-failed" ? false : "unknown";
116
+ const success = state === "patched" ? and3(resolved, regressionTri, integrityTri) : state === "patch-failed" ? false : "unknown";
117
+ m.taskResolved = triMetric("taskResolved", resolved, gate ?? "a task check or required fact could not be determined.");
118
+ m.taskSuccess = triMetric("taskSuccess", success, gate ?? "a component of task success could not be determined.");
119
+ // --- behavior facts ---
120
+ const determinate = (states) => states.every((s) => s !== "unknown");
121
+ const count = (states) => states.filter((s) => s === true).length;
122
+ m.requiredFactsTotal = availableMetric("requiredFactsTotal", requiredFacts.length, "count");
123
+ m.optionalFactsTotal = availableMetric("optionalFactsTotal", optionalFacts.length, "count");
124
+ m.requiredFactsSatisfiedCount = determinate(requiredStates) && patched ? availableMetric("requiredFactsSatisfiedCount", count(requiredStates), "count") : un("requiredFactsSatisfiedCount", "count", reasonFor("a required fact could not be determined."));
125
+ m.optionalFactsSatisfiedCount = determinate(optionalStates) && patched ? availableMetric("optionalFactsSatisfiedCount", count(optionalStates), "count") : un("optionalFactsSatisfiedCount", "count", reasonFor("an optional fact could not be determined."));
126
+ m.requiredFactCoverage =
127
+ requiredFacts.length === 0
128
+ ? notApplicableMetric("requiredFactCoverage", "ratio", "the task defines no required facts.")
129
+ : determinate(requiredStates) && patched
130
+ ? ratioMetric("requiredFactCoverage", count(requiredStates), requiredFacts.length, "no required facts.")
131
+ : un("requiredFactCoverage", "ratio", reasonFor("a required fact could not be determined."));
132
+ const allStates = [...requiredStates, ...optionalStates];
133
+ m.factCoverage =
134
+ task.behaviorFacts.length === 0
135
+ ? notApplicableMetric("factCoverage", "ratio", "the task defines no behavior facts.")
136
+ : determinate(allStates) && patched
137
+ ? ratioMetric("factCoverage", count(allStates), task.behaviorFacts.length, "no facts.")
138
+ : un("factCoverage", "ratio", reasonFor("a behavior fact could not be determined."));
139
+ // --- check-level counts ---
140
+ const taskComplete = patched && taskIds.every((id) => postTask.has(id) && postTask.get(id).status !== "timeout" && postTask.get(id).status !== "error");
141
+ const regressionAll = task.regressionChecks.map((c) => c.id);
142
+ const regressionComplete = patched && regressionAll.every((id) => postRegression.has(id) && postRegression.get(id).status !== "timeout" && postRegression.get(id).status !== "error");
143
+ const passedOf = (ids, results) => ids.filter((id) => results.get(id)?.status === "passed").length;
144
+ const checkReason = (kind) => reasonFor(`a ${kind} check was missing, timed out or errored; complete check evidence is required.`);
145
+ if (taskComplete) {
146
+ m.taskCheckPassedCount = availableMetric("taskCheckPassedCount", passedOf(taskIds, postTask), "count");
147
+ m.taskCheckTotalCount = availableMetric("taskCheckTotalCount", taskIds.length, "count");
148
+ m.taskCheckPassRate = ratioMetric("taskCheckPassRate", passedOf(taskIds, postTask), taskIds.length, "the task defines no task checks.");
149
+ }
150
+ else {
151
+ m.taskCheckPassedCount = un("taskCheckPassedCount", "count", checkReason("task"));
152
+ m.taskCheckTotalCount = un("taskCheckTotalCount", "count", checkReason("task"));
153
+ m.taskCheckPassRate = un("taskCheckPassRate", "ratio", checkReason("task"));
154
+ }
155
+ if (regressionComplete) {
156
+ m.regressionCheckPassedCount = availableMetric("regressionCheckPassedCount", passedOf(regressionAll, postRegression), "count");
157
+ m.regressionCheckTotalCount = availableMetric("regressionCheckTotalCount", regressionAll.length, "count");
158
+ m.regressionCheckPassRate = ratioMetric("regressionCheckPassRate", passedOf(regressionAll, postRegression), regressionAll.length, "the task defines no regression checks.");
159
+ m.regressionFailureCount = availableMetric("regressionFailureCount", regressionIds.filter((id) => postRegression.get(id)?.status === "failed").length, "count");
160
+ }
161
+ else {
162
+ for (const id of ["regressionCheckPassedCount", "regressionCheckTotalCount", "regressionFailureCount"])
163
+ m[id] = un(id, "count", checkReason("regression"));
164
+ m.regressionCheckPassRate = un("regressionCheckPassRate", "ratio", checkReason("regression"));
165
+ }
166
+ // --- edit quality and blast radius (ChangeSet evidence only) ---
167
+ const change = patched ? evidence.change : null;
168
+ const changeReason = reasonFor("change evidence was not captured.");
169
+ const expected = new Set(task.expectedEditFiles);
170
+ const allowed = new Set(task.allowedEditFiles);
171
+ m.expectedEditFileCount = availableMetric("expectedEditFileCount", expected.size, "count");
172
+ if (change === null) {
173
+ for (const id of ["expectedEditFilesChangedCount", "allowedEditChangedFileCount", "unexpectedChangedFileCount", "changedFileCount", "addedFileCount", "modifiedFileCount", "deletedFileCount", "linesAdded", "linesDeleted", "totalChurn"]) {
174
+ m[id] = un(id, id.startsWith("lines") || id === "totalChurn" ? "lines" : "count", changeReason);
175
+ }
176
+ m.expectedEditCoverage = un("expectedEditCoverage", "ratio", changeReason);
177
+ m.editScopePrecision = un("editScopePrecision", "ratio", changeReason);
178
+ m.relativeChurn = un("relativeChurn", "ratio", changeReason);
179
+ }
180
+ else {
181
+ const paths = change.changedFiles.map((file) => file.relativePath);
182
+ const expectedChanged = paths.filter((p) => expected.has(p)).length;
183
+ const allowedChanged = paths.filter((p) => allowed.has(p)).length;
184
+ const statusCount = (status) => change.changedFiles.filter((file) => file.status === status).length;
185
+ m.expectedEditFilesChangedCount = availableMetric("expectedEditFilesChangedCount", expectedChanged, "count");
186
+ m.expectedEditCoverage = ratioMetric("expectedEditCoverage", expectedChanged, expected.size, "the task expects no edited files.");
187
+ m.allowedEditChangedFileCount = availableMetric("allowedEditChangedFileCount", allowedChanged, "count");
188
+ m.unexpectedChangedFileCount = availableMetric("unexpectedChangedFileCount", paths.length - allowedChanged, "count");
189
+ m.editScopePrecision = ratioMetric("editScopePrecision", allowedChanged, paths.length, "no files were changed.");
190
+ m.changedFileCount = availableMetric("changedFileCount", paths.length, "count");
191
+ m.addedFileCount = availableMetric("addedFileCount", statusCount("added"), "count");
192
+ m.modifiedFileCount = availableMetric("modifiedFileCount", statusCount("modified"), "count");
193
+ m.deletedFileCount = availableMetric("deletedFileCount", statusCount("deleted"), "count");
194
+ const hasBinary = change.changedFiles.some((file) => file.additions === null || file.deletions === null);
195
+ if (hasBinary) {
196
+ const reason = "a changed file has no line counts (binary); line metrics are not estimated.";
197
+ m.linesAdded = un("linesAdded", "lines", reason);
198
+ m.linesDeleted = un("linesDeleted", "lines", reason);
199
+ m.totalChurn = un("totalChurn", "lines", reason);
200
+ m.relativeChurn = un("relativeChurn", "ratio", reason);
201
+ }
202
+ else {
203
+ const added = change.changedFiles.reduce((sum, file) => sum + (file.additions ?? 0), 0);
204
+ const deleted = change.changedFiles.reduce((sum, file) => sum + (file.deletions ?? 0), 0);
205
+ m.linesAdded = availableMetric("linesAdded", added, "lines");
206
+ m.linesDeleted = availableMetric("linesDeleted", deleted, "lines");
207
+ m.totalChurn = availableMetric("totalChurn", added + deleted, "lines");
208
+ const baseLines = evidence.baselineTextLineCount;
209
+ m.relativeChurn =
210
+ baseLines === null
211
+ ? un("relativeChurn", "ratio", "the baseline text-line count could not be determined.")
212
+ : ratioMetric("relativeChurn", added + deleted, baseLines, "the baseline has no text lines.");
213
+ }
214
+ }
215
+ m.baselineTextLineCount =
216
+ evidence.baselineTextLineCount === null
217
+ ? un("baselineTextLineCount", "lines", "the baseline text-line count could not be determined.")
218
+ : availableMetric("baselineTextLineCount", evidence.baselineTextLineCount, "lines");
219
+ // --- protected edits: actual mutation vs. attempted ---
220
+ const mutationEvaluable = (state === "patched" || state === "patch-failed") && integrity !== "unproven";
221
+ m.protectedMutationCount = mutationEvaluable
222
+ ? availableMetric("protectedMutationCount", evidence.protectedIntegrity.mutatedPaths.length, "count")
223
+ : un("protectedMutationCount", "count", gate ?? "protected-file integrity could not be proven.");
224
+ m.attemptedProtectedEditCount = evidence.patch.attempted
225
+ ? availableMetric("attemptedProtectedEditCount", evidence.patch.attemptedProtectedPaths.length, "count")
226
+ : un("attemptedProtectedEditCount", "count", "no patch was attempted.");
227
+ // --- time and tokens ---
228
+ const real = evidence.realAgent;
229
+ if (!real) {
230
+ m.agentDurationMs = un("agentDurationMs", "ms", "deterministic-fixture mode runs no agent.");
231
+ m.agentTotalTokens = un("agentTotalTokens", "tokens", "deterministic-fixture mode has no provider token usage.");
232
+ }
233
+ else {
234
+ m.agentDurationMs = real.providerInvoked ? duration("agentDurationMs", evidence.timing.agentDurationMs) : un("agentDurationMs", "ms", "no provider attempt ran.");
235
+ const usage = evidence.agentTokenUsage;
236
+ m.agentTotalTokens =
237
+ usage !== null && usage.totalTokens !== null
238
+ ? availableMetric("agentTotalTokens", usage.totalTokens, "tokens")
239
+ : un("agentTotalTokens", "tokens", usage === null ? "no provider attempt ran." : `the provider reported no total token usage (source: ${usage.source}, reliability: ${usage.reliability}).`);
240
+ }
241
+ m.baselineVerificationDurationMs = duration("baselineVerificationDurationMs", evidence.timing.baselineVerificationDurationMs);
242
+ m.patchPipelineDurationMs = duration("patchPipelineDurationMs", evidence.timing.patchPipelineDurationMs);
243
+ m.postEditVerificationDurationMs = duration("postEditVerificationDurationMs", evidence.timing.postEditVerificationDurationMs);
244
+ m.evaluationDurationMs = duration("evaluationDurationMs", evidence.timing.evaluationDurationMs);
245
+ const metrics = {};
246
+ for (const id of AGENT_SUCCESS_METRIC_IDS) {
247
+ const metric = m[id];
248
+ if (!metric)
249
+ throw new Error(`Agent success analysis did not produce metric ${id}.`);
250
+ metrics[id] = metric;
251
+ }
252
+ const repair = evidence.attempts && evidence.attempts.length > 0 ? buildRepairAnalysis(task, evidence.attempts) : undefined;
253
+ return { treatmentId: evidence.treatmentId, caseId: task.id, executionStatus: evidence.status, evidenceAvailability: evidence.availability, metrics, ...(repair ? { repair } : {}) };
254
+ }
255
+ function retag(metric, id) {
256
+ return { ...metric, id };
257
+ }
258
+ /** Sum of a per-attempt quantity over the provider attempts that ran. Unavailable if any invoked attempt was not measured. */
259
+ function sumOverInvoked(id, unit, noun, samples) {
260
+ const invoked = samples.filter((sample) => sample.invoked);
261
+ if (invoked.length === 0)
262
+ return unavailableMetric(id, unit, "no provider attempt ran.");
263
+ const missing = invoked.filter((sample) => sample.metric.availability !== "available");
264
+ if (missing.length > 0)
265
+ return unavailableMetric(id, unit, `${missing.length} of ${invoked.length} provider attempt(s) have no ${noun}; a complete total is not available.`);
266
+ return availableMetric(id, invoked.reduce((total, sample) => total + sample.metric.value, 0), unit);
267
+ }
268
+ /** Per-attempt analysis plus the initial/final separation. Attempt order is evidence order (contiguous, one-based). */
269
+ function buildRepairAnalysis(task, attempts) {
270
+ const analyzed = attempts.map((attempt) => {
271
+ const analysis = analyzeAgentSuccessTreatment(task, attempt.evidence);
272
+ const classification = classifyAgentSuccessAttempt(attempt.evidence, analysis.metrics);
273
+ const entry = {
274
+ attemptNumber: attempt.attemptNumber,
275
+ providerStatus: attempt.providerStatus,
276
+ failureCategory: classification.category,
277
+ repairEligible: classification.repairEligible,
278
+ executionStatus: analysis.executionStatus,
279
+ evidenceAvailability: analysis.evidenceAvailability,
280
+ metrics: analysis.metrics
281
+ };
282
+ return { entry, invoked: attempt.evidence.realAgent?.providerInvoked === true };
283
+ });
284
+ const first = analyzed[0];
285
+ const last = analyzed[analyzed.length - 1];
286
+ const initial = retag(first.entry.metrics.taskSuccess, "initialAttemptTaskSuccess");
287
+ const final = retag(last.entry.metrics.taskSuccess, "finalTaskSuccess");
288
+ const repairAttemptCount = analyzed.length - 1;
289
+ let repairSucceeded;
290
+ if (repairAttemptCount === 0) {
291
+ repairSucceeded = notApplicableMetric("repairSucceeded", "boolean", "no repair attempt was executed.");
292
+ }
293
+ else if (initial.availability !== "available" || final.availability !== "available") {
294
+ repairSucceeded = unavailableMetric("repairSucceeded", "boolean", "the initial or final attempt has no determinate task-success verdict.");
295
+ }
296
+ else {
297
+ repairSucceeded = availableMetric("repairSucceeded", initial.value === false && final.value === true, "boolean");
298
+ }
299
+ const duration = (item) => item.entry.metrics.agentDurationMs;
300
+ const tokens = (item) => item.entry.metrics.agentTotalTokens;
301
+ const firstOr = (item, pick, id, unit) => item.invoked ? retag(pick(item), id) : unavailableMetric(id, unit, "no provider attempt ran.");
302
+ const evaluationDurations = analyzed.map((item) => item.entry.metrics.evaluationDurationMs);
303
+ const missingEvaluation = evaluationDurations.filter((metric) => metric.availability !== "available").length;
304
+ const invokedItems = analyzed.filter((item) => item.invoked);
305
+ return {
306
+ attemptCount: analyzed.length,
307
+ repairAttemptCount,
308
+ initialAttemptRepairEligible: first.entry.repairEligible,
309
+ initialAttemptTaskSuccess: initial,
310
+ finalTaskSuccess: final,
311
+ repairSucceeded,
312
+ firstAttemptProviderDurationMs: firstOr(first, duration, "firstAttemptProviderDurationMs", "ms"),
313
+ finalAttemptProviderDurationMs: firstOr(last, duration, "finalAttemptProviderDurationMs", "ms"),
314
+ totalProviderDurationMs: sumOverInvoked("totalProviderDurationMs", "ms", "duration measurement", analyzed.map((item) => ({ invoked: item.invoked, metric: duration(item) }))),
315
+ firstAttemptProviderTokens: firstOr(first, tokens, "firstAttemptProviderTokens", "tokens"),
316
+ finalAttemptProviderTokens: firstOr(last, tokens, "finalAttemptProviderTokens", "tokens"),
317
+ totalProviderTokens: sumOverInvoked("totalProviderTokens", "tokens", "provider-reported total token usage", analyzed.map((item) => ({ invoked: item.invoked, metric: tokens(item) }))),
318
+ totalEvaluationDurationMs: missingEvaluation > 0
319
+ ? unavailableMetric("totalEvaluationDurationMs", "ms", `${missingEvaluation} of ${analyzed.length} attempt(s) have no evaluation duration; a complete total is not available.`)
320
+ : availableMetric("totalEvaluationDurationMs", evaluationDurations.reduce((total, metric) => total + metric.value, 0), "ms"),
321
+ providerAttemptCount: invokedItems.length,
322
+ providerDurationMeasuredAttemptCount: invokedItems.filter((item) => duration(item).availability === "available").length,
323
+ providerTokenMeasuredAttemptCount: invokedItems.filter((item) => tokens(item).availability === "available").length,
324
+ attempts: analyzed.map((item) => item.entry)
325
+ };
326
+ }
327
+ function mean(id, caseIds, treatment, allTreatments) {
328
+ const sourceMetricId = AGENT_SUCCESS_MEAN_SOURCES[id];
329
+ const matched = caseIds.filter((caseId) => allTreatments.every((series) => series.find((entry) => entry.caseId === caseId)?.metrics[sourceMetricId].availability === "available"));
330
+ const unit = treatment[0]?.metrics[sourceMetricId].unit ?? "count";
331
+ if (matched.length === 0) {
332
+ return { sourceMetricId, matchedCaseIds: [], metric: unavailableMetric(id, unit, "no case has available evidence for this quantity in every treatment.") };
333
+ }
334
+ const values = matched.map((caseId) => treatment.find((entry) => entry.caseId === caseId).metrics[sourceMetricId].value);
335
+ return { sourceMetricId, matchedCaseIds: matched, metric: availableMetric(id, values.reduce((sum, value) => sum + value, 0) / values.length, unit) };
336
+ }
337
+ /**
338
+ * Per-case analysis plus unweighted aggregates. Means are taken over matched cases only (available for every
339
+ * treatment); unavailable values are never converted to zero. No composite, ranking or winner is produced.
340
+ */
341
+ export function analyzeAgentSuccessRate(tasks, caseEvidence, executionMode = AGENT_SUCCESS_RATE_EXECUTION_MODE) {
342
+ if (tasks.length !== caseEvidence.length || tasks.some((task, index) => caseEvidence[index]?.caseId !== task.id)) {
343
+ throw new Error("Agent success analysis requires case evidence in the same order as the selected tasks.");
344
+ }
345
+ const cases = tasks.map((task, index) => {
346
+ const evidence = caseEvidence[index];
347
+ if (evidence.treatments.length !== AGENT_SUCCESS_RATE_TREATMENT_IDS.length || evidence.treatments.some((t, i) => t.treatmentId !== AGENT_SUCCESS_RATE_TREATMENT_IDS[i])) {
348
+ throw new Error(`Agent success analysis requires both treatments in fixed order for case ${task.id}.`);
349
+ }
350
+ return { caseId: task.id, benchmarkProject: task.benchmarkProject, treatments: evidence.treatments.map((t) => analyzeAgentSuccessTreatment(task, t)) };
351
+ });
352
+ const series = AGENT_SUCCESS_RATE_TREATMENT_IDS.map((_, treatmentIndex) => cases.map((c) => c.treatments[treatmentIndex]));
353
+ const caseIds = cases.map((c) => c.caseId);
354
+ const aggregates = AGENT_SUCCESS_RATE_TREATMENT_IDS.map((treatmentId, treatmentIndex) => {
355
+ const own = series[treatmentIndex];
356
+ const evaluable = own.filter((entry) => entry.metrics.taskSuccess.availability === "available");
357
+ const successful = evaluable.filter((entry) => entry.metrics.taskSuccess.value === true);
358
+ const rate = (id) => evaluable.length === 0 ? unavailableMetric(id, "ratio", "no case has a determinate task-success verdict for this treatment.") : availableMetric(id, successful.length / evaluable.length, "ratio");
359
+ const means = {};
360
+ for (const meanId of Object.keys(AGENT_SUCCESS_MEAN_SOURCES))
361
+ means[meanId] = mean(meanId, caseIds, own, series);
362
+ const repair = aggregateRepair(own);
363
+ return {
364
+ treatmentId,
365
+ evaluableCaseCount: evaluable.length,
366
+ successfulCaseCount: successful.length,
367
+ taskSuccessRate: rate("taskSuccessRate"),
368
+ // With repair evidence the initial-attempt rate comes from attempt 1; otherwise one attempt makes it equal the final rate.
369
+ initialAttemptSuccessRate: repair ? repair.initialAttemptSuccessRate : rate("initialAttemptSuccessRate"),
370
+ agentTokenMeasurementsAvailable: own.filter((entry) => entry.metrics.agentTotalTokens.availability === "available").length,
371
+ agentTokenMeasurementsUnavailable: own.filter((entry) => entry.metrics.agentTotalTokens.availability !== "available").length,
372
+ means,
373
+ ...(repair ? { repair } : {})
374
+ };
375
+ });
376
+ if (executionMode === AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE) {
377
+ const comparison = buildComparison(cases);
378
+ const hasRepairEvidence = cases.some((entry) => entry.treatments.some((treatment) => treatment.repair !== undefined));
379
+ return {
380
+ executionMode,
381
+ contextEffectEvaluated: comparison.matchedCaseIds.length > 0,
382
+ treatmentOrder: [...AGENT_SUCCESS_RATE_TREATMENT_IDS],
383
+ caseCount: cases.length,
384
+ cases,
385
+ aggregates,
386
+ limitations: [...AGENT_SUCCESS_RATE_REAL_AGENT_LIMITATIONS],
387
+ comparison,
388
+ ...(hasRepairEvidence ? { repairComparison: buildRepairComparison(cases, comparison.pairedOutcomes) } : {})
389
+ };
390
+ }
391
+ return {
392
+ executionMode: AGENT_SUCCESS_RATE_EXECUTION_MODE,
393
+ contextEffectEvaluated: false,
394
+ treatmentOrder: [...AGENT_SUCCESS_RATE_TREATMENT_IDS],
395
+ caseCount: cases.length,
396
+ cases,
397
+ aggregates,
398
+ limitations: [...AGENT_SUCCESS_RATE_LIMITATIONS]
399
+ };
400
+ }
401
+ /** Descriptive matched-case accounting: only cases with a determinate verdict for every treatment are matched. */
402
+ function buildComparison(cases) {
403
+ const matchedCaseIds = [];
404
+ const incompleteCases = [];
405
+ const pairedOutcomes = { bothSucceeded: 0, onlyRawFullFileSucceeded: 0, onlyContextPackSucceeded: 0, neitherSucceeded: 0 };
406
+ for (const entry of cases) {
407
+ const unavailableTreatmentIds = entry.treatments.filter((t) => t.metrics.taskSuccess.availability !== "available").map((t) => t.treatmentId);
408
+ if (unavailableTreatmentIds.length > 0) {
409
+ incompleteCases.push({ caseId: entry.caseId, unavailableTreatmentIds });
410
+ continue;
411
+ }
412
+ matchedCaseIds.push(entry.caseId);
413
+ const raw = entry.treatments[0].metrics.taskSuccess.value === true;
414
+ const pack = entry.treatments[1].metrics.taskSuccess.value === true;
415
+ if (raw && pack)
416
+ pairedOutcomes.bothSucceeded += 1;
417
+ else if (raw)
418
+ pairedOutcomes.onlyRawFullFileSucceeded += 1;
419
+ else if (pack)
420
+ pairedOutcomes.onlyContextPackSucceeded += 1;
421
+ else
422
+ pairedOutcomes.neitherSucceeded += 1;
423
+ }
424
+ return { basis: "matched-evaluable-cases", matchedCaseIds, incompleteCases, pairedOutcomes };
425
+ }
@@ -0,0 +1,155 @@
1
+ import { AGENT_SUCCESS_METRIC_IDS } from "./analysisTypes.js";
2
+ import { AGENT_SUCCESS_RATE_PLUGIN_ID, AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE, AGENT_SUCCESS_RATE_TREATMENT_IDS } from "./metadata.js";
3
+ import { validateAgentSuccessMetric } from "./metrics.js";
4
+ export const AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE = "agent-success-rate-analysis.json";
5
+ export const AGENT_SUCCESS_RATE_ANALYSIS_SCHEMA_VERSION = "my-dev-kit-lab-agent-success-rate-analysis-v1";
6
+ export const AGENT_SUCCESS_RATE_METHODOLOGY = {
7
+ taskSuccess: "resolved-and-regression-safe-and-protected-intact",
8
+ factSatisfaction: "all-referenced-post-edit-checks-passed",
9
+ verificationGranularity: "command-check-level",
10
+ aggregation: "matched-case-unweighted-mean",
11
+ treatmentComparison: "pipeline-diagnostic-only",
12
+ relativeChurnDenominator: "baseline-text-line-count"
13
+ };
14
+ export function buildAgentSuccessRateAnalysisArtifact(args) {
15
+ return {
16
+ schemaVersion: AGENT_SUCCESS_RATE_ANALYSIS_SCHEMA_VERSION,
17
+ runId: args.runId,
18
+ pluginId: args.pluginId,
19
+ pluginSchemaVersion: args.pluginSchemaVersion,
20
+ startedAt: args.startedAt,
21
+ completedAt: args.completedAt,
22
+ methodology: {
23
+ ...AGENT_SUCCESS_RATE_METHODOLOGY,
24
+ ...(args.analysis.executionMode === AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE ? { treatmentComparison: "descriptive-matched-case" } : {})
25
+ },
26
+ analysis: structuredClone(args.analysis)
27
+ };
28
+ }
29
+ /** Structural validation of an in-memory analysis artifact. Returns problems; empty means valid. */
30
+ export function validateAgentSuccessRateAnalysisArtifact(artifact) {
31
+ const problems = [];
32
+ if (artifact.schemaVersion !== AGENT_SUCCESS_RATE_ANALYSIS_SCHEMA_VERSION)
33
+ problems.push("analysis artifact schemaVersion is not recognized.");
34
+ if (artifact.pluginId !== AGENT_SUCCESS_RATE_PLUGIN_ID)
35
+ problems.push("analysis artifact pluginId does not match.");
36
+ const { analysis } = artifact;
37
+ if (analysis.executionMode === AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE) {
38
+ if (!analysis.comparison)
39
+ problems.push("real-agent analysis is missing the matched-case comparison.");
40
+ else if (analysis.contextEffectEvaluated !== analysis.comparison.matchedCaseIds.length > 0)
41
+ problems.push("analysis contextEffectEvaluated disagrees with the matched cases.");
42
+ if (artifact.methodology.treatmentComparison !== "descriptive-matched-case")
43
+ problems.push("real-agent analysis must use the descriptive matched-case methodology.");
44
+ }
45
+ else {
46
+ if (analysis.contextEffectEvaluated !== false)
47
+ problems.push("analysis must state contextEffectEvaluated=false.");
48
+ if (analysis.comparison !== undefined)
49
+ problems.push("deterministic-fixture analysis must not carry a comparison.");
50
+ }
51
+ if (analysis.treatmentOrder.join(",") !== AGENT_SUCCESS_RATE_TREATMENT_IDS.join(","))
52
+ problems.push("analysis treatmentOrder is not the fixed order.");
53
+ for (const entry of analysis.cases) {
54
+ if (entry.treatments.map((t) => t.treatmentId).join(",") !== AGENT_SUCCESS_RATE_TREATMENT_IDS.join(","))
55
+ problems.push(`analysis case ${entry.caseId} does not hold both treatments in fixed order.`);
56
+ for (const treatment of entry.treatments) {
57
+ for (const id of AGENT_SUCCESS_METRIC_IDS) {
58
+ const metric = treatment.metrics[id];
59
+ if (!metric)
60
+ problems.push(`analysis case ${entry.caseId}/${treatment.treatmentId} is missing metric ${id}.`);
61
+ else
62
+ problems.push(...validateAgentSuccessMetric(metric));
63
+ }
64
+ if (analysis.executionMode !== AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE && treatment.repair !== undefined) {
65
+ problems.push(`analysis case ${entry.caseId}/${treatment.treatmentId} carries repair analysis in deterministic-fixture mode.`);
66
+ }
67
+ if (treatment.repair) {
68
+ const repair = treatment.repair;
69
+ const label = `analysis case ${entry.caseId}/${treatment.treatmentId}`;
70
+ for (const metric of [
71
+ repair.initialAttemptTaskSuccess,
72
+ repair.finalTaskSuccess,
73
+ repair.repairSucceeded,
74
+ repair.firstAttemptProviderDurationMs,
75
+ repair.finalAttemptProviderDurationMs,
76
+ repair.totalProviderDurationMs,
77
+ repair.firstAttemptProviderTokens,
78
+ repair.finalAttemptProviderTokens,
79
+ repair.totalProviderTokens,
80
+ repair.totalEvaluationDurationMs
81
+ ]) {
82
+ problems.push(...validateAgentSuccessMetric(metric));
83
+ }
84
+ if (repair.attempts.length !== repair.attemptCount || repair.repairAttemptCount !== repair.attemptCount - 1)
85
+ problems.push(`${label} attempt counts are inconsistent.`);
86
+ repair.attempts.forEach((attempt, index) => {
87
+ if (attempt.attemptNumber !== index + 1)
88
+ problems.push(`${label} attempt analysis numbering is not contiguous from 1.`);
89
+ for (const id of AGENT_SUCCESS_METRIC_IDS) {
90
+ const metric = attempt.metrics[id];
91
+ if (!metric)
92
+ problems.push(`${label} attempt ${attempt.attemptNumber} is missing metric ${id}.`);
93
+ else
94
+ problems.push(...validateAgentSuccessMetric(metric));
95
+ }
96
+ });
97
+ const final = repair.attempts[repair.attempts.length - 1];
98
+ if (final && JSON.stringify(final.metrics.taskSuccess.value) !== JSON.stringify(treatment.metrics.taskSuccess.value))
99
+ problems.push(`${label} final attempt analysis disagrees with the final metrics.`);
100
+ }
101
+ }
102
+ }
103
+ for (const aggregate of analysis.aggregates) {
104
+ problems.push(...validateAgentSuccessMetric(aggregate.taskSuccessRate), ...validateAgentSuccessMetric(aggregate.initialAttemptSuccessRate));
105
+ if (aggregate.repair) {
106
+ const repair = aggregate.repair;
107
+ problems.push(...validateAgentSuccessMetric(repair.initialAttemptSuccessRate), ...validateAgentSuccessMetric(repair.finalTaskSuccessRate), ...validateAgentSuccessMetric(repair.repairSuccessRate), ...validateAgentSuccessMetric(repair.meanAttemptsPerEvaluableCase));
108
+ for (const total of [...repair.providerDurationMs, ...repair.providerTokens])
109
+ problems.push(...validateAgentSuccessMetric(total.total));
110
+ }
111
+ for (const mean of Object.values(aggregate.means))
112
+ problems.push(...validateAgentSuccessMetric(mean.metric));
113
+ }
114
+ return problems;
115
+ }
116
+ /** Execution and analysis must describe the same run, cases and treatments. */
117
+ export function validateAgentSuccessRateArtifactFamily(execution, analysis) {
118
+ const problems = [];
119
+ if (execution.runId !== analysis.runId)
120
+ problems.push("execution and analysis runId differ.");
121
+ if (execution.pluginSchemaVersion !== analysis.pluginSchemaVersion)
122
+ problems.push("execution and analysis pluginSchemaVersion differ.");
123
+ if (execution.executionMode !== analysis.analysis.executionMode)
124
+ problems.push("execution and analysis executionMode differ.");
125
+ if (execution.contextEffectEvaluated !== analysis.analysis.contextEffectEvaluated)
126
+ problems.push("execution and analysis contextEffectEvaluated differ.");
127
+ if (execution.cases.length !== analysis.analysis.cases.length)
128
+ problems.push("execution and analysis case counts differ.");
129
+ execution.cases.forEach((entry, index) => {
130
+ const counterpart = analysis.analysis.cases[index];
131
+ if (!counterpart || counterpart.caseId !== entry.caseId) {
132
+ problems.push(`execution and analysis case identity differs at index ${index}.`);
133
+ return;
134
+ }
135
+ if (entry.treatments.map((t) => t.treatmentId).join(",") !== counterpart.treatments.map((t) => t.treatmentId).join(",")) {
136
+ problems.push(`execution and analysis treatment identity differs for case ${entry.caseId}.`);
137
+ }
138
+ entry.treatments.forEach((treatment, treatmentIndex) => {
139
+ const executedAttempts = treatment.attempts?.length ?? null;
140
+ const analyzedAttempts = counterpart.treatments[treatmentIndex]?.repair?.attempts.length ?? null;
141
+ if (executedAttempts !== analyzedAttempts)
142
+ problems.push(`execution and analysis attempt counts differ for case ${entry.caseId}/${treatment.treatmentId}.`);
143
+ treatment.attempts?.forEach((attempt, attemptIndex) => {
144
+ const analyzed = counterpart.treatments[treatmentIndex]?.repair?.attempts[attemptIndex];
145
+ if (analyzed && analyzed.attemptNumber !== attempt.attemptNumber)
146
+ problems.push(`execution and analysis attempt identity differs for case ${entry.caseId}/${treatment.treatmentId}.`);
147
+ const verdict = analyzed?.metrics.taskSuccess;
148
+ const analyzedSuccess = verdict && verdict.availability === "available" ? verdict.value : null;
149
+ if (analyzed && analyzedSuccess !== attempt.taskSuccess)
150
+ problems.push(`execution and analysis task success differ for case ${entry.caseId}/${treatment.treatmentId} attempt ${attempt.attemptNumber}.`);
151
+ });
152
+ });
153
+ });
154
+ return problems;
155
+ }
@@ -0,0 +1,65 @@
1
+ /** Stable metric IDs, in the order they are written to artifacts. */
2
+ export const AGENT_SUCCESS_METRIC_IDS = [
3
+ // task success components
4
+ "patchApplied",
5
+ "taskChecksPassed",
6
+ "regressionSafe",
7
+ "requiredFactsSatisfied",
8
+ "protectedIntegrity",
9
+ "taskResolved",
10
+ "taskSuccess",
11
+ // behavior facts
12
+ "requiredFactsTotal",
13
+ "requiredFactsSatisfiedCount",
14
+ "optionalFactsTotal",
15
+ "optionalFactsSatisfiedCount",
16
+ "requiredFactCoverage",
17
+ "factCoverage",
18
+ // verification checks (command/check level only)
19
+ "taskCheckPassedCount",
20
+ "taskCheckTotalCount",
21
+ "taskCheckPassRate",
22
+ "regressionCheckPassedCount",
23
+ "regressionCheckTotalCount",
24
+ "regressionCheckPassRate",
25
+ "regressionFailureCount",
26
+ // edit quality
27
+ "expectedEditFileCount",
28
+ "expectedEditFilesChangedCount",
29
+ "expectedEditCoverage",
30
+ "allowedEditChangedFileCount",
31
+ "unexpectedChangedFileCount",
32
+ "editScopePrecision",
33
+ "protectedMutationCount",
34
+ "attemptedProtectedEditCount",
35
+ // blast radius
36
+ "changedFileCount",
37
+ "addedFileCount",
38
+ "modifiedFileCount",
39
+ "deletedFileCount",
40
+ "linesAdded",
41
+ "linesDeleted",
42
+ "totalChurn",
43
+ "baselineTextLineCount",
44
+ "relativeChurn",
45
+ // time and tokens
46
+ "agentDurationMs",
47
+ "agentTotalTokens",
48
+ "baselineVerificationDurationMs",
49
+ "patchPipelineDurationMs",
50
+ "postEditVerificationDurationMs",
51
+ "evaluationDurationMs"
52
+ ];
53
+ /** Aggregate mean ID -> the per-case metric it averages. */
54
+ export const AGENT_SUCCESS_MEAN_SOURCES = {
55
+ meanTaskCheckPassRate: "taskCheckPassRate",
56
+ meanRegressionCheckPassRate: "regressionCheckPassRate",
57
+ meanRequiredFactCoverage: "requiredFactCoverage",
58
+ meanExpectedEditCoverage: "expectedEditCoverage",
59
+ meanEditScopePrecision: "editScopePrecision",
60
+ meanUnexpectedChangedFileCount: "unexpectedChangedFileCount",
61
+ meanChangedFileCount: "changedFileCount",
62
+ meanTotalChurn: "totalChurn",
63
+ meanRelativeChurn: "relativeChurn",
64
+ meanEvaluationDurationMs: "evaluationDurationMs"
65
+ };