@dailephd/my-dev-kit-lab 0.8.1 → 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 (127) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +20 -4
  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 +54 -2
  36. package/dist/src/commands/runExperimentDescribeCommand.js +60 -2
  37. package/dist/src/commands/runExperimentRunCommand.js +208 -5
  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/evaluation/runMyDevKitRetrieval.js +1 -0
  68. package/dist/src/experiments/defaultRegistry.js +4 -0
  69. package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
  70. package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
  71. package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
  72. package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
  73. package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
  74. package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
  75. package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
  76. package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
  77. package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
  78. package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
  79. package/dist/src/experiments/plugins/agentSuccessRate/index.js +20 -0
  80. package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
  81. package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
  82. package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +507 -0
  83. package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +250 -0
  84. package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
  85. package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
  86. package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
  87. package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
  88. package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
  89. package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
  90. package/dist/src/experiments/plugins/contextPackGeneration/analysis.js +157 -0
  91. package/dist/src/experiments/plugins/contextPackGeneration/analysisArtifact.js +22 -0
  92. package/dist/src/experiments/plugins/contextPackGeneration/analysisTypes.js +1 -0
  93. package/dist/src/experiments/plugins/contextPackGeneration/buildContextPack.js +84 -0
  94. package/dist/src/experiments/plugins/contextPackGeneration/config.js +39 -0
  95. package/dist/src/experiments/plugins/contextPackGeneration/execution.js +361 -0
  96. package/dist/src/experiments/plugins/contextPackGeneration/executionArtifact.js +17 -0
  97. package/dist/src/experiments/plugins/contextPackGeneration/executionTypes.js +11 -0
  98. package/dist/src/experiments/plugins/contextPackGeneration/identityEvidence.js +71 -0
  99. package/dist/src/experiments/plugins/contextPackGeneration/index.js +19 -0
  100. package/dist/src/experiments/plugins/contextPackGeneration/localSubjectExecution.js +146 -0
  101. package/dist/src/experiments/plugins/contextPackGeneration/localSubjectPrivacy.js +276 -0
  102. package/dist/src/experiments/plugins/contextPackGeneration/metadata.js +24 -0
  103. package/dist/src/experiments/plugins/contextPackGeneration/metrics.js +46 -0
  104. package/dist/src/experiments/plugins/contextPackGeneration/packArtifact.js +17 -0
  105. package/dist/src/experiments/plugins/contextPackGeneration/packEvidence.js +152 -0
  106. package/dist/src/experiments/plugins/contextPackGeneration/packSelectionPolicy.js +305 -0
  107. package/dist/src/experiments/plugins/contextPackGeneration/plugin.js +355 -0
  108. package/dist/src/experiments/plugins/contextPackGeneration/renderContextPack.js +61 -0
  109. package/dist/src/experiments/plugins/contextPackGeneration/types.js +29 -0
  110. package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
  111. package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
  112. package/dist/src/report/experiments/buildAgentSuccessRateReport.js +255 -0
  113. package/dist/src/report/experiments/buildContextPackGenerationReport.js +171 -0
  114. package/dist/src/report/experiments/buildPluginExperimentReport.js +49 -2
  115. package/dist/src/report/experiments/contextPackGenerationReportModel.js +9 -0
  116. package/dist/src/report/experiments/index.js +9 -0
  117. package/dist/src/report/experiments/loadContextPackArtifacts.js +43 -0
  118. package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +169 -0
  119. package/dist/src/report/experiments/renderAgentSuccessRateText.js +139 -0
  120. package/dist/src/report/experiments/renderContextPackGenerationHtml.js +122 -0
  121. package/dist/src/report/experiments/renderContextPackGenerationText.js +101 -0
  122. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +6 -0
  123. package/dist/src/report/experiments/renderPluginExperimentReportText.js +12 -0
  124. package/dist/src/report/experiments/renderRetrievalQueryStrategyComparisonHtml.js +15 -15
  125. package/dist/src/report/experiments/writePluginExperimentReports.js +2 -0
  126. package/docs/METRICS.md +98 -1
  127. package/package.json +1 -1
@@ -28,6 +28,15 @@ export * from "./retrievalPrecisionRecallReportModel.js";
28
28
  export { buildRetrievalPrecisionRecallReport, RETRIEVAL_PRECISION_RECALL_LIMITATIONS } from "./buildRetrievalPrecisionRecallReport.js";
29
29
  export { renderRetrievalPrecisionRecallHtml } from "./renderRetrievalPrecisionRecallHtml.js";
30
30
  export { renderRetrievalPrecisionRecallTextLines } from "./renderRetrievalPrecisionRecallText.js";
31
+ export * from "./contextPackGenerationReportModel.js";
32
+ export { buildContextPackGenerationReport, CONTEXT_PACK_GENERATION_LIMITATIONS } from "./buildContextPackGenerationReport.js";
33
+ export { loadContextPackArtifacts } from "./loadContextPackArtifacts.js";
34
+ export { renderContextPackGenerationHtml } from "./renderContextPackGenerationHtml.js";
35
+ export { renderContextPackGenerationTextLines } from "./renderContextPackGenerationText.js";
36
+ export * from "./agentSuccessRateReportModel.js";
37
+ export { buildAgentSuccessRateReport, safeAgentSuccessArtifactReference } from "./buildAgentSuccessRateReport.js";
38
+ export { renderAgentSuccessRateHtml } from "./renderAgentSuccessRateHtml.js";
39
+ export { renderAgentSuccessRateTextLines } from "./renderAgentSuccessRateText.js";
31
40
  export * from "./retrievalQueryStrategyComparisonReportModel.js";
32
41
  export { buildRetrievalQueryStrategyComparisonReport, RETRIEVAL_QUERY_STRATEGY_COMPARISON_LIMITATIONS } from "./buildRetrievalQueryStrategyComparisonReport.js";
