@dailephd/my-dev-kit-lab 0.8.2 → 0.9.1
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/CHANGELOG.md +19 -0
- package/README.md +19 -3
- package/benchmarks/contracts/agent-success-rate-project-profiles.json +33 -0
- package/benchmarks/contracts/agent-success-rate-tasks.json +671 -0
- package/benchmarks/projects/agent-success-inventory-node/.gitattributes +2 -0
- package/benchmarks/projects/agent-success-inventory-node/README.md +74 -0
- package/benchmarks/projects/agent-success-inventory-node/package.json +14 -0
- package/benchmarks/projects/agent-success-inventory-node/src/fulfillmentReport.js +38 -0
- package/benchmarks/projects/agent-success-inventory-node/src/index.js +26 -0
- package/benchmarks/projects/agent-success-inventory-node/src/inventoryStore.js +114 -0
- package/benchmarks/projects/agent-success-inventory-node/src/quantity.js +19 -0
- package/benchmarks/projects/agent-success-inventory-node/src/reservationService.js +45 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/quantity-edge.check.mjs +51 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/quantity-primary.check.mjs +46 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/regression.check.mjs +115 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/report-edge.check.mjs +109 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/report-primary.check.mjs +47 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/reservation-edge.check.mjs +63 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/reservation-primary.check.mjs +44 -0
- package/benchmarks/projects/agent-success-task-board-node/.gitattributes +2 -0
- package/benchmarks/projects/agent-success-task-board-node/README.md +77 -0
- package/benchmarks/projects/agent-success-task-board-node/package.json +14 -0
- package/benchmarks/projects/agent-success-task-board-node/src/index.js +24 -0
- package/benchmarks/projects/agent-success-task-board-node/src/projectSummary.js +15 -0
- package/benchmarks/projects/agent-success-task-board-node/src/taskService.js +58 -0
- package/benchmarks/projects/agent-success-task-board-node/src/taskStore.js +58 -0
- package/benchmarks/projects/agent-success-task-board-node/src/validation.js +31 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/import-edge.check.mjs +65 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/import-primary.check.mjs +31 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/regression.check.mjs +107 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/summary-edge.check.mjs +67 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/summary-primary.check.mjs +54 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/title-edge.check.mjs +39 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/title-primary.check.mjs +29 -0
- package/dist/src/agents/adapters/claudeAdapter.js +6 -3
- package/dist/src/agents/adapters/codexAdapter.js +51 -10
- package/dist/src/agents/index.js +1 -0
- package/dist/src/agents/normalizeAgentTelemetry.js +50 -0
- package/dist/src/agents/parseAgentTokenUsage.js +71 -49
- package/dist/src/agents/providerBuildObservation.js +97 -0
- package/dist/src/audits/core/projectInventory.js +1 -0
- package/dist/src/cli/help.js +44 -1
- package/dist/src/commands/runExperimentDescribeCommand.js +83 -1
- package/dist/src/commands/runExperimentRunCommand.js +309 -6
- package/dist/src/core/processTree.js +55 -0
- package/dist/src/core/runMeasuredCommand.js +108 -22
- package/dist/src/evaluation/agentSuccess/applyPatchToSandbox.js +37 -0
- package/dist/src/evaluation/agentSuccess/assessBaseline.js +23 -0
- package/dist/src/evaluation/agentSuccess/corpusTypes.js +74 -0
- package/dist/src/evaluation/agentSuccess/extractPatchCandidate.js +46 -0
- package/dist/src/evaluation/agentSuccess/index.js +14 -0
- package/dist/src/evaluation/agentSuccess/parseUnifiedDiff.js +210 -0
- package/dist/src/evaluation/agentSuccess/patchTypes.js +5 -0
- package/dist/src/evaluation/agentSuccess/readAgentSuccessCorpus.js +183 -0
- package/dist/src/evaluation/agentSuccess/runVerificationCheck.js +100 -0
- package/dist/src/evaluation/agentSuccess/taskPaths.js +34 -0
- package/dist/src/evaluation/agentSuccess/taskTypes.js +14 -0
- package/dist/src/evaluation/agentSuccess/validateAgentSuccessCorpus.js +307 -0
- package/dist/src/evaluation/agentSuccess/validateAgentSuccessTask.js +297 -0
- package/dist/src/evaluation/agentSuccess/validatePatchPolicy.js +66 -0
- package/dist/src/evaluation/agentSuccess/verificationTypes.js +1 -0
- package/dist/src/evaluation/benchmarkSandbox/createBenchmarkSandbox.js +127 -0
- package/dist/src/evaluation/benchmarkSandbox/errors.js +11 -0
- package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +62 -0
- package/dist/src/evaluation/benchmarkSandbox/index.js +8 -0
- package/dist/src/evaluation/benchmarkSandbox/minimalHostEnv.js +37 -0
- package/dist/src/evaluation/benchmarkSandbox/pathPolicy.js +49 -0
- package/dist/src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.js +74 -0
- package/dist/src/evaluation/benchmarkSandbox/treeSnapshot.js +57 -0
- package/dist/src/evaluation/benchmarkSandbox/types.js +3 -0
- package/dist/src/evaluation/changeSet/captureChangeSet.js +98 -0
- package/dist/src/evaluation/changeSet/index.js +2 -0
- package/dist/src/evaluation/changeSet/types.js +9 -0
- package/dist/src/experiments/defaultRegistry.js +2 -0
- package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
- package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpoint.js +216 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +588 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignEvidenceQuality.js +77 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignExecution.js +314 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignIdentity.js +111 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignProgress.js +173 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignQueue.js +11 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignRecovery.js +256 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignResumableExecution.js +207 -0
- package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
- package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
- package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
- package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
- package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
- package/dist/src/experiments/plugins/agentSuccessRate/index.js +26 -0
- package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
- package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
- package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +517 -0
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +272 -0
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
- package/dist/src/experiments/plugins/agentSuccessRate/timeoutPresets.js +9 -0
- package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
- package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
- package/dist/src/report/experiments/agentSuccessCampaignProgressReportModel.js +1 -0
- package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
- package/dist/src/report/experiments/buildAgentSuccessCampaignProgressReport.js +6 -0
- package/dist/src/report/experiments/buildAgentSuccessRateReport.js +266 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +27 -2
- package/dist/src/report/experiments/index.js +4 -0
- package/dist/src/report/experiments/renderAgentSuccessCampaignProgressHtml.js +6 -0
- package/dist/src/report/experiments/renderAgentSuccessCampaignProgressText.js +25 -0
- package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +171 -0
- package/dist/src/report/experiments/renderAgentSuccessRateText.js +146 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +5 -0
- package/dist/src/report/experiments/writeAgentSuccessCampaignProgressReport.js +135 -0
- package/dist/src/report/experiments/writePluginExperimentReports.js +4 -3
- package/docs/METRICS.md +79 -0
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { AGENT_SUCCESS_BASIS_LABEL, formatAgentSuccessMetric, formatAgentSuccessPairs, formatAgentSuccessTotal, renderCampaignEvidenceSummary } from "./renderAgentSuccessRateText.js";
|
|
2
|
+
function escapeHtml(value) {
|
|
3
|
+
return String(value ?? "")
|
|
4
|
+
.replaceAll("&", "&")
|
|
5
|
+
.replaceAll("<", "<")
|
|
6
|
+
.replaceAll(">", ">")
|
|
7
|
+
.replaceAll('"', """)
|
|
8
|
+
.replaceAll("'", "'");
|
|
9
|
+
}
|
|
10
|
+
function table(headers, rows) {
|
|
11
|
+
return `<table><thead><tr>${headers.map((header) => `<th>${escapeHtml(header)}</th>`).join("")}</tr></thead><tbody>${rows
|
|
12
|
+
.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`)
|
|
13
|
+
.join("")}</tbody></table>`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* HTML rendering of the typed section. All dynamic content is escaped, artifact references are plain text (never links
|
|
17
|
+
* or embedded files), and no value is recalculated here.
|
|
18
|
+
*/
|
|
19
|
+
export function renderAgentSuccessRateHtml(report) {
|
|
20
|
+
if (!report)
|
|
21
|
+
return "";
|
|
22
|
+
const realMode = report.identity.executionMode === "real-agent";
|
|
23
|
+
const m = formatAgentSuccessMetric;
|
|
24
|
+
const parts = [];
|
|
25
|
+
if (report.campaignEvidence)
|
|
26
|
+
parts.push(`<section><h2>Campaign evidence</h2><pre>${escapeHtml(renderCampaignEvidenceSummary(report))}</pre></section>`);
|
|
27
|
+
parts.push(`<section id="agent-success-rate">
|
|
28
|
+
<h2>Agent Success Rate</h2>
|
|
29
|
+
<h3>Experiment identity</h3>
|
|
30
|
+
${table(["Field", "Value"], [
|
|
31
|
+
["Run ID", report.identity.runId],
|
|
32
|
+
["Plugin", `${report.identity.pluginId} (schema ${report.identity.pluginSchemaVersion})`],
|
|
33
|
+
["Run status", report.identity.runStatus],
|
|
34
|
+
["Cases", String(report.identity.caseCount)],
|
|
35
|
+
["Treatments", report.identity.treatmentOrder.join(", ")],
|
|
36
|
+
["Treatment outcomes", String(report.identity.treatmentOutcomeCount)]
|
|
37
|
+
])}
|
|
38
|
+
<h3>Execution mode and provider</h3>
|
|
39
|
+
${table(["Field", "Value"], [
|
|
40
|
+
["Execution mode", report.identity.executionMode],
|
|
41
|
+
["Provider", report.identity.providerId ?? "none (no coding agent was invoked)"],
|
|
42
|
+
["Provider timeout per attempt", report.identity.timeoutMs === null ? "not applicable" : `${report.identity.timeoutMs} ms`],
|
|
43
|
+
["Repair attempts allowed", report.identity.repairAttempts === null ? "not applicable" : String(report.identity.repairAttempts)],
|
|
44
|
+
["Maximum attempts per treatment", String(report.identity.maxAttemptsPerTreatment)]
|
|
45
|
+
])}
|
|
46
|
+
<h3>Scientific limitations</h3>
|
|
47
|
+
<p><strong>${escapeHtml(report.scientificStatement)}</strong></p>
|
|
48
|
+
<ul>${report.limitations.map((limitation) => `<li>${escapeHtml(limitation)}</li>`).join("")}</ul>
|
|
49
|
+
<h3>Overall result availability</h3>
|
|
50
|
+
${table(["Field", "Value"], [
|
|
51
|
+
["Overall", report.resultAvailability.overall],
|
|
52
|
+
["Outcomes with a determinate verdict", `${report.resultAvailability.evaluableTreatmentOutcomes} of ${report.resultAvailability.totalTreatmentOutcomes}`],
|
|
53
|
+
["Outcomes without a determinate verdict", String(report.resultAvailability.unavailableTreatmentOutcomes)]
|
|
54
|
+
])}
|
|
55
|
+
${report.resultAvailability.notes.length > 0 ? `<ul>${report.resultAvailability.notes.map((note) => `<li>${escapeHtml(note)}</li>`).join("")}</ul>` : ""}
|
|
56
|
+
</section>`);
|
|
57
|
+
const treatmentRows = report.treatments.map((treatment) => [
|
|
58
|
+
treatment.treatmentId,
|
|
59
|
+
String(treatment.evaluableCases),
|
|
60
|
+
String(treatment.initialSuccessfulCases),
|
|
61
|
+
m(treatment.initialSuccessRate),
|
|
62
|
+
String(treatment.finalSuccessfulCases),
|
|
63
|
+
m(treatment.finalSuccessRate),
|
|
64
|
+
treatment.repair ? String(treatment.repair.repairAttempts) : "not applicable",
|
|
65
|
+
treatment.repair ? String(treatment.repair.repairedCases) : "not applicable"
|
|
66
|
+
]);
|
|
67
|
+
const comparison = report.comparison;
|
|
68
|
+
parts.push(`<section id="agent-success-rate-comparison">
|
|
69
|
+
<h2>Treatment Comparison</h2>
|
|
70
|
+
${table(["Treatment", "Evaluable cases", "Initial successful cases", "Initial success rate", "Final successful cases", "Final success rate", "Repair attempts", "Repaired cases"], treatmentRows)}
|
|
71
|
+
${report.treatments
|
|
72
|
+
.filter((treatment) => treatment.repair)
|
|
73
|
+
.map((treatment) => `<p class="muted">${escapeHtml(treatment.treatmentId)}: repair-eligible cases ${treatment.repair.repairEligibleCases}; cases with a repair ${treatment.repair.repairAttemptedCases}; repair success rate ${escapeHtml(m(treatment.repair.repairSuccessRate))}; mean attempts per evaluable case ${escapeHtml(m(treatment.repair.meanAttemptsPerEvaluableCase))}.</p>`)
|
|
74
|
+
.join("")}
|
|
75
|
+
${comparison
|
|
76
|
+
? `<h3>Matched comparison (descriptive; no winner is declared)</h3>
|
|
77
|
+
${table(["Statement", "Value"], [
|
|
78
|
+
["Comparison evaluable", comparison.comparisonEvaluable ? "yes" : "no"],
|
|
79
|
+
["Observed outcome difference between treatments", comparison.observedOutcomeDifference],
|
|
80
|
+
["Statistical or causal effect", `${comparison.statisticalEffect} (no significance or causal claim is made)`],
|
|
81
|
+
["Final matched cases", comparison.finalMatchedCaseIds.join(", ") || "none"],
|
|
82
|
+
["Final paired outcomes", formatAgentSuccessPairs(comparison.finalPairedOutcomes)],
|
|
83
|
+
["Initial-attempt matched cases", comparison.initialMatchedCaseIds ? comparison.initialMatchedCaseIds.join(", ") || "none" : "not applicable"],
|
|
84
|
+
["Initial-attempt paired outcomes", formatAgentSuccessPairs(comparison.initialPairedOutcomes)]
|
|
85
|
+
])}
|
|
86
|
+
${comparison.pairedDifferences.length > 0
|
|
87
|
+
? table(["Measurement", "Basis", "Matched cases", "raw-full-file mean", "context-pack mean", "context-pack minus raw-full-file"], comparison.pairedDifferences.map((difference) => [
|
|
88
|
+
difference.id,
|
|
89
|
+
AGENT_SUCCESS_BASIS_LABEL[difference.basis] ?? difference.basis,
|
|
90
|
+
String(difference.matchedCaseIds.length),
|
|
91
|
+
m(difference.rawFullFileMean),
|
|
92
|
+
m(difference.contextPackMean),
|
|
93
|
+
m(difference.meanDifference)
|
|
94
|
+
]))
|
|
95
|
+
: ""}`
|
|
96
|
+
: `<p class="muted">Matched comparison: not applicable (deterministic-fixture mode does not compare context treatments).</p>`}
|
|
97
|
+
</section>`);
|
|
98
|
+
parts.push(`<section id="agent-success-rate-cases">
|
|
99
|
+
<h2>Per-Case Results</h2>
|
|
100
|
+
${table(["Case", "Project", "Locality", "Treatment", "Status", "Initial task success", "Final task success", "Attempts", "Task-check pass rate", "Regression-check pass rate", "Required-fact coverage"], report.cases.flatMap((entry) => entry.treatments.map((treatment) => [
|
|
101
|
+
entry.caseId,
|
|
102
|
+
entry.benchmarkProject,
|
|
103
|
+
entry.taskLocality,
|
|
104
|
+
treatment.treatmentId,
|
|
105
|
+
`${treatment.executionStatus} / ${treatment.evidenceAvailability}`,
|
|
106
|
+
m(treatment.initialTaskSuccess),
|
|
107
|
+
m(treatment.finalTaskSuccess),
|
|
108
|
+
String(treatment.attemptCount),
|
|
109
|
+
m(treatment.taskCheckPassRate),
|
|
110
|
+
m(treatment.regressionCheckPassRate),
|
|
111
|
+
m(treatment.requiredFactCoverage)
|
|
112
|
+
])))}
|
|
113
|
+
</section>`);
|
|
114
|
+
parts.push(`<section id="agent-success-rate-repair-history">
|
|
115
|
+
<h2>Per-Attempt Repair History</h2>
|
|
116
|
+
${!realMode || report.repairHistory.length === 0
|
|
117
|
+
? `<p class="muted">${realMode ? "No attempt history was recorded." : "Not applicable: deterministic-fixture mode runs a single fixture patch and no provider attempts."}</p>`
|
|
118
|
+
: table(["Case", "Treatment", "Attempt", "Provider status", "Patch outcome", "Verification", "Task success", "Outcome", "Duration", "Tokens", "Artifacts"], report.repairHistory.flatMap((history) => history.attempts.map((attempt) => [
|
|
119
|
+
history.caseId,
|
|
120
|
+
history.treatmentId,
|
|
121
|
+
String(attempt.attemptNumber),
|
|
122
|
+
attempt.providerStatus,
|
|
123
|
+
attempt.patchOutcome,
|
|
124
|
+
attempt.verificationAvailability,
|
|
125
|
+
m(attempt.taskSuccess),
|
|
126
|
+
`${attempt.failureCategory}${attempt.repairEligible ? " (repair-eligible)" : ""}`,
|
|
127
|
+
m(attempt.providerDurationMs),
|
|
128
|
+
attempt.tokenAvailability === "available" ? m(attempt.providerTokens) : "unavailable",
|
|
129
|
+
`proposed: ${attempt.proposedPatchPath ?? "none"}; applied: ${attempt.appliedPatchPath ?? "none"}; agent: ${attempt.agentArtifactDirectory ?? "none"}`
|
|
130
|
+
])))}
|
|
131
|
+
</section>`);
|
|
132
|
+
parts.push(`<section id="agent-success-rate-edit-quality">
|
|
133
|
+
<h2>Edit Quality And Blast Radius (final evaluated patch)</h2>
|
|
134
|
+
${table(["Case", "Treatment", "Expected-edit coverage", "Edit-scope precision", "Unexpected changed files", "Protected mutations", "Changed files", "Total churn", "Relative churn"], report.cases.flatMap((entry) => entry.treatments.map((treatment) => [
|
|
135
|
+
entry.caseId,
|
|
136
|
+
treatment.treatmentId,
|
|
137
|
+
m(treatment.expectedEditCoverage),
|
|
138
|
+
m(treatment.editScopePrecision),
|
|
139
|
+
m(treatment.unexpectedChangedFiles),
|
|
140
|
+
m(treatment.protectedMutations),
|
|
141
|
+
`${m(treatment.changedFileCount)}${treatment.changedFiles ? ` [${treatment.changedFiles.items.join(", ")}${treatment.changedFiles.omittedCount > 0 ? `, +${treatment.changedFiles.omittedCount} more` : ""}]` : ""}`,
|
|
142
|
+
m(treatment.totalChurn),
|
|
143
|
+
m(treatment.relativeChurn)
|
|
144
|
+
])))}
|
|
145
|
+
</section>`);
|
|
146
|
+
parts.push(`<section id="agent-success-rate-cost">
|
|
147
|
+
<h2>Duration And Token Evidence</h2>
|
|
148
|
+
${table(["Case", "Treatment", "Final-attempt provider duration", "Final-attempt provider tokens", "Total provider duration (all attempts)", "Total provider tokens (all attempts)"], report.cases.flatMap((entry) => entry.treatments.map((treatment) => [
|
|
149
|
+
entry.caseId,
|
|
150
|
+
treatment.treatmentId,
|
|
151
|
+
m(treatment.finalAttemptProviderDurationMs),
|
|
152
|
+
m(treatment.finalAttemptProviderTokens),
|
|
153
|
+
treatment.totalProviderDurationMs ? m(treatment.totalProviderDurationMs) : "not applicable",
|
|
154
|
+
treatment.totalProviderTokens ? m(treatment.totalProviderTokens) : "not applicable"
|
|
155
|
+
])))}
|
|
156
|
+
${report.treatments
|
|
157
|
+
.filter((treatment) => treatment.repair)
|
|
158
|
+
.flatMap((treatment) => [
|
|
159
|
+
...treatment.repair.providerDurationMs.map((total) => `<p class="muted">${escapeHtml(treatment.treatmentId)} provider duration [${escapeHtml(AGENT_SUCCESS_BASIS_LABEL[total.basis] ?? total.basis)}]: ${escapeHtml(formatAgentSuccessTotal(total))}</p>`),
|
|
160
|
+
...treatment.repair.providerTokens.map((total) => `<p class="muted">${escapeHtml(treatment.treatmentId)} provider tokens [${escapeHtml(AGENT_SUCCESS_BASIS_LABEL[total.basis] ?? total.basis)}]: ${escapeHtml(formatAgentSuccessTotal(total))}</p>`)
|
|
161
|
+
])
|
|
162
|
+
.join("")}
|
|
163
|
+
</section>`);
|
|
164
|
+
const errors = report.cases.flatMap((entry) => entry.treatments.flatMap((treatment) => treatment.errors.map((error) => [entry.caseId, treatment.treatmentId, String(error.attemptNumber), error.code, error.message])));
|
|
165
|
+
parts.push(`<section id="agent-success-rate-findings">
|
|
166
|
+
<h2>Agent Success Rate Warnings, Failures And Artifacts</h2>
|
|
167
|
+
${report.warnings.length === 0 && errors.length === 0 ? "<p>No warnings or recorded errors.</p>" : table(["Source", "Case", "Treatment", "Attempt", "Code", "Message"], [...report.warnings.map((warning) => ["warning", "", "", "", warning.code, warning.message]), ...errors.map((error) => ["error", ...error])])}
|
|
168
|
+
${table(["Artifact", "Reference", "Case", "Treatment"], report.artifacts.map((artifact) => [artifact.id, artifact.path ?? "[reference withheld]", artifact.caseId ?? "", artifact.variantId ?? ""]))}
|
|
169
|
+
</section>`);
|
|
170
|
+
return parts.join("\n\n ");
|
|
171
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export function renderCampaignEvidenceSummary(report) {
|
|
2
|
+
const m = report.campaignEvidence;
|
|
3
|
+
return m ? [`Campaign evidence: ${m.state}; checkpoint revision ${m.checkpointGeneration} (${m.checkpointRevision}); completed evaluations ${m.unitCounts.completed}/${m.totalTreatmentUnits}; completed cases ${m.caseCounts.completed}/${m.selectedCaseCount}`,
|
|
4
|
+
...m.comparisons.map(q => `${q.quantity}: ${q.quality}; ${q.basis}; ${q.measurementBasis}; matched ${q.matchedCaseIds.length}/${q.selectedCaseCount}; excluded ${q.excludedCaseIds.length}; raw=${q.rawFullFileMean ?? "unavailable"}; context=${q.contextPackMean ?? "unavailable"}; delta=${q.meanDifference ?? "unavailable"}; unit=${q.unit}; reasons=${q.reasons.join(", ") || "none"}; ${q.reasonExplanations.join(" ")}`)].join("\n") : "";
|
|
5
|
+
}
|
|
6
|
+
/** One display form for every metric: an absent value is labelled, never rendered as zero. */
|
|
7
|
+
export function formatAgentSuccessMetric(metric) {
|
|
8
|
+
if (!metric)
|
|
9
|
+
return "unavailable (not recorded)";
|
|
10
|
+
if (metric.availability === "unavailable")
|
|
11
|
+
return `unavailable (${metric.reason ?? "no reason recorded"})`;
|
|
12
|
+
if (metric.availability === "not-applicable")
|
|
13
|
+
return `not applicable (${metric.reason ?? "no reason recorded"})`;
|
|
14
|
+
const value = metric.value;
|
|
15
|
+
if (typeof value === "boolean")
|
|
16
|
+
return value ? "yes" : "no";
|
|
17
|
+
if (typeof value !== "number")
|
|
18
|
+
return "unavailable (no value)";
|
|
19
|
+
switch (metric.unit) {
|
|
20
|
+
case "ratio":
|
|
21
|
+
return value.toFixed(4);
|
|
22
|
+
case "ms":
|
|
23
|
+
return `${Math.round(value)} ms`;
|
|
24
|
+
case "tokens":
|
|
25
|
+
return `${Math.round(value)} tokens`;
|
|
26
|
+
case "lines":
|
|
27
|
+
return `${Math.round(value)} lines`;
|
|
28
|
+
default:
|
|
29
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(4);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function formatAgentSuccessTotal(total) {
|
|
33
|
+
const base = `${formatAgentSuccessMetric(total.total)}; contributing cases=${total.contributingCaseCount}, measured=${total.availableCaseCount}, unmeasured=${total.unavailableCaseCount}`;
|
|
34
|
+
return total.unavailableCaseCount > 0 && total.sumOfAvailable !== null ? `${base}; sum of measured cases only=${total.sumOfAvailable} (not a complete total)` : base;
|
|
35
|
+
}
|
|
36
|
+
export function formatAgentSuccessPairs(pairs) {
|
|
37
|
+
if (!pairs)
|
|
38
|
+
return "not recorded";
|
|
39
|
+
return `both succeeded=${pairs.bothSucceeded}, only raw-full-file=${pairs.onlyRawFullFileSucceeded}, only context-pack=${pairs.onlyContextPackSucceeded}, neither=${pairs.neitherSucceeded}`;
|
|
40
|
+
}
|
|
41
|
+
export const AGENT_SUCCESS_BASIS_LABEL = {
|
|
42
|
+
"first-attempt": "first attempt",
|
|
43
|
+
"final-attempt": "final attempt",
|
|
44
|
+
"total-across-attempts": "total across attempts"
|
|
45
|
+
};
|
|
46
|
+
const sanitize = (value) => String(value ?? "").replace(/[\r\n\t]+/g, " ").trim();
|
|
47
|
+
/** Text rendering of the typed section. It formats already-calculated values and recalculates none of them. */
|
|
48
|
+
export function renderAgentSuccessRateTextLines(report) {
|
|
49
|
+
const realMode = report.identity.executionMode === "real-agent";
|
|
50
|
+
const lines = [];
|
|
51
|
+
const section = (title) => {
|
|
52
|
+
lines.push("", title, "-".repeat(title.length));
|
|
53
|
+
};
|
|
54
|
+
lines.push("", "AGENT SUCCESS RATE", "==================");
|
|
55
|
+
section("A. Experiment identity");
|
|
56
|
+
lines.push(`Run ID: ${sanitize(report.identity.runId)}`, `Plugin: ${sanitize(report.identity.pluginId)} (schema ${sanitize(report.identity.pluginSchemaVersion)})`, `Run status: ${sanitize(report.identity.runStatus)}`, `Cases: ${report.identity.caseCount}; treatments: ${report.identity.treatmentOrder.join(", ")}; treatment outcomes: ${report.identity.treatmentOutcomeCount}`);
|
|
57
|
+
section("B. Execution mode and provider");
|
|
58
|
+
lines.push(`Execution mode: ${sanitize(report.identity.executionMode)}`, `Provider: ${report.identity.providerId ?? "none (no coding agent was invoked)"}`, `Provider timeout per attempt: ${report.identity.timeoutMs === null ? "not applicable" : `${report.identity.timeoutMs} ms`}`, `Repair attempts allowed: ${report.identity.repairAttempts === null ? "not applicable" : report.identity.repairAttempts}; maximum attempts per treatment: ${report.identity.maxAttemptsPerTreatment}`);
|
|
59
|
+
section("C. Scientific limitations");
|
|
60
|
+
lines.push(sanitize(report.scientificStatement));
|
|
61
|
+
for (const limitation of report.limitations)
|
|
62
|
+
lines.push(`- ${sanitize(limitation)}`);
|
|
63
|
+
section("D. Overall result availability");
|
|
64
|
+
const availability = report.resultAvailability;
|
|
65
|
+
lines.push(`Overall: ${availability.overall}`, `Treatment outcomes with a determinate task-success verdict: ${availability.evaluableTreatmentOutcomes} of ${availability.totalTreatmentOutcomes}; without: ${availability.unavailableTreatmentOutcomes}`);
|
|
66
|
+
for (const note of availability.notes)
|
|
67
|
+
lines.push(`- ${sanitize(note)}`);
|
|
68
|
+
section("E. Treatment comparison");
|
|
69
|
+
for (const treatment of report.treatments) {
|
|
70
|
+
lines.push(`Treatment ${treatment.treatmentId}:`, ` evaluable cases: ${treatment.evaluableCases}`, ` initial-attempt successful cases: ${treatment.initialSuccessfulCases}; initial success rate: ${formatAgentSuccessMetric(treatment.initialSuccessRate)}`, ` final successful cases: ${treatment.finalSuccessfulCases}; final success rate: ${formatAgentSuccessMetric(treatment.finalSuccessRate)}`);
|
|
71
|
+
if (treatment.repair) {
|
|
72
|
+
lines.push(` repair-eligible cases: ${treatment.repair.repairEligibleCases}; repair attempts executed: ${treatment.repair.repairAttempts}; cases with a repair: ${treatment.repair.repairAttemptedCases}; repaired cases: ${treatment.repair.repairedCases}`, ` repair success rate: ${formatAgentSuccessMetric(treatment.repair.repairSuccessRate)}; mean attempts per evaluable case: ${formatAgentSuccessMetric(treatment.repair.meanAttemptsPerEvaluableCase)}`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
lines.push(" repair: not applicable (deterministic-fixture mode; the single fixture attempt is both initial and final)");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (report.comparison) {
|
|
79
|
+
const comparison = report.comparison;
|
|
80
|
+
lines.push("Matched comparison (descriptive; no winner is declared):", ` comparison evaluable: ${comparison.comparisonEvaluable ? "yes" : "no"}`, ` observed outcome difference between treatments: ${comparison.observedOutcomeDifference}`, ` statistical or causal effect: ${comparison.statisticalEffect} (no significance or causal claim is made)`, ` final matched cases: ${comparison.finalMatchedCaseIds.length > 0 ? comparison.finalMatchedCaseIds.join(", ") : "none"}`, ` final paired outcomes: ${formatAgentSuccessPairs(comparison.finalPairedOutcomes)}`);
|
|
81
|
+
if (comparison.initialMatchedCaseIds) {
|
|
82
|
+
lines.push(` initial-attempt matched cases: ${comparison.initialMatchedCaseIds.length > 0 ? comparison.initialMatchedCaseIds.join(", ") : "none"}`, ` initial-attempt paired outcomes: ${formatAgentSuccessPairs(comparison.initialPairedOutcomes)}`);
|
|
83
|
+
}
|
|
84
|
+
for (const incomplete of comparison.finalIncompleteCases)
|
|
85
|
+
lines.push(` excluded from final matched metrics: ${incomplete.caseId} (no determinate verdict for ${incomplete.unavailableTreatmentIds.join(", ")})`);
|
|
86
|
+
for (const difference of comparison.pairedDifferences) {
|
|
87
|
+
lines.push(` paired difference ${difference.id} [${AGENT_SUCCESS_BASIS_LABEL[difference.basis] ?? difference.basis}] over ${difference.matchedCaseIds.length} matched case(s): raw-full-file ${formatAgentSuccessMetric(difference.rawFullFileMean)}; context-pack ${formatAgentSuccessMetric(difference.contextPackMean)}; context-pack minus raw-full-file ${formatAgentSuccessMetric(difference.meanDifference)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
lines.push("Matched comparison: not applicable (deterministic-fixture mode does not compare context treatments).");
|
|
92
|
+
}
|
|
93
|
+
section("F. Per-case results");
|
|
94
|
+
for (const entry of report.cases) {
|
|
95
|
+
lines.push(`Case ${entry.caseId} (${sanitize(entry.caseName)}) project=${entry.benchmarkProject} locality=${entry.taskLocality}`);
|
|
96
|
+
for (const treatment of entry.treatments) {
|
|
97
|
+
lines.push(` ${treatment.treatmentId}: status=${treatment.executionStatus} evidence=${treatment.evidenceAvailability}`, ` initial task success: ${formatAgentSuccessMetric(treatment.initialTaskSuccess)}; final task success: ${formatAgentSuccessMetric(treatment.finalTaskSuccess)}; attempts=${treatment.attemptCount}; repair attempts=${treatment.repairAttemptCount}; repair succeeded: ${formatAgentSuccessMetric(treatment.repairSucceeded)}`, ` task-check pass rate: ${formatAgentSuccessMetric(treatment.taskCheckPassRate)}; regression-check pass rate: ${formatAgentSuccessMetric(treatment.regressionCheckPassRate)}; required-fact coverage: ${formatAgentSuccessMetric(treatment.requiredFactCoverage)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
section("G. Per-attempt repair history");
|
|
101
|
+
if (!realMode || report.repairHistory.length === 0) {
|
|
102
|
+
lines.push(realMode ? "No attempt history was recorded." : "Not applicable: deterministic-fixture mode runs a single fixture patch and no provider attempts.");
|
|
103
|
+
}
|
|
104
|
+
for (const history of report.repairHistory) {
|
|
105
|
+
lines.push(`Case ${history.caseId} / ${history.treatmentId}: ${history.attempts.length} attempt(s)`);
|
|
106
|
+
for (const attempt of history.attempts) {
|
|
107
|
+
lines.push(` attempt ${attempt.attemptNumber}: provider=${attempt.providerStatus}; patch=${attempt.patchOutcome}; verification=${attempt.verificationAvailability}; task success=${formatAgentSuccessMetric(attempt.taskSuccess)}; outcome=${attempt.failureCategory}${attempt.repairEligible ? " (repair-eligible)" : ""}`, ` duration: ${formatAgentSuccessMetric(attempt.providerDurationMs)}; tokens: ${attempt.tokenAvailability === "available" ? formatAgentSuccessMetric(attempt.providerTokens) : "unavailable"}`, ` artifacts: proposed patch=${attempt.proposedPatchPath ?? "none"}; applied patch=${attempt.appliedPatchPath ?? "none"}; agent files=${attempt.agentArtifactDirectory ?? "none"}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
section("H. Edit-quality and blast-radius measurements (final evaluated patch)");
|
|
111
|
+
for (const entry of report.cases) {
|
|
112
|
+
for (const treatment of entry.treatments) {
|
|
113
|
+
lines.push(`${entry.caseId} / ${treatment.treatmentId}: expected-edit coverage=${formatAgentSuccessMetric(treatment.expectedEditCoverage)}; edit-scope precision=${formatAgentSuccessMetric(treatment.editScopePrecision)}; unexpected changed files=${formatAgentSuccessMetric(treatment.unexpectedChangedFiles)}; protected mutations=${formatAgentSuccessMetric(treatment.protectedMutations)}`, ` changed files=${formatAgentSuccessMetric(treatment.changedFileCount)}${treatment.changedFiles ? ` [${treatment.changedFiles.items.join(", ")}${treatment.changedFiles.omittedCount > 0 ? `, +${treatment.changedFiles.omittedCount} more` : ""}]` : ""}; total churn=${formatAgentSuccessMetric(treatment.totalChurn)}; relative churn=${formatAgentSuccessMetric(treatment.relativeChurn)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
section("I. Duration and token evidence");
|
|
117
|
+
for (const entry of report.cases) {
|
|
118
|
+
for (const treatment of entry.treatments) {
|
|
119
|
+
lines.push(`${entry.caseId} / ${treatment.treatmentId}: final-attempt provider duration=${formatAgentSuccessMetric(treatment.finalAttemptProviderDurationMs)}; final-attempt provider tokens=${formatAgentSuccessMetric(treatment.finalAttemptProviderTokens)}` +
|
|
120
|
+
(treatment.totalProviderDurationMs
|
|
121
|
+
? `; total provider duration across attempts=${formatAgentSuccessMetric(treatment.totalProviderDurationMs)}; total provider tokens across attempts=${formatAgentSuccessMetric(treatment.totalProviderTokens)}`
|
|
122
|
+
: ""));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const treatment of report.treatments) {
|
|
126
|
+
if (!treatment.repair)
|
|
127
|
+
continue;
|
|
128
|
+
for (const total of treatment.repair.providerDurationMs)
|
|
129
|
+
lines.push(`${treatment.treatmentId} provider duration [${AGENT_SUCCESS_BASIS_LABEL[total.basis] ?? total.basis}]: ${formatAgentSuccessTotal(total)}`);
|
|
130
|
+
for (const total of treatment.repair.providerTokens)
|
|
131
|
+
lines.push(`${treatment.treatmentId} provider tokens [${AGENT_SUCCESS_BASIS_LABEL[total.basis] ?? total.basis}]: ${formatAgentSuccessTotal(total)}`);
|
|
132
|
+
}
|
|
133
|
+
section("J. Warnings, failures and artifact references");
|
|
134
|
+
const errors = report.cases.flatMap((entry) => entry.treatments.flatMap((treatment) => treatment.errors.map((error) => ({ caseId: entry.caseId, treatmentId: treatment.treatmentId, ...error }))));
|
|
135
|
+
if (report.warnings.length === 0 && errors.length === 0)
|
|
136
|
+
lines.push("No warnings or recorded errors.");
|
|
137
|
+
for (const warning of report.warnings)
|
|
138
|
+
lines.push(`warning ${sanitize(warning.code)}: ${sanitize(warning.message)}`);
|
|
139
|
+
for (const error of errors)
|
|
140
|
+
lines.push(`error ${error.caseId} / ${error.treatmentId} attempt ${error.attemptNumber} ${sanitize(error.code)}: ${sanitize(error.message)}`);
|
|
141
|
+
for (const artifact of report.artifacts)
|
|
142
|
+
lines.push(`artifact ${sanitize(artifact.id)}: ${artifact.path ?? "[reference withheld]"}`);
|
|
143
|
+
if (report.campaignEvidence)
|
|
144
|
+
lines.push(renderCampaignEvidenceSummary(report));
|
|
145
|
+
return lines;
|
|
146
|
+
}
|
|
@@ -5,6 +5,7 @@ import { renderContextWindowScalingHtml } from "./renderContextWindowScalingHtml
|
|
|
5
5
|
import { renderRetrievalPrecisionRecallHtml } from "./renderRetrievalPrecisionRecallHtml.js";
|
|
6
6
|
import { renderRetrievalQueryStrategyComparisonHtml } from "./renderRetrievalQueryStrategyComparisonHtml.js";
|
|
7
7
|
import { renderContextPackGenerationHtml } from "./renderContextPackGenerationHtml.js";
|
|
8
|
+
import { renderAgentSuccessRateHtml } from "./renderAgentSuccessRateHtml.js";
|
|
8
9
|
export function renderPluginExperimentReportHtml(report) {
|
|
9
10
|
const redactedTarget = report.target.privacyProjection === "external-local-redacted";
|
|
10
11
|
return `<!DOCTYPE html>
|
|
@@ -115,6 +116,8 @@ export function renderPluginExperimentReportHtml(report) {
|
|
|
115
116
|
|
|
116
117
|
${renderContextPackGenerationHtml(report.contextPackGeneration ?? null)}
|
|
117
118
|
|
|
119
|
+
${renderAgentSuccessRateHtml(report.agentSuccessRate ?? null)}
|
|
120
|
+
|
|
118
121
|
<section>
|
|
119
122
|
<h2>Warnings, Skips, And Failures</h2>
|
|
120
123
|
${report.findings.length ? table(["Severity", "Code", "Message", "Variant", "Case"], report.findings.map((finding) => [
|
|
@@ -6,6 +6,7 @@ import { renderContextWindowScalingTextLines } from "./renderContextWindowScalin
|
|
|
6
6
|
import { renderRetrievalPrecisionRecallTextLines } from "./renderRetrievalPrecisionRecallText.js";
|
|
7
7
|
import { renderRetrievalQueryStrategyComparisonTextLines } from "./renderRetrievalQueryStrategyComparisonText.js";
|
|
8
8
|
import { renderContextPackGenerationTextLines } from "./renderContextPackGenerationText.js";
|
|
9
|
+
import { renderAgentSuccessRateTextLines } from "./renderAgentSuccessRateText.js";
|
|
9
10
|
function sanitizeScalar(value) {
|
|
10
11
|
const text = String(value);
|
|
11
12
|
let result = "";
|
|
@@ -691,6 +692,10 @@ export function renderPluginExperimentReportText(report) {
|
|
|
691
692
|
lines.push(...renderRetrievalQueryStrategyComparisonTextLines(retrievalQueryStrategyComparison));
|
|
692
693
|
}
|
|
693
694
|
// Additive: other plugins keep their existing text output unchanged.
|
|
695
|
+
const agentSuccessRate = report.agentSuccessRate ?? null;
|
|
696
|
+
if (agentSuccessRate !== null) {
|
|
697
|
+
lines.push(...renderAgentSuccessRateTextLines(agentSuccessRate));
|
|
698
|
+
}
|
|
694
699
|
const contextPackGeneration = report.contextPackGeneration ?? null;
|
|
695
700
|
if (contextPackGeneration !== null) {
|
|
696
701
|
pushSection(lines, "Context Pack Generation");
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { lstat, mkdir, open, readdir, realpath } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { campaignAssert, campaignHash, canonicalCampaignJson } from "../../experiments/plugins/agentSuccessRate/campaignIdentity.js";
|
|
4
|
+
import { validateCampaignOutputPath } from "../../experiments/plugins/agentSuccessRate/campaignCheckpointStore.js";
|
|
5
|
+
import { readCampaignProgressScientificFamily } from "../../experiments/plugins/agentSuccessRate/campaignProgress.js";
|
|
6
|
+
import { agentSuccessRatePlugin, mapAgentSuccessRateToRun } from "../../experiments/plugins/agentSuccessRate/plugin.js";
|
|
7
|
+
import { writePluginExperimentReports } from "./writePluginExperimentReports.js";
|
|
8
|
+
import { buildPluginExperimentReport } from "./buildPluginExperimentReport.js";
|
|
9
|
+
import { renderPluginExperimentReportText } from "./renderPluginExperimentReportText.js";
|
|
10
|
+
import { renderPluginExperimentReportHtml } from "./renderPluginExperimentReportHtml.js";
|
|
11
|
+
import { buildAgentSuccessCampaignProgressReport } from "./buildAgentSuccessCampaignProgressReport.js";
|
|
12
|
+
import { renderAgentSuccessCampaignProgressText } from "./renderAgentSuccessCampaignProgressText.js";
|
|
13
|
+
import { renderAgentSuccessCampaignProgressHtml } from "./renderAgentSuccessCampaignProgressHtml.js";
|
|
14
|
+
async function plain(p, kind) {
|
|
15
|
+
const s = await lstat(p);
|
|
16
|
+
campaignAssert(!s.isSymbolicLink() && (kind === "file" ? s.isFile() : s.isDirectory()) && path.resolve(await realpath(p)).toLowerCase() === path.resolve(p).toLowerCase(), "PROGRESS_UNSAFE_PATH");
|
|
17
|
+
}
|
|
18
|
+
async function writeExclusive(p, content) {
|
|
19
|
+
const h = await open(p, "wx");
|
|
20
|
+
try {
|
|
21
|
+
await h.writeFile(content, "utf8");
|
|
22
|
+
await h.sync();
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
await h.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function readExpected(p, expected, code) {
|
|
29
|
+
await plain(p, "file");
|
|
30
|
+
const length = Buffer.byteLength(expected);
|
|
31
|
+
campaignAssert(length <= 64 * 1024 * 1024, "PROGRESS_DATA_BOUND");
|
|
32
|
+
const h = await open(p, "r");
|
|
33
|
+
try {
|
|
34
|
+
const before = await h.stat();
|
|
35
|
+
campaignAssert(before.isFile() && before.size === length, code);
|
|
36
|
+
const buffer = Buffer.alloc(length + 1);
|
|
37
|
+
let size = 0;
|
|
38
|
+
for (;;) {
|
|
39
|
+
const n = await h.read(buffer, size, buffer.length - size, size);
|
|
40
|
+
size += n.bytesRead;
|
|
41
|
+
if (!n.bytesRead || size === buffer.length)
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
campaignAssert(size === length && (await h.stat()).size === length && buffer.subarray(0, size).toString("utf8") === expected, code);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
await h.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function owned(parent, name, owner) {
|
|
51
|
+
const p = path.join(parent, name);
|
|
52
|
+
let created = false;
|
|
53
|
+
try {
|
|
54
|
+
await mkdir(p);
|
|
55
|
+
created = true;
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
if (e.code !== "EEXIST")
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
61
|
+
await plain(p, "directory");
|
|
62
|
+
if (created)
|
|
63
|
+
await writeExclusive(path.join(p, "owner.json"), owner);
|
|
64
|
+
await readExpected(path.join(p, "owner.json"), owner, "PROGRESS_OWNER_MISMATCH");
|
|
65
|
+
return p;
|
|
66
|
+
}
|
|
67
|
+
/** Revision-specific files are exclusive. The manifest is published last and verified before returning success. */
|
|
68
|
+
export async function writeAgentSuccessCampaignProgressReport(args) {
|
|
69
|
+
const model = buildAgentSuccessCampaignProgressReport(args.snapshot, args.boundary);
|
|
70
|
+
await validateCampaignOutputPath(args.boundary);
|
|
71
|
+
const root = path.resolve(args.boundary.outputRoot);
|
|
72
|
+
await plain(root, "directory");
|
|
73
|
+
const family = await owned(root, "campaign-progress-v1", "agent-success-rate-campaign-progress-v1");
|
|
74
|
+
const identity = await owned(family, model.campaignIdentity, `agent-success-rate-campaign-progress-v1:${model.campaignIdentity}`);
|
|
75
|
+
// Ownership blockers can change without a checkpoint transition. Keep both projections immutable at that revision.
|
|
76
|
+
const modelDigest = campaignHash(canonicalCampaignJson(model));
|
|
77
|
+
const revision = `revision-${String(model.checkpointGeneration).padStart(10, "0")}-${model.checkpointRevision}-${modelDigest.slice(0, 16)}`;
|
|
78
|
+
const outDir = path.join(identity, revision);
|
|
79
|
+
const scientific = model.state === "complete" ? await readCampaignProgressScientificFamily(args.snapshot, args.boundary) : null;
|
|
80
|
+
const run = scientific ? mapAgentSuccessRateToRun({ runId: scientific.execution.runId, startedAt: scientific.execution.startedAt, completedAt: scientific.execution.completedAt,
|
|
81
|
+
target: { kind: "self", targetRoot: "[redacted]", toolRoot: "[redacted]", packageName: "@dailephd/my-dev-kit-lab", packageVersion: "0.9.0", hasPackageJson: true, hasLockfile: true, branch: null, commit: null, hasGit: false, isSelf: true },
|
|
82
|
+
caseEvidence: scientific.execution.cases.map(c => ({ ...c, caseName: c.caseId })), analysis: scientific.analysis.analysis, patchArtifactPaths: [],
|
|
83
|
+
providerId: scientific.execution.realAgent?.providerId, ...(scientific.execution.realAgent ? { realSettings: { timeoutMs: scientific.execution.realAgent.timeoutMs, repairAttempts: scientific.execution.realAgent.repairAttempts ?? 0 } } : {}) }) : null;
|
|
84
|
+
if (run)
|
|
85
|
+
Object.assign(run, { agentSuccessCampaignProgress: model });
|
|
86
|
+
const files = [
|
|
87
|
+
{ name: "agent-success-rate-campaign-progress.json", content: `${JSON.stringify(model, null, 2)}\n`, phase: "json-written" },
|
|
88
|
+
{ name: "agent-success-rate-campaign-progress.txt", content: renderAgentSuccessCampaignProgressText(model), phase: "text-written" },
|
|
89
|
+
{ name: "agent-success-rate-campaign-progress.html", content: renderAgentSuccessCampaignProgressHtml(model), phase: "html-written" }
|
|
90
|
+
];
|
|
91
|
+
if (run) {
|
|
92
|
+
const built = buildPluginExperimentReport({ run, plugin: agentSuccessRatePlugin.metadata, outputRoot: outDir, generatedAt: run.completedAt });
|
|
93
|
+
const report = { ...built, metadata: { ...built.metadata, outputRoot: "[redacted]" } };
|
|
94
|
+
files.push({ name: "report.json", content: `${JSON.stringify({ report, outputPaths: { outDir: "[redacted]", jsonPath: "report.json", htmlPath: "report.html", textPath: "report.txt" } }, null, 2)}\n`, phase: "json-written" }, { name: "report.txt", content: renderPluginExperimentReportText(report), phase: "text-written" }, { name: "report.html", content: renderPluginExperimentReportHtml(report), phase: "html-written" });
|
|
95
|
+
}
|
|
96
|
+
const marker = canonicalCampaignJson({ schemaVersion: "agent-success-rate-campaign-progress-publication-v1", campaignIdentity: model.campaignIdentity, checkpointGeneration: model.checkpointGeneration, checkpointRevision: model.checkpointRevision, modelDigest, files: files.map(f => ({ name: f.name, sha256: campaignHash(f.content), bytes: Buffer.byteLength(f.content) })) });
|
|
97
|
+
let created = false;
|
|
98
|
+
try {
|
|
99
|
+
await mkdir(outDir);
|
|
100
|
+
created = true;
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
if (e.code !== "EEXIST")
|
|
104
|
+
throw e;
|
|
105
|
+
}
|
|
106
|
+
await plain(outDir, "directory");
|
|
107
|
+
if (created) {
|
|
108
|
+
for (const file of files.slice(0, 3)) {
|
|
109
|
+
await writeExclusive(path.join(outDir, file.name), file.content);
|
|
110
|
+
await args.failureHook?.(file.phase);
|
|
111
|
+
}
|
|
112
|
+
// Exclusive directory reservation owns these ordinary report names; the existing writer remains their owner.
|
|
113
|
+
if (run)
|
|
114
|
+
await writePluginExperimentReports({ run, plugin: agentSuccessRatePlugin.metadata, outputRoot: outDir, generatedAt: run.completedAt, redactOutputRoot: true, exclusive: true });
|
|
115
|
+
for (const f of files)
|
|
116
|
+
await readExpected(path.join(outDir, f.name), f.content, "PROGRESS_SNAPSHOT_INTEGRITY");
|
|
117
|
+
// The legacy writer owns report rendering. Flush its campaign-only output before committing the group marker.
|
|
118
|
+
for (const f of files.slice(3)) {
|
|
119
|
+
const h = await open(path.join(outDir, f.name), "r+");
|
|
120
|
+
try {
|
|
121
|
+
await h.sync();
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
await h.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
await args.failureHook?.("before-manifest");
|
|
128
|
+
await writeExclusive(path.join(outDir, "manifest.json"), marker);
|
|
129
|
+
}
|
|
130
|
+
campaignAssert((await readdir(outDir)).sort().join(",") === [...files.map(f => f.name), "manifest.json"].sort().join(","), "PROGRESS_INCOMPLETE_OR_UNKNOWN_SNAPSHOT");
|
|
131
|
+
await readExpected(path.join(outDir, "manifest.json"), marker, "PROGRESS_SNAPSHOT_COLLISION");
|
|
132
|
+
for (const f of files)
|
|
133
|
+
await readExpected(path.join(outDir, f.name), f.content, "PROGRESS_SNAPSHOT_INTEGRITY");
|
|
134
|
+
return { model, outputPaths: { outDir, jsonPath: path.join(outDir, files[0].name), textPath: path.join(outDir, files[1].name), htmlPath: path.join(outDir, files[2].name), manifestPath: path.join(outDir, "manifest.json") } };
|
|
135
|
+
}
|
|
@@ -31,9 +31,10 @@ export async function writePluginExperimentReports(args) {
|
|
|
31
31
|
const serializedPaths = args.redactOutputRoot
|
|
32
32
|
? { outDir: "[redacted]", jsonPath: "report.json", htmlPath: "report.html", textPath: "report.txt" }
|
|
33
33
|
: outputPaths;
|
|
34
|
-
|
|
35
|
-
await writeFile(outputPaths.
|
|
36
|
-
await writeFile(outputPaths.
|
|
34
|
+
const writeOptions = args.exclusive ? { encoding: "utf8", flag: "wx" } : "utf8";
|
|
35
|
+
await writeFile(outputPaths.jsonPath, `${JSON.stringify({ report, outputPaths: serializedPaths }, null, 2)}\n`, writeOptions);
|
|
36
|
+
await writeFile(outputPaths.htmlPath, renderPluginExperimentReportHtml(report), writeOptions);
|
|
37
|
+
await writeFile(outputPaths.textPath, renderPluginExperimentReportText(report), writeOptions);
|
|
37
38
|
return { report, outputPaths };
|
|
38
39
|
}
|
|
39
40
|
function readString(value) {
|
package/docs/METRICS.md
CHANGED
|
@@ -868,3 +868,82 @@ There is no composite score, winner, or ranking. Tests and call relationships ma
|
|
|
868
868
|
In external-local mode, analysis uses real identities before the privacy projection. Redacted placeholders never enter metric calculations. Durable external-local output has no pack body; the report preview presents redacted identities.
|
|
869
869
|
|
|
870
870
|
See [COMMANDS.md](COMMANDS.md#context-pack-generation-v082), [WORKFLOWS.md](WORKFLOWS.md#context-pack-generation-experiment), and [ARCHITECTURE.md](ARCHITECTURE.md#context-pack-generation-architecture-v082).
|
|
871
|
+
|
|
872
|
+
## Agent-success-rate metrics (v0.9.0)
|
|
873
|
+
|
|
874
|
+
Status: shipped in v0.9.0. The scientific owner of every per-treatment value is `analyzeAgentSuccessTreatment` in `src/experiments/plugins/agentSuccessRate/analysis.ts`; repair aggregation and paired differences are owned by `repairAnalysis.ts`. Values are persisted in `agent-success-rate-analysis.json` (schema `1.0.0`); `agent-success-rate-execution.json` holds the evidence they are computed from. `contextEffectEvaluated` is `false` in deterministic-fixture mode.
|
|
875
|
+
|
|
876
|
+
### Availability rules
|
|
877
|
+
|
|
878
|
+
Every metric is typed `available`, `unavailable`, or `not-applicable`, with a unit and, when not available, a reason.
|
|
879
|
+
|
|
880
|
+
- **Unavailable** means the evidence needed to compute the value does not exist (for example the provider reported no token usage, a check timed out, the patch was not applied, or the baseline was invalid). It is never converted to zero, never counted as a failure, and excluded from rates and means.
|
|
881
|
+
- **Not applicable** means the quantity is undefined by construction (for example a ratio whose denominator is zero, or `repairSucceeded` when no repair attempt ran).
|
|
882
|
+
- **Invalid baseline exclusion:** a treatment whose unmodified baseline is not evaluable (it needs at least one failing task check, all regression checks passing, and no check timing out or erroring) has no task-success verdict and is excluded from rates; this is a benchmark defect, not an agent failure.
|
|
883
|
+
- **Tri-state verdicts:** a verdict is true, false, or unknown. A failed check makes a conjunction false; otherwise a missing, timed-out, or errored check makes it unknown; only complete passing evidence makes it true.
|
|
884
|
+
|
|
885
|
+
### Task-success metrics
|
|
886
|
+
|
|
887
|
+
| Metric | Meaning and formula | Source, units, availability |
|
|
888
|
+
|---|---|---|
|
|
889
|
+
| `taskChecksPassed` | Every trusted task check passed on the changed state | boolean from post-edit verification; unknown if any task check is missing, timed out, or errored |
|
|
890
|
+
| `regressionSafe` | Every regression check that passed on the baseline still passes | boolean; only baseline-passing regression checks are considered |
|
|
891
|
+
| `requiredFactsSatisfied` | Every behavior fact marked required has all its verification checks passing | boolean |
|
|
892
|
+
| `protectedIntegrity` | Protected files are byte-intact after the patch | boolean from the protected-integrity comparison |
|
|
893
|
+
| `taskResolved` | `taskChecksPassed` AND `requiredFactsSatisfied` | boolean |
|
|
894
|
+
| `taskSuccess` | `taskResolved` AND `regressionSafe` AND `protectedIntegrity` intact. A patch that was rejected or not applied yields false; an uncomputable verdict yields unavailable | boolean; the only success verdict; agent prose is never evidence |
|
|
895
|
+
| `initialAttemptTaskSuccess` | `taskSuccess` of attempt 1 | boolean; real-agent repair evidence only |
|
|
896
|
+
| `finalTaskSuccess` | `taskSuccess` of the last attempt executed | boolean; equals `taskSuccess` |
|
|
897
|
+
| `repairSucceeded` | `initialAttemptTaskSuccess` is false AND `finalTaskSuccess` is true | boolean; not applicable when no repair attempt ran; unavailable if either verdict is unavailable |
|
|
898
|
+
| `repairSuccessRate` (aggregate) | repaired cases / repair-attempted cases with a determinate repair verdict | ratio; not applicable when no repair attempt ran for the treatment |
|
|
899
|
+
| `taskSuccessRate`, `initialAttemptSuccessRate`, `finalTaskSuccessRate` (aggregates) | successful cases / cases with a determinate verdict for that attempt basis | ratio; unavailable when no case has a determinate verdict |
|
|
900
|
+
|
|
901
|
+
First versus final attempt: `initialAttemptSuccessRate` and `finalTaskSuccessRate` are separate measurements. A repaired success raises the final rate and never the initial rate. Without repair evidence a single attempt makes the two equal.
|
|
902
|
+
|
|
903
|
+
### Check, fact, and edit-quality metrics
|
|
904
|
+
|
|
905
|
+
| Metric | Formula | Numerator / denominator | Notes |
|
|
906
|
+
|---|---|---|---|
|
|
907
|
+
| `taskCheckPassRate` | passed task checks / task checks | `taskCheckPassedCount` / `taskCheckTotalCount` | unavailable unless every task check has a complete result; not applicable with zero checks |
|
|
908
|
+
| `regressionCheckPassRate` | passed regression checks / regression checks | `regressionCheckPassedCount` / `regressionCheckTotalCount` | `regressionFailureCount` counts failed checks that passed on the baseline |
|
|
909
|
+
| `requiredFactCoverage` | satisfied required facts / required facts | `requiredFactsSatisfiedCount` / `requiredFactsTotal` | not applicable with no required facts |
|
|
910
|
+
| `factCoverage` | satisfied facts (required and optional) / all facts | satisfied / total facts | not applicable with no facts |
|
|
911
|
+
| `expectedEditCoverage` | changed files that were expected / expected edit files | `expectedEditFilesChangedCount` / `expectedEditFileCount` | ratio |
|
|
912
|
+
| `editScopePrecision` | changed files inside the allowed edit scope / changed files | `allowedEditChangedFileCount` / `changedFileCount` | not applicable when no file changed |
|
|
913
|
+
| `unexpectedChangedFileCount` | changed files minus changed files inside the allowed scope | count | files outside the allowed scope |
|
|
914
|
+
| `protectedMutationCount` | protected paths found mutated after the patch | count | unavailable if integrity could not be proven; `attemptedProtectedEditCount` counts protected paths a patch tried to edit |
|
|
915
|
+
| `changedFileCount`, `addedFileCount`, `modifiedFileCount`, `deletedFileCount` | counts of captured changed files by status | count | from the changed-file capture of the final attempt |
|
|
916
|
+
| `linesAdded`, `linesDeleted` | sums of per-file additions and deletions | lines | unavailable if any changed file has no line counts (binary); never estimated |
|
|
917
|
+
| `totalChurn` | `linesAdded` + `linesDeleted` | lines | the final patch's churn, not the churn accumulated over repair attempts |
|
|
918
|
+
| `relativeChurn` | `totalChurn` / `baselineTextLineCount` | ratio | unavailable when the baseline text-line count is unknown; not applicable at zero baseline lines |
|
|
919
|
+
|
|
920
|
+
Edit-quality and blast-radius metrics describe the final attempt only. A treatment that never applied a patch has these metrics unavailable, not zero.
|
|
921
|
+
|
|
922
|
+
### Time and token metrics
|
|
923
|
+
|
|
924
|
+
| Metric | Meaning | Source and caveat |
|
|
925
|
+
|---|---|---|
|
|
926
|
+
| `agentDurationMs` | Measured wall-clock duration of the provider attempt | unavailable in deterministic mode and when no provider ran; for repair runs, `firstAttemptProviderDurationMs`, `finalAttemptProviderDurationMs`, and `totalProviderDurationMs` are reported separately |
|
|
927
|
+
| `totalProviderDurationMs` | Sum of provider durations over attempts that ran | unavailable if any invoked attempt was not measured |
|
|
928
|
+
| `agentTotalTokens` (provider token usage) | Provider-reported total token usage for the attempt | unavailable when the provider reported none (source and reliability are recorded); `totalProviderTokens` sums over attempts and is unavailable if any invoked attempt lacks a total |
|
|
929
|
+
| `estimatedContextTokens` | Estimated size of the context supplied to the provider | a size estimate recorded in execution evidence, not provider-reported usage; never substituted for provider tokens |
|
|
930
|
+
| `baselineVerificationDurationMs`, `patchPipelineDurationMs`, `postEditVerificationDurationMs`, `evaluationDurationMs` | Measured durations of the evaluator's own stages | milliseconds; `totalEvaluationDurationMs` sums attempts |
|
|
931
|
+
|
|
932
|
+
Cumulative versus final: durations and tokens accumulate over all provider attempts (repair costs are included), while edit-quality and blast-radius metrics describe only the final patch.
|
|
933
|
+
|
|
934
|
+
### Paired comparisons and means
|
|
935
|
+
|
|
936
|
+
- Aggregate means (`meanTaskCheckPassRate`, `meanEditScopePrecision`, `meanTotalChurn`, and the other `mean*` entries) are unweighted arithmetic means over **matched** cases: cases for which the source metric is available in every treatment. Unavailable values are not converted to zero. A mean with no matched case is unavailable.
|
|
937
|
+
- The matched comparison counts, over cases with a determinate verdict in both treatments, `bothSucceeded`, `onlyRawFullFileSucceeded`, `onlyContextPackSucceeded`, and `neitherSucceeded`; incomplete cases are listed separately with the unavailable treatment IDs. With repair evidence a second matched comparison uses initial-attempt success.
|
|
938
|
+
- Paired differences report the mean of the `raw-full-file` values, the mean of the `context-pack` values, and the mean of `context-pack` minus `raw-full-file` over matched cases.
|
|
939
|
+
- These are **descriptive**. No weighted composite score, ranking, automatic winner, causal claim, or statistical-significance test is produced (`statisticalEffect` is `not-assessed`). The corpus is finite and controlled, so results do not generalize beyond it, and a single run carries no variance estimate.
|
|
940
|
+
- Provider telemetry can be unavailable, and in deterministic-fixture mode both treatments receive the same reference patch, so those results measure the pipeline only.
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
## Campaign progress evidence (v0.9.1 implemented; unreleased)
|
|
944
|
+
|
|
945
|
+
Token evidence levels are `provider-reported`, `cli-reported`, `parsed-from-output`, `unavailable`, and `estimated`. The original telemetry source is retained. Numeric availability is recorded per token field, so partial measurements remain partial and normalization never fills missing fields or invents totals; unattributed OpenTelemetry evidence is unavailable. Duration provenance is `lab-wall-clock` for valid observed elapsed time and `unavailable` for missing or invalid values; campaign comparison time is Lab-measured wall-clock duration, not provider-native time.
|
|
946
|
+
|
|
947
|
+
The plugin-specific progress projection is `my-dev-kit-lab-agent-success-rate-campaign-progress-v1`; the writer publishes immutable revision-specific JSON, HTML, and text snapshots. It accounts for selected cases and treatment units, while execution epochs remain recovery history and are excluded from selected-unit counts. Paused provider-limit and uncertain states remain distinct. Partial progress is not a complete scientific analysis artifact.
|
|
948
|
+
|
|
949
|
+
Evidence quality is metric-specific: task outcomes, token measurements, and duration can have different strong, partial, or unavailable classifications. Strong describes the completeness and provenance of the available evidence; it is not statistical significance. Partial evidence does not mean task failure. Missing token evidence is unavailable, never zero; estimated tokens are not provider usage. Duration is Lab-measured wall-clock time, not provider-native time. Comparisons use matched completed cases and state their denominator and exclusions; the existing paired direction is preserved. These descriptive comparisons establish no causal effect, winner, p-value, or general provider performance.
|