33
42
  export { renderRetrievalQueryStrategyComparisonHtml } from "./renderRetrievalQueryStrategyComparisonHtml.js";
@@ -0,0 +1,43 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolveWithinRoot } from "../../core/pathSafety.js";
3
+ import { CONTEXT_PACK_GENERATION_PLUGIN_ID } from "../../experiments/plugins/contextPackGeneration/metadata.js";
4
+ import { CONTEXT_PACK_SCHEMA_VERSION } from "../../experiments/plugins/contextPackGeneration/types.js";
5
+ function isContextPack(value, caseId) {
6
+ if (typeof value !== "object" || value === null)
7
+ return false;
8
+ const pack = value;
9
+ return (pack.schemaVersion === CONTEXT_PACK_SCHEMA_VERSION &&
10
+ pack.caseId === caseId &&
11
+ typeof pack.task === "object" &&
12
+ pack.task !== null &&
13
+ Array.isArray(pack.files) &&
14
+ Array.isArray(pack.symbols) &&
15
+ Array.isArray(pack.sourceSlices) &&
16
+ Array.isArray(pack.callRelationships) &&
17
+ Array.isArray(pack.tests) &&
18
+ Array.isArray(pack.evidenceNotes));
19
+ }
20
+ /**
21
+ * Reads the persisted per-case pack artifacts the plugin already wrote under the output root. It never reruns retrieval or
22
+ * reopens repository source. A missing or invalid artifact is simply absent: the report then marks that preview unavailable.
23
+ */
24
+ export async function loadContextPackArtifacts(run, outputRoot) {
25
+ const packs = new Map();
26
+ if (run.pluginId !== CONTEXT_PACK_GENERATION_PLUGIN_ID)
27
+ return packs;
28
+ const evidence = run.caseExecutionEvidence ?? [];
29
+ for (const entry of evidence) {
30
+ const relativePath = entry.treatments.find((treatment) => treatment.treatmentId === "context-pack")?.packArtifactPath;
31
+ if (!relativePath)
32
+ continue;
33
+ try {
34
+ const parsed = JSON.parse(await readFile(resolveWithinRoot(outputRoot, relativePath), "utf8"));
35
+ if (isContextPack(parsed, entry.caseId))
36
+ packs.set(entry.caseId, parsed);
37
+ }
38
+ catch {
39
+ // Unreadable or unsafe artifact: the preview is reported as pack-artifact-unavailable, never as an empty pack.
40
+ }
41
+ }
42
+ return packs;
43
+ }
@@ -0,0 +1,169 @@
1
+ import { AGENT_SUCCESS_BASIS_LABEL, formatAgentSuccessMetric, formatAgentSuccessPairs, formatAgentSuccessTotal } from "./renderAgentSuccessRateText.js";
2
+ function escapeHtml(value) {
3
+ return String(value ?? "")
4
+ .replaceAll("&", "&")
5
+ .replaceAll("<", "&lt;")
6
+ .replaceAll(">", "&gt;")
7
+ .replaceAll('"', "&quot;")
8
+ .replaceAll("'", "&#39;");
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
+ parts.push(`<section id="agent-success-rate">
26
+ <h2>Agent Success Rate</h2>
27
+ <h3>Experiment identity</h3>
28
+ ${table(["Field", "Value"], [
29
+ ["Run ID", report.identity.runId],
30
+ ["Plugin", `${report.identity.pluginId} (schema ${report.identity.pluginSchemaVersion})`],
31
+ ["Run status", report.identity.runStatus],
32
+ ["Cases", String(report.identity.caseCount)],
33
+ ["Treatments", report.identity.treatmentOrder.join(", ")],
34
+ ["Treatment outcomes", String(report.identity.treatmentOutcomeCount)]
35
+ ])}
36
+ <h3>Execution mode and provider</h3>
37
+ ${table(["Field", "Value"], [
38
+ ["Execution mode", report.identity.executionMode],
39
+ ["Provider", report.identity.providerId ?? "none (no coding agent was invoked)"],
40
+ ["Provider timeout per attempt", report.identity.timeoutMs === null ? "not applicable" : `${report.identity.timeoutMs} ms`],
41
+ ["Repair attempts allowed", report.identity.repairAttempts === null ? "not applicable" : String(report.identity.repairAttempts)],
42
+ ["Maximum attempts per treatment", String(report.identity.maxAttemptsPerTreatment)]
43
+ ])}
44
+ <h3>Scientific limitations</h3>
45
+ <p><strong>${escapeHtml(report.scientificStatement)}</strong></p>
46
+ <ul>${report.limitations.map((limitation) => `<li>${escapeHtml(limitation)}</li>`).join("")}</ul>
47
+ <h3>Overall result availability</h3>
48
+ ${table(["Field", "Value"], [
49
+ ["Overall", report.resultAvailability.overall],
50
+ ["Outcomes with a determinate verdict", `${report.resultAvailability.evaluableTreatmentOutcomes} of ${report.resultAvailability.totalTreatmentOutcomes}`],
51
+ ["Outcomes without a determinate verdict", String(report.resultAvailability.unavailableTreatmentOutcomes)]
52
+ ])}
53
+ ${report.resultAvailability.notes.length > 0 ? `<ul>${report.resultAvailability.notes.map((note) => `<li>${escapeHtml(note)}</li>`).join("")}</ul>` : ""}
54
+ </section>`);
55
+ const treatmentRows = report.treatments.map((treatment) => [
56
+ treatment.treatmentId,
57
+ String(treatment.evaluableCases),
58
+ String(treatment.initialSuccessfulCases),
59
+ m(treatment.initialSuccessRate),
60
+ String(treatment.finalSuccessfulCases),
61
+ m(treatment.finalSuccessRate),
62
+ treatment.repair ? String(treatment.repair.repairAttempts) : "not applicable",
63
+ treatment.repair ? String(treatment.repair.repairedCases) : "not applicable"
64
+ ]);
65
+ const comparison = report.comparison;
66
+ parts.push(`<section id="agent-success-rate-comparison">
67
+ <h2>Treatment Comparison</h2>
68
+ ${table(["Treatment", "Evaluable cases", "Initial successful cases", "Initial success rate", "Final successful cases", "Final success rate", "Repair attempts", "Repaired cases"], treatmentRows)}
69
+ ${report.treatments
70
+ .filter((treatment) => treatment.repair)
71
+ .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>`)
72
+ .join("")}
73
+ ${comparison
74
+ ? `<h3>Matched comparison (descriptive; no winner is declared)</h3>
75
+ ${table(["Statement", "Value"], [
76
+ ["Comparison evaluable", comparison.comparisonEvaluable ? "yes" : "no"],
77
+ ["Observed outcome difference between treatments", comparison.observedOutcomeDifference],
78
+ ["Statistical or causal effect", `${comparison.statisticalEffect} (no significance or causal claim is made)`],
79
+ ["Final matched cases", comparison.finalMatchedCaseIds.join(", ") || "none"],
80
+ ["Final paired outcomes", formatAgentSuccessPairs(comparison.finalPairedOutcomes)],
81
+ ["Initial-attempt matched cases", comparison.initialMatchedCaseIds ? comparison.initialMatchedCaseIds.join(", ") || "none" : "not applicable"],
82
+ ["Initial-attempt paired outcomes", formatAgentSuccessPairs(comparison.initialPairedOutcomes)]
83
+ ])}
84
+ ${comparison.pairedDifferences.length > 0
85
+ ? table(["Measurement", "Basis", "Matched cases", "raw-full-file mean", "context-pack mean", "context-pack minus raw-full-file"], comparison.pairedDifferences.map((difference) => [
86
+ difference.id,
87
+ AGENT_SUCCESS_BASIS_LABEL[difference.basis] ?? difference.basis,
88
+ String(difference.matchedCaseIds.length),
89
+ m(difference.rawFullFileMean),
90
+ m(difference.contextPackMean),
91
+ m(difference.meanDifference)
92
+ ]))
93
+ : ""}`
94
+ : `<p class="muted">Matched comparison: not applicable (deterministic-fixture mode does not compare context treatments).</p>`}
95
+ </section>`);
96
+ parts.push(`<section id="agent-success-rate-cases">
97
+ <h2>Per-Case Results</h2>
98
+ ${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) => [
99
+ entry.caseId,
100
+ entry.benchmarkProject,
101
+ entry.taskLocality,
102
+ treatment.treatmentId,
103
+ `${treatment.executionStatus} / ${treatment.evidenceAvailability}`,
104
+ m(treatment.initialTaskSuccess),
105
+ m(treatment.finalTaskSuccess),
106
+ String(treatment.attemptCount),
107
+ m(treatment.taskCheckPassRate),
108
+ m(treatment.regressionCheckPassRate),
109
+ m(treatment.requiredFactCoverage)
110
+ ])))}
111
+ </section>`);
112
+ parts.push(`<section id="agent-success-rate-repair-history">
113
+ <h2>Per-Attempt Repair History</h2>
114
+ ${!realMode || report.repairHistory.length === 0
115
+ ? `<p class="muted">${realMode ? "No attempt history was recorded." : "Not applicable: deterministic-fixture mode runs a single fixture patch and no provider attempts."}</p>`
116
+ : table(["Case", "Treatment", "Attempt", "Provider status", "Patch outcome", "Verification", "Task success", "Outcome", "Duration", "Tokens", "Artifacts"], report.repairHistory.flatMap((history) => history.attempts.map((attempt) => [
117
+ history.caseId,
118
+ history.treatmentId,
119
+ String(attempt.attemptNumber),
120
+ attempt.providerStatus,
121
+ attempt.patchOutcome,
122
+ attempt.verificationAvailability,
123
+ m(attempt.taskSuccess),
124
+ `${attempt.failureCategory}${attempt.repairEligible ? " (repair-eligible)" : ""}`,
125
+ m(attempt.providerDurationMs),
126
+ attempt.tokenAvailability === "available" ? m(attempt.providerTokens) : "unavailable",
127
+ `proposed: ${attempt.proposedPatchPath ?? "none"}; applied: ${attempt.appliedPatchPath ?? "none"}; agent: ${attempt.agentArtifactDirectory ?? "none"}`
128
+ ])))}
129
+ </section>`);
130
+ parts.push(`<section id="agent-success-rate-edit-quality">
131
+ <h2>Edit Quality And Blast Radius (final evaluated patch)</h2>
132
+ ${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) => [
133
+ entry.caseId,
134
+ treatment.treatmentId,
135
+ m(treatment.expectedEditCoverage),
136
+ m(treatment.editScopePrecision),
137
+ m(treatment.unexpectedChangedFiles),
138
+ m(treatment.protectedMutations),
139
+ `${m(treatment.changedFileCount)}${treatment.changedFiles ? ` [${treatment.changedFiles.items.join(", ")}${treatment.changedFiles.omittedCount > 0 ? `, +${treatment.changedFiles.omittedCount} more` : ""}]` : ""}`,
140
+ m(treatment.totalChurn),
141
+ m(treatment.relativeChurn)
142
+ ])))}
143
+ </section>`);
144
+ parts.push(`<section id="agent-success-rate-cost">
145
+ <h2>Duration And Token Evidence</h2>
146
+ ${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) => [
147
+ entry.caseId,
148
+ treatment.treatmentId,
149
+ m(treatment.finalAttemptProviderDurationMs),
150
+ m(treatment.finalAttemptProviderTokens),
151
+ treatment.totalProviderDurationMs ? m(treatment.totalProviderDurationMs) : "not applicable",
152
+ treatment.totalProviderTokens ? m(treatment.totalProviderTokens) : "not applicable"
153
+ ])))}
154
+ ${report.treatments
155
+ .filter((treatment) => treatment.repair)
156
+ .flatMap((treatment) => [
157
+ ...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>`),
158
+ ...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>`)
159
+ ])
160
+ .join("")}
161
+ </section>`);
162
+ 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])));
163
+ parts.push(`<section id="agent-success-rate-findings">
164
+ <h2>Agent Success Rate Warnings, Failures And Artifacts</h2>
165
+ ${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])])}
166
+ ${table(["Artifact", "Reference", "Case", "Treatment"], report.artifacts.map((artifact) => [artifact.id, artifact.path ?? "[reference withheld]", artifact.caseId ?? "", artifact.variantId ?? ""]))}
167
+ </section>`);
168
+ return parts.join("\n\n ");
169
+ }
@@ -0,0 +1,139 @@
1
+ /** One display form for every metric: an absent value is labelled, never rendered as zero. */
2
+ export function formatAgentSuccessMetric(metric) {
3
+ if (!metric)
4
+ return "unavailable (not recorded)";
5
+ if (metric.availability === "unavailable")
6
+ return `unavailable (${metric.reason ?? "no reason recorded"})`;
7
+ if (metric.availability === "not-applicable")
8
+ return `not applicable (${metric.reason ?? "no reason recorded"})`;
9
+ const value = metric.value;
10
+ if (typeof value === "boolean")
11
+ return value ? "yes" : "no";
12
+ if (typeof value !== "number")
13
+ return "unavailable (no value)";
14
+ switch (metric.unit) {
15
+ case "ratio":
16
+ return value.toFixed(4);
17
+ case "ms":
18
+ return `${Math.round(value)} ms`;
19
+ case "tokens":
20
+ return `${Math.round(value)} tokens`;
21
+ case "lines":
22
+ return `${Math.round(value)} lines`;
23
+ default:
24
+ return Number.isInteger(value) ? String(value) : value.toFixed(4);
25
+ }
26
+ }
27
+ export function formatAgentSuccessTotal(total) {
28
+ const base = `${formatAgentSuccessMetric(total.total)}; contributing cases=${total.contributingCaseCount}, measured=${total.availableCaseCount}, unmeasured=${total.unavailableCaseCount}`;
29
+ return total.unavailableCaseCount > 0 && total.sumOfAvailable !== null ? `${base}; sum of measured cases only=${total.sumOfAvailable} (not a complete total)` : base;
30
+ }
31
+ export function formatAgentSuccessPairs(pairs) {
32
+ if (!pairs)
33
+ return "not recorded";
34
+ return `both succeeded=${pairs.bothSucceeded}, only raw-full-file=${pairs.onlyRawFullFileSucceeded}, only context-pack=${pairs.onlyContextPackSucceeded}, neither=${pairs.neitherSucceeded}`;
35
+ }
36
+ export const AGENT_SUCCESS_BASIS_LABEL = {
37
+ "first-attempt": "first attempt",
38
+ "final-attempt": "final attempt",
39
+ "total-across-attempts": "total across attempts"
40
+ };
41
+ const sanitize = (value) => String(value ?? "").replace(/[\r\n\t]+/g, " ").trim();
42
+ /** Text rendering of the typed section. It formats already-calculated values and recalculates none of them. */
43
+ export function renderAgentSuccessRateTextLines(report) {
44
+ const realMode = report.identity.executionMode === "real-agent";
45
+ const lines = [];
46
+ const section = (title) => {
47
+ lines.push("", title, "-".repeat(title.length));
48
+ };
49
+ lines.push("", "AGENT SUCCESS RATE", "==================");
50
+ section("A. Experiment identity");
51
+ 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}`);
52
+ section("B. Execution mode and provider");
53
+ 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}`);
54
+ section("C. Scientific limitations");
55
+ lines.push(sanitize(report.scientificStatement));
56
+ for (const limitation of report.limitations)
57
+ lines.push(`- ${sanitize(limitation)}`);
58
+ section("D. Overall result availability");
59
+ const availability = report.resultAvailability;
60
+ lines.push(`Overall: ${availability.overall}`, `Treatment outcomes with a determinate task-success verdict: ${availability.evaluableTreatmentOutcomes} of ${availability.totalTreatmentOutcomes}; without: ${availability.unavailableTreatmentOutcomes}`);
61
+ for (const note of availability.notes)
62
+ lines.push(`- ${sanitize(note)}`);
63
+ section("E. Treatment comparison");
64
+ for (const treatment of report.treatments) {
65
+ 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)}`);
66
+ if (treatment.repair) {
67
+ 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)}`);
68
+ }
69
+ else {
70
+ lines.push(" repair: not applicable (deterministic-fixture mode; the single fixture attempt is both initial and final)");
71
+ }
72
+ }
73
+ if (report.comparison) {
74
+ const comparison = report.comparison;
75
+ 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)}`);
76
+ if (comparison.initialMatchedCaseIds) {
77
+ lines.push(` initial-attempt matched cases: ${comparison.initialMatchedCaseIds.length > 0 ? comparison.initialMatchedCaseIds.join(", ") : "none"}`, ` initial-attempt paired outcomes: ${formatAgentSuccessPairs(comparison.initialPairedOutcomes)}`);
78
+ }
79
+ for (const incomplete of comparison.finalIncompleteCases)
80
+ lines.push(` excluded from final matched metrics: ${incomplete.caseId} (no determinate verdict for ${incomplete.unavailableTreatmentIds.join(", ")})`);
81
+ for (const difference of comparison.pairedDifferences) {
82
+ 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)}`);
83
+ }
84
+ }
85
+ else {
86
+ lines.push("Matched comparison: not applicable (deterministic-fixture mode does not compare context treatments).");
87
+ }
88
+ section("F. Per-case results");
89
+ for (const entry of report.cases) {
90
+ lines.push(`Case ${entry.caseId} (${sanitize(entry.caseName)}) project=${entry.benchmarkProject} locality=${entry.taskLocality}`);
91
+ for (const treatment of entry.treatments) {
92
+ 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)}`);
93
+ }
94
+ }
95
+ section("G. Per-attempt repair history");
96
+ if (!realMode || report.repairHistory.length === 0) {
97
+ lines.push(realMode ? "No attempt history was recorded." : "Not applicable: deterministic-fixture mode runs a single fixture patch and no provider attempts.");
98
+ }
99
+ for (const history of report.repairHistory) {
100
+ lines.push(`Case ${history.caseId} / ${history.treatmentId}: ${history.attempts.length} attempt(s)`);
101
+ for (const attempt of history.attempts) {
102
+ 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"}`);
103
+ }
104
+ }
105
+ section("H. Edit-quality and blast-radius measurements (final evaluated patch)");
106
+ for (const entry of report.cases) {
107
+ for (const treatment of entry.treatments) {
108
+ 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)}`);
109
+ }
110
+ }
111
+ section("I. Duration and token evidence");
112
+ for (const entry of report.cases) {
113
+ for (const treatment of entry.treatments) {
114
+ lines.push(`${entry.caseId} / ${treatment.treatmentId}: final-attempt provider duration=${formatAgentSuccessMetric(treatment.finalAttemptProviderDurationMs)}; final-attempt provider tokens=${formatAgentSuccessMetric(treatment.finalAttemptProviderTokens)}` +
115
+ (treatment.totalProviderDurationMs
116
+ ? `; total provider duration across attempts=${formatAgentSuccessMetric(treatment.totalProviderDurationMs)}; total provider tokens across attempts=${formatAgentSuccessMetric(treatment.totalProviderTokens)}`
117
+ : ""));
118
+ }
119
+ }
120
+ for (const treatment of report.treatments) {
121
+ if (!treatment.repair)
122
+ continue;
123
+ for (const total of treatment.repair.providerDurationMs)
124
+ lines.push(`${treatment.treatmentId} provider duration [${AGENT_SUCCESS_BASIS_LABEL[total.basis] ?? total.basis}]: ${formatAgentSuccessTotal(total)}`);
125
+ for (const total of treatment.repair.providerTokens)
126
+ lines.push(`${treatment.treatmentId} provider tokens [${AGENT_SUCCESS_BASIS_LABEL[total.basis] ?? total.basis}]: ${formatAgentSuccessTotal(total)}`);
127
+ }
128
+ section("J. Warnings, failures and artifact references");
129
+ const errors = report.cases.flatMap((entry) => entry.treatments.flatMap((treatment) => treatment.errors.map((error) => ({ caseId: entry.caseId, treatmentId: treatment.treatmentId, ...error }))));
130
+ if (report.warnings.length === 0 && errors.length === 0)
131
+ lines.push("No warnings or recorded errors.");
132
+ for (const warning of report.warnings)
133
+ lines.push(`warning ${sanitize(warning.code)}: ${sanitize(warning.message)}`);
134
+ for (const error of errors)
135
+ lines.push(`error ${error.caseId} / ${error.treatmentId} attempt ${error.attemptNumber} ${sanitize(error.code)}: ${sanitize(error.message)}`);
136
+ for (const artifact of report.artifacts)
137
+ lines.push(`artifact ${sanitize(artifact.id)}: ${artifact.path ?? "[reference withheld]"}`);
138
+ return lines;
139
+ }
@@ -0,0 +1,122 @@
1
+ import { formatContextPackNumber, redactedPreviewDisplayLines } from "./renderContextPackGenerationText.js";
2
+ import { formatRetrievalQueryStrategyMetric } from "./renderRetrievalQueryStrategyComparisonText.js";
3
+ const formatMean = (value) => (value === undefined || value === null ? "unavailable" : value.toFixed(4));
4
+ export function renderContextPackGenerationHtml(section) {
5
+ if (section === null)
6
+ return "";
7
+ return `<section>
8
+ <h2>Context Pack Generation</h2>
9
+ <h3>Methodology</h3>
10
+ ${table(["Field", "Value"], [
11
+ ["Schema", section.schemaVersion],
12
+ ["Experiment", `${section.experiment.pluginId} (${section.experiment.target})`],
13
+ ["Treatment order", section.treatmentOrder.join(", ")],
14
+ ["File F1", section.methodology.fileF1],
15
+ ["Symbol F1", section.methodology.symbolF1],
16
+ ["Aggregation", section.methodology.aggregation],
17
+ ["Treatment comparison", section.methodology.treatmentComparison],
18
+ ["Size measure", section.methodology.sizeMeasure]
19
+ ])}
20
+ <h3>Per-Case Treatments</h3>
21
+ ${table(["Case", "Project", "Task Type", "Treatment", "Status", "Availability", "File F1", "Symbol F1", "Fact Coverage", "Estimated Tokens"], section.cases.flatMap((entry) => entry.treatments.map((treatment) => [
22
+ entry.caseId,
23
+ entry.benchmarkProject,
24
+ entry.taskLocality ?? "unavailable",
25
+ treatment.treatmentId,
26
+ treatment.executionStatus,
27
+ treatment.availability ?? "unavailable",
28
+ formatRetrievalQueryStrategyMetric(treatment.fileF1),
29
+ formatRetrievalQueryStrategyMetric(treatment.symbolF1),
30
+ formatRetrievalQueryStrategyMetric(treatment.factCoverage),
31
+ formatContextPackNumber(treatment.estimatedTokens)
32
+ ])))}
33
+ <h3>Paired Comparison (context-pack minus raw-full-file)</h3>
34
+ ${table(["Case", "File F1 Delta", "Symbol F1 Delta", "Fact Coverage Delta", "Estimated Token Delta", "Tokens Saved", "Percent Saved"], section.cases.map((entry) => [
35
+ entry.caseId,
36
+ formatMean(entry.comparison.fileF1Delta),
37
+ formatMean(entry.comparison.symbolF1Delta),
38
+ formatMean(entry.comparison.factCoverageDelta),
39
+ formatContextPackNumber(entry.comparison.estimatedTokenDelta),
40
+ formatContextPackNumber(entry.comparison.tokensSaved),
41
+ formatMean(entry.comparison.percentSaved)
42
+ ]))}
43
+ <h3>Scope Aggregates</h3>
44
+ ${section.scopes
45
+ .map((scope) => `<h4>${escapeHtml(scope.scopeId)}</h4>
46
+ <p>Cases ${scope.caseCount}; included ${scope.includedCaseCount} (${escapeHtml(scope.includedCaseIds.join(", ") || "none")}); excluded ${scope.excludedCaseCount} (${escapeHtml(scope.excludedCaseIds.join(", ") || "none")}).</p>
47
+ ${table(["Treatment", "Completed", "Partial", "Failed", "Mean File F1", "Mean Symbol F1", "Mean Fact Coverage", "Mean Estimated Tokens"], scope.treatmentSummaries.map((summary) => [
48
+ summary.treatmentId,
49
+ String(summary.completedCaseCount),
50
+ String(summary.partialCaseCount),
51
+ String(summary.failedCaseCount),
52
+ formatMean(summary.objectives?.meanFileF1),
53
+ formatMean(summary.objectives?.meanSymbolF1),
54
+ formatMean(summary.objectives?.meanFactCoverage),
55
+ formatContextPackNumber(summary.objectives?.meanEstimatedTokenCount)
56
+ ]))}
57
+ ${table(["Mean File F1 Delta", "Mean Symbol F1 Delta", "Mean Fact Coverage Delta", "Mean Estimated Token Delta", "Mean Tokens Saved", "Percent Saved Of Means"], [
58
+ [
59
+ formatMean(scope.pairedDeltas?.meanFileF1Delta),
60
+ formatMean(scope.pairedDeltas?.meanSymbolF1Delta),
61
+ formatMean(scope.pairedDeltas?.meanFactCoverageDelta),
62
+ formatContextPackNumber(scope.pairedDeltas?.meanEstimatedTokenDelta),
63
+ formatContextPackNumber(scope.tokenSavings?.meanTokensSaved),
64
+ formatMean(scope.tokenSavings?.percentSavedOfMeans)
65
+ ]
66
+ ])}`)
67
+ .join("\n ")}
68
+ <h3>Context Pack Preview</h3>
69
+ <p>Bounded for display only; stored packs and measured sizes are unchanged.</p>
70
+ ${section.previews.map(previewHtml).join("\n ")}
71
+ <h3>Interpretation Limits</h3>
72
+ ${listHtml(section.limitations)}
73
+ </section>`;
74
+ }
75
+ function previewHtml(preview) {
76
+ if (preview.status === "redacted-external-local") {
77
+ return `<h4>${escapeHtml(preview.caseId)}</h4>
78
+ <p>Status ${escapeHtml(preview.status)}; pack availability ${escapeHtml(preview.packAvailability ?? "unavailable")}.</p>
79
+ ${table(["Section", "Availability", "Items", "Estimated Tokens"], preview.sections.map((s) => [s.id, s.availability, String(s.itemCount), String(s.estimatedTokens)]))}
80
+ ${listHtml(redactedPreviewDisplayLines(preview))}`;
81
+ }
82
+ const head = `<h4>${escapeHtml(preview.caseId)}</h4>
83
+ <p>Status ${escapeHtml(preview.status)}; pack availability ${escapeHtml(preview.packAvailability ?? "unavailable")}.</p>
84
+ ${table(["Section", "Availability", "Items", "Estimated Tokens"], preview.sections.map((s) => [s.id, s.availability, String(s.itemCount), String(s.estimatedTokens)]))}`;
85
+ if (preview.task === null)
86
+ return head;
87
+ const count = (list) => `shown ${list.items.length} of ${list.totalCount}; omitted ${list.omittedCount}`;
88
+ return `${head}
89
+ <h5>1. Task</h5>
90
+ <p><strong>${escapeHtml(preview.task.title)}</strong> ${escapeHtml(preview.task.summary)}</p>
91
+ <h5>2. Relevant files (${count(preview.files)})</h5>
92
+ ${listHtml(preview.files.items.map((file) => `${file.path} rank=${file.rank} reason=${file.reason}`))}
93
+ <h5>3. Relevant symbols (${count(preview.symbols)})</h5>
94
+ ${listHtml(preview.symbols.items.map((symbol) => `${symbol.name} file=${symbol.file ?? "unknown"} line=${symbol.line ?? "unknown"}`))}
95
+ <h5>4. Source slices (${count(preview.sourceSlices)})</h5>
96
+ ${preview
97
+ .sourceSlices.items.map((slice) => `<p>${escapeHtml(`${slice.file}:${slice.startLine}-${slice.endLine}`)} boundary known: ${slice.boundaryKnown}; truncated by experiment policy: ${slice.truncated}; truncated by report preview: ${slice.previewTruncated} (showing ${slice.previewLineCount} of ${slice.lineCount} lines)</p>
98
+ <pre>${escapeHtml(slice.previewText)}</pre>`)
99
+ .join("\n ")}
100
+ <h5>5. Call relationships (${count(preview.callRelationships)})</h5>
101
+ ${listHtml(preview.callRelationships.items.map((call) => `${call.fromNodeId} calls ${call.toNodeId}`))}
102
+ <h5>6. Tests (${count(preview.tests)})</h5>
103
+ ${listHtml(preview.tests.items.map((test) => `${test.path} how=${test.how}`))}
104
+ <h5>7. Evidence notes (${count(preview.evidenceNotes)})</h5>
105
+ ${listHtml(preview.evidenceNotes.items)}`;
106
+ }
107
+ function listHtml(items) {
108
+ return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
109
+ }
110
+ function table(headers, rows) {
111
+ return `<table><thead><tr>${headers.map((header) => `<th>${escapeHtml(header)}</th>`).join("")}</tr></thead><tbody>${rows
112
+ .map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`)
113
+ .join("")}</tbody></table>`;
114
+ }
115
+ function escapeHtml(value) {
116
+ return String(value ?? "")
117
+ .replaceAll("&", "&amp;")
118
+ .replaceAll("<", "&lt;")
119
+ .replaceAll(">", "&gt;")
120
+ .replaceAll('"', "&quot;")
121
+ .replaceAll("'", "&#39;");
122
+ }
@@ -0,0 +1,101 @@
1
+ import { formatRetrievalQueryStrategyMetric, formatRetrievalQueryStrategyNumber } from "./renderRetrievalQueryStrategyComparisonText.js";
2
+ const formatMean = (value) => (value === undefined || value === null ? "unavailable" : value.toFixed(4));
3
+ export const formatContextPackNumber = (value) => formatRetrievalQueryStrategyNumber(value ?? null);
4
+ const omitted = (list) => `shown=${list.items.length} total=${list.totalCount} omitted=${list.omittedCount}`;
5
+ const REDACTED_SECTION_LABELS = {
6
+ files: { label: "Relevant files", what: "identities redacted" },
7
+ symbols: { label: "Relevant symbols", what: "identities redacted" },
8
+ sourceSlices: { label: "Source slices", what: "content redacted" },
9
+ callRelationships: { label: "Call relationships", what: "identities redacted" },
10
+ tests: { label: "Tests", what: "identities redacted" },
11
+ evidenceNotes: { label: "Evidence notes", what: "counts only" }
12
+ };
13
+ /**
14
+ * Literal, fixed-text lines for an external-local preview, generated from persisted safe counts only. Shared by the text
15
+ * and HTML renderers so both show identical wording; no identity or source text can reach this function.
16
+ */
17
+ export function redactedPreviewDisplayLines(preview) {
18
+ const lines = ["Task: title and summary redacted"];
19
+ for (const section of preview.sections) {
20
+ const entry = REDACTED_SECTION_LABELS[section.id];
21
+ if (!entry)
22
+ continue;
23
+ const truncated = section.truncatedCount !== null ? `, ${section.truncatedCount} truncated by experiment policy` : "";
24
+ lines.push(`${entry.label}: ${section.itemCount} ${section.itemCount === 1 ? "item" : "items"}${truncated} — ${entry.what}`);
25
+ }
26
+ return lines;
27
+ }
28
+ function redactedPreviewLines(preview) {
29
+ const lines = [`Pack preview: case=${preview.caseId} status=${preview.status} packAvailability=${preview.packAvailability ?? "unavailable"}`];
30
+ for (const section of preview.sections) {
31
+ lines.push(` section ${section.id}: availability=${section.availability} items=${section.itemCount} estimatedTokens=${section.estimatedTokens}${section.reason ? ` reason=${section.reason}` : ""}`);
32
+ }
33
+ for (const [index, line] of redactedPreviewDisplayLines(preview).entries())
34
+ lines.push(` ${index + 1}. ${line}`);
35
+ return lines;
36
+ }
37
+ function previewLines(preview) {
38
+ if (preview.status === "redacted-external-local")
39
+ return redactedPreviewLines(preview);
40
+ const lines = [`Pack preview: case=${preview.caseId} status=${preview.status} packAvailability=${preview.packAvailability ?? "unavailable"}`];
41
+ for (const section of preview.sections) {
42
+ lines.push(` section ${section.id}: availability=${section.availability} items=${section.itemCount} estimatedTokens=${section.estimatedTokens}${section.reason ? ` reason=${section.reason}` : ""}`);
43
+ }
44
+ if (preview.task === null)
45
+ return lines;
46
+ lines.push(` 1. Task: ${preview.task.title}`, ` ${preview.task.summary}`);
47
+ lines.push(` 2. Relevant files (${omitted(preview.files)})`);
48
+ for (const file of preview.files.items)
49
+ lines.push(` - ${file.path} rank=${file.rank} reason=${file.reason}`);
50
+ lines.push(` 3. Relevant symbols (${omitted(preview.symbols)})`);
51
+ for (const symbol of preview.symbols.items)
52
+ lines.push(` - ${symbol.name} file=${symbol.file ?? "unknown"} line=${symbol.line ?? "unknown"}`);
53
+ lines.push(` 4. Source slices (${omitted(preview.sourceSlices)})`);
54
+ for (const slice of preview.sourceSlices.items) {
55
+ lines.push(` - ${slice.file}:${slice.startLine}-${slice.endLine} boundaryKnown=${slice.boundaryKnown} policyTruncated=${slice.truncated} previewTruncated=${slice.previewTruncated} (showing ${slice.previewLineCount} of ${slice.lineCount} lines)`);
56
+ for (const sourceLine of slice.previewText.split("\n"))
57
+ lines.push(` | ${sourceLine}`);
58
+ }
59
+ lines.push(` 5. Call relationships (${omitted(preview.callRelationships)})`);
60
+ for (const call of preview.callRelationships.items)
61
+ lines.push(` - ${call.fromNodeId} calls ${call.toNodeId}`);
62
+ lines.push(` 6. Tests (${omitted(preview.tests)})`);
63
+ for (const test of preview.tests.items)
64
+ lines.push(` - ${test.path} how=${test.how}`);
65
+ lines.push(` 7. Evidence notes (${omitted(preview.evidenceNotes)})`);
66
+ for (const note of preview.evidenceNotes.items)
67
+ lines.push(` - ${note}`);
68
+ return lines;
69
+ }
70
+ export function renderContextPackGenerationTextLines(section) {
71
+ const lines = [];
72
+ lines.push(`Schema: ${section.schemaVersion}`);
73
+ lines.push(`Experiment: ${section.experiment.pluginId} target=${section.experiment.target}`);
74
+ lines.push(`Treatment order: ${section.treatmentOrder.join(", ")}`);
75
+ lines.push(`Methodology: fileF1=${section.methodology.fileF1} symbolF1=${section.methodology.symbolF1} aggregation=${section.methodology.aggregation} comparison=${section.methodology.treatmentComparison} size=${section.methodology.sizeMeasure}`);
76
+ lines.push("", "Per-Case Treatments");
77
+ for (const entry of section.cases) {
78
+ for (const treatment of entry.treatments) {
79
+ lines.push(`- case=${entry.caseId} project=${entry.benchmarkProject} taskType=${entry.taskLocality ?? "unavailable"} treatment=${treatment.treatmentId} status=${treatment.executionStatus} availability=${treatment.availability ?? "unavailable"} fileF1=${formatRetrievalQueryStrategyMetric(treatment.fileF1)} symbolF1=${formatRetrievalQueryStrategyMetric(treatment.symbolF1)} factCoverage=${formatRetrievalQueryStrategyMetric(treatment.factCoverage)} estimatedTokens=${formatContextPackNumber(treatment.estimatedTokens)}`);
80
+ }
81
+ const c = entry.comparison;
82
+ lines.push(` paired case=${entry.caseId} fileF1Delta=${formatMean(c.fileF1Delta)} symbolF1Delta=${formatMean(c.symbolF1Delta)} factCoverageDelta=${formatMean(c.factCoverageDelta)} estimatedTokenDelta=${formatContextPackNumber(c.estimatedTokenDelta)} tokensSaved=${formatContextPackNumber(c.tokensSaved)} percentSaved=${formatMean(c.percentSaved)}`);
83
+ }
84
+ lines.push("", "Scope Aggregates");
85
+ for (const scope of section.scopes) {
86
+ lines.push(`Scope ${scope.scopeId}: cases=${scope.caseCount} included=${scope.includedCaseCount} excluded=${scope.excludedCaseCount} includedIds=${scope.includedCaseIds.join(", ") || "none"} excludedIds=${scope.excludedCaseIds.join(", ") || "none"}`);
87
+ for (const summary of scope.treatmentSummaries) {
88
+ const o = summary.objectives;
89
+ lines.push(`- ${summary.treatmentId} completed=${summary.completedCaseCount} partial=${summary.partialCaseCount} failed=${summary.failedCaseCount} meanFileF1=${formatMean(o?.meanFileF1)} meanSymbolF1=${formatMean(o?.meanSymbolF1)} meanFactCoverage=${formatMean(o?.meanFactCoverage)} meanEstimatedTokens=${formatContextPackNumber(o?.meanEstimatedTokenCount)}`);
90
+ }
91
+ const d = scope.pairedDeltas;
92
+ lines.push(` paired meanFileF1Delta=${formatMean(d?.meanFileF1Delta)} meanSymbolF1Delta=${formatMean(d?.meanSymbolF1Delta)} meanFactCoverageDelta=${formatMean(d?.meanFactCoverageDelta)} meanEstimatedTokenDelta=${formatContextPackNumber(d?.meanEstimatedTokenDelta)} meanTokensSaved=${formatContextPackNumber(scope.tokenSavings?.meanTokensSaved)} percentSavedOfMeans=${formatMean(scope.tokenSavings?.percentSavedOfMeans)}`);
93
+ }
94
+ lines.push("", "Context Pack Preview (bounded for display; stored packs are unchanged)");
95
+ for (const preview of section.previews)
96
+ lines.push(...previewLines(preview));
97
+ lines.push("", "Interpretation Limits");
98
+ for (const limitation of section.limitations)
99
+ lines.push(`- ${limitation}`);
100
+ return lines;
101
+ }
@@ -4,6 +4,8 @@ import { renderIncrementalChangeStalenessHtml } from "./renderIncrementalChangeS
4
4
  import { renderContextWindowScalingHtml } from "./renderContextWindowScalingHtml.js";
5
5
  import { renderRetrievalPrecisionRecallHtml } from "./renderRetrievalPrecisionRecallHtml.js";
6
6
  import { renderRetrievalQueryStrategyComparisonHtml } from "./renderRetrievalQueryStrategyComparisonHtml.js";
7
+ import { renderContextPackGenerationHtml } from "./renderContextPackGenerationHtml.js";
8
+ import { renderAgentSuccessRateHtml } from "./renderAgentSuccessRateHtml.js";
7
9
  export function renderPluginExperimentReportHtml(report) {
8
10
  const redactedTarget = report.target.privacyProjection === "external-local-redacted";
9
11
  return `<!DOCTYPE html>
@@ -112,6 +114,10 @@ export function renderPluginExperimentReportHtml(report) {
112
114
 
113
115
  ${renderRetrievalQueryStrategyComparisonHtml(report.retrievalQueryStrategyComparison ?? null)}
114
116
 
117
+ ${renderContextPackGenerationHtml(report.contextPackGeneration ?? null)}
118
+
119
+ ${renderAgentSuccessRateHtml(report.agentSuccessRate ?? null)}
120
+
115
121
  <section>
116
122
  <h2>Warnings, Skips, And Failures</h2>
117
123
  ${report.findings.length ? table(["Severity", "Code", "Message", "Variant", "Case"], report.findings.map((finding) => [