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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +18 -3
  3. package/benchmarks/contracts/agent-success-rate-project-profiles.json +33 -0
  4. package/benchmarks/contracts/agent-success-rate-tasks.json +671 -0
  5. package/benchmarks/projects/agent-success-inventory-node/.gitattributes +2 -0
  6. package/benchmarks/projects/agent-success-inventory-node/README.md +74 -0
  7. package/benchmarks/projects/agent-success-inventory-node/package.json +14 -0
  8. package/benchmarks/projects/agent-success-inventory-node/src/fulfillmentReport.js +38 -0
  9. package/benchmarks/projects/agent-success-inventory-node/src/index.js +26 -0
  10. package/benchmarks/projects/agent-success-inventory-node/src/inventoryStore.js +114 -0
  11. package/benchmarks/projects/agent-success-inventory-node/src/quantity.js +19 -0
  12. package/benchmarks/projects/agent-success-inventory-node/src/reservationService.js +45 -0
  13. package/benchmarks/projects/agent-success-inventory-node/tests/quantity-edge.check.mjs +51 -0
  14. package/benchmarks/projects/agent-success-inventory-node/tests/quantity-primary.check.mjs +46 -0
  15. package/benchmarks/projects/agent-success-inventory-node/tests/regression.check.mjs +115 -0
  16. package/benchmarks/projects/agent-success-inventory-node/tests/report-edge.check.mjs +109 -0
  17. package/benchmarks/projects/agent-success-inventory-node/tests/report-primary.check.mjs +47 -0
  18. package/benchmarks/projects/agent-success-inventory-node/tests/reservation-edge.check.mjs +63 -0
  19. package/benchmarks/projects/agent-success-inventory-node/tests/reservation-primary.check.mjs +44 -0
  20. package/benchmarks/projects/agent-success-task-board-node/.gitattributes +2 -0
  21. package/benchmarks/projects/agent-success-task-board-node/README.md +77 -0
  22. package/benchmarks/projects/agent-success-task-board-node/package.json +14 -0
  23. package/benchmarks/projects/agent-success-task-board-node/src/index.js +24 -0
  24. package/benchmarks/projects/agent-success-task-board-node/src/projectSummary.js +15 -0
  25. package/benchmarks/projects/agent-success-task-board-node/src/taskService.js +58 -0
  26. package/benchmarks/projects/agent-success-task-board-node/src/taskStore.js +58 -0
  27. package/benchmarks/projects/agent-success-task-board-node/src/validation.js +31 -0
  28. package/benchmarks/projects/agent-success-task-board-node/tests/import-edge.check.mjs +65 -0
  29. package/benchmarks/projects/agent-success-task-board-node/tests/import-primary.check.mjs +31 -0
  30. package/benchmarks/projects/agent-success-task-board-node/tests/regression.check.mjs +107 -0
  31. package/benchmarks/projects/agent-success-task-board-node/tests/summary-edge.check.mjs +67 -0
  32. package/benchmarks/projects/agent-success-task-board-node/tests/summary-primary.check.mjs +54 -0
  33. package/benchmarks/projects/agent-success-task-board-node/tests/title-edge.check.mjs +39 -0
  34. package/benchmarks/projects/agent-success-task-board-node/tests/title-primary.check.mjs +29 -0
  35. package/dist/src/cli/help.js +38 -1
  36. package/dist/src/commands/runExperimentDescribeCommand.js +52 -1
  37. package/dist/src/commands/runExperimentRunCommand.js +118 -2
  38. package/dist/src/core/processTree.js +55 -0
  39. package/dist/src/core/runMeasuredCommand.js +104 -22
  40. package/dist/src/evaluation/agentSuccess/applyPatchToSandbox.js +37 -0
  41. package/dist/src/evaluation/agentSuccess/assessBaseline.js +23 -0
  42. package/dist/src/evaluation/agentSuccess/corpusTypes.js +74 -0
  43. package/dist/src/evaluation/agentSuccess/extractPatchCandidate.js +46 -0
  44. package/dist/src/evaluation/agentSuccess/index.js +14 -0
  45. package/dist/src/evaluation/agentSuccess/parseUnifiedDiff.js +210 -0
  46. package/dist/src/evaluation/agentSuccess/patchTypes.js +5 -0
  47. package/dist/src/evaluation/agentSuccess/readAgentSuccessCorpus.js +183 -0
  48. package/dist/src/evaluation/agentSuccess/runVerificationCheck.js +100 -0
  49. package/dist/src/evaluation/agentSuccess/taskPaths.js +34 -0
  50. package/dist/src/evaluation/agentSuccess/taskTypes.js +14 -0
  51. package/dist/src/evaluation/agentSuccess/validateAgentSuccessCorpus.js +307 -0
  52. package/dist/src/evaluation/agentSuccess/validateAgentSuccessTask.js +297 -0
  53. package/dist/src/evaluation/agentSuccess/validatePatchPolicy.js +66 -0
  54. package/dist/src/evaluation/agentSuccess/verificationTypes.js +1 -0
  55. package/dist/src/evaluation/benchmarkSandbox/createBenchmarkSandbox.js +127 -0
  56. package/dist/src/evaluation/benchmarkSandbox/errors.js +11 -0
  57. package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +60 -0
  58. package/dist/src/evaluation/benchmarkSandbox/index.js +8 -0
  59. package/dist/src/evaluation/benchmarkSandbox/minimalHostEnv.js +37 -0
  60. package/dist/src/evaluation/benchmarkSandbox/pathPolicy.js +49 -0
  61. package/dist/src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.js +74 -0
  62. package/dist/src/evaluation/benchmarkSandbox/treeSnapshot.js +57 -0
  63. package/dist/src/evaluation/benchmarkSandbox/types.js +3 -0
  64. package/dist/src/evaluation/changeSet/captureChangeSet.js +98 -0
  65. package/dist/src/evaluation/changeSet/index.js +2 -0
  66. package/dist/src/evaluation/changeSet/types.js +9 -0
  67. package/dist/src/experiments/defaultRegistry.js +2 -0
  68. package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
  69. package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
  70. package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
  71. package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
  72. package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
  73. package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
  74. package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
  75. package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
  76. package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
  77. package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
  78. package/dist/src/experiments/plugins/agentSuccessRate/index.js +20 -0
  79. package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
  80. package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
  81. package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +507 -0
  82. package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +250 -0
  83. package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
  84. package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
  85. package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
  86. package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
  87. package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
  88. package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
  89. package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
  90. package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
  91. package/dist/src/report/experiments/buildAgentSuccessRateReport.js +255 -0
  92. package/dist/src/report/experiments/buildPluginExperimentReport.js +27 -2
  93. package/dist/src/report/experiments/index.js +4 -0
  94. package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +169 -0
  95. package/dist/src/report/experiments/renderAgentSuccessRateText.js +139 -0
  96. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  97. package/dist/src/report/experiments/renderPluginExperimentReportText.js +5 -0
  98. package/docs/METRICS.md +70 -0
  99. package/package.json +1 -1
@@ -0,0 +1,441 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstat, readFile, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { resolveWithinRoot } from "../../../core/pathSafety.js";
5
+ import { applyPatchToSandbox, assessBaseline, runVerificationChecks, validateAgentSuccessTask } from "../../../evaluation/agentSuccess/index.js";
6
+ import { BenchmarkSandboxError, createBenchmarkSandbox, removeBenchmarkSandbox, snapshotProjectTree } from "../../../evaluation/benchmarkSandbox/index.js";
7
+ import { isSameOrInside, resolvePhysicalPath } from "../../../evaluation/benchmarkSandbox/pathPolicy.js";
8
+ import { captureChangeSet } from "../../../evaluation/changeSet/index.js";
9
+ import { isPathInside, sanitizePathSegment } from "../../outputPaths.js";
10
+ import { AGENT_SUCCESS_RATE_TREATMENT_IDS } from "./metadata.js";
11
+ export const AGENT_SUCCESS_RATE_MISSING_INPUT_MESSAGE = "Agent success rate requires an agentSuccessTasks input (an array of AgentSuccessTaskV1). No production task catalog is bundled in this version.";
12
+ export const AGENT_SUCCESS_RATE_SELF_ONLY_MESSAGE = "agent-success-rate supports only the self target; external-local targets are not supported.";
13
+ const MAX_BOUNDED_MESSAGE = 300;
14
+ const SAFE_PROJECT_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
15
+ export class AgentSuccessRateInputError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "AgentSuccessRateInputError";
19
+ }
20
+ }
21
+ export const defaultAgentSuccessRateDependencies = {
22
+ createSandbox: createBenchmarkSandbox,
23
+ removeSandbox: removeBenchmarkSandbox,
24
+ applyPatch: applyPatchToSandbox,
25
+ captureChanges: captureChangeSet,
26
+ runChecks: (options) => runVerificationChecks(options)
27
+ };
28
+ // ---------------------------------------------------------------------------------------------------------------
29
+ // Inputs
30
+ // ---------------------------------------------------------------------------------------------------------------
31
+ /**
32
+ * Validates every candidate with the Batch 1 validator before anything touches the filesystem. Fails closed: there is
33
+ * no default catalog, and no task is inferred from retrieval EvaluationCase objects.
34
+ */
35
+ export function readAgentSuccessTasksInput(inputs, options = {}) {
36
+ const requireFixture = options.requireFixture !== false;
37
+ const value = inputs?.agentSuccessTasks;
38
+ if (value === undefined)
39
+ throw new AgentSuccessRateInputError(AGENT_SUCCESS_RATE_MISSING_INPUT_MESSAGE);
40
+ if (!Array.isArray(value))
41
+ throw new AgentSuccessRateInputError("agentSuccessTasks input must be an array of AgentSuccessTaskV1.");
42
+ if (value.length === 0)
43
+ throw new AgentSuccessRateInputError("agentSuccessTasks input must not be empty.");
44
+ const tasks = [];
45
+ const seen = new Set();
46
+ value.forEach((candidate, index) => {
47
+ const result = validateAgentSuccessTask(candidate);
48
+ if (!result.ok) {
49
+ const detail = result.issues
50
+ .slice(0, 5)
51
+ .map((issue) => `${issue.path || "(root)"}: ${issue.code}`)
52
+ .join("; ");
53
+ throw new AgentSuccessRateInputError(`agentSuccessTasks[${index}] is not a valid AgentSuccessTaskV1: ${detail}`);
54
+ }
55
+ const task = result.task;
56
+ if (seen.has(task.id))
57
+ throw new AgentSuccessRateInputError(`Duplicate agent-success task id: ${task.id}`);
58
+ seen.add(task.id);
59
+ if (requireFixture && !task.deterministicFixture) {
60
+ throw new AgentSuccessRateInputError(`Task ${task.id} has no deterministicFixture; deterministic-fixture mode requires one.`);
61
+ }
62
+ tasks.push(task);
63
+ });
64
+ return tasks;
65
+ }
66
+ /** Keeps input order, applies case then project filters, and fails clearly for unknown IDs or an empty selection. */
67
+ export function selectAgentSuccessTasks(tasks, filters) {
68
+ const knownCases = new Set(tasks.map((task) => task.id));
69
+ const missingCases = (filters.caseIds ?? []).filter((caseId) => !knownCases.has(caseId));
70
+ if (missingCases.length > 0)
71
+ throw new AgentSuccessRateInputError(`Agent-success task not found: ${missingCases.join(", ")}`);
72
+ const knownProjects = new Set(tasks.map((task) => task.benchmarkProject));
73
+ const missingProjects = (filters.benchmarkProjects ?? []).filter((project) => !knownProjects.has(project));
74
+ if (missingProjects.length > 0)
75
+ throw new AgentSuccessRateInputError(`Benchmark project not found: ${missingProjects.join(", ")}`);
76
+ const selected = tasks
77
+ .filter((task) => !filters.caseIds?.length || filters.caseIds.includes(task.id))
78
+ .filter((task) => !filters.benchmarkProjects?.length || filters.benchmarkProjects.includes(task.benchmarkProject));
79
+ if (selected.length === 0)
80
+ throw new AgentSuccessRateInputError("No agent-success tasks matched the requested filters.");
81
+ return selected;
82
+ }
83
+ /**
84
+ * Resolves `<toolRoot>/benchmarks/projects/<benchmarkProject>` physically. A project ID that is not a plain path
85
+ * segment, a missing project, a link, or a project that resolves outside the controlled directory is rejected.
86
+ */
87
+ export async function resolveControlledBenchmarkProject(toolRoot, benchmarkProject) {
88
+ if (!SAFE_PROJECT_ID.test(benchmarkProject)) {
89
+ throw new AgentSuccessRateInputError(`Benchmark project id is not a safe path segment: ${boundMessage(benchmarkProject, [])}`);
90
+ }
91
+ const projectsRoot = path.join(path.resolve(toolRoot), "benchmarks", "projects");
92
+ const candidate = resolveWithinRoot(projectsRoot, benchmarkProject);
93
+ let stats;
94
+ try {
95
+ stats = await lstat(candidate);
96
+ }
97
+ catch {
98
+ throw new AgentSuccessRateInputError(`Benchmark project not found in the controlled benchmark directory: ${benchmarkProject}`);
99
+ }
100
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
101
+ throw new AgentSuccessRateInputError(`Benchmark project must be a real directory: ${benchmarkProject}`);
102
+ }
103
+ const physicalRoot = await realpath(projectsRoot);
104
+ const physicalProject = await realpath(candidate);
105
+ if (physicalProject === physicalRoot || !isPathInside(physicalRoot, physicalProject)) {
106
+ throw new AgentSuccessRateInputError(`Benchmark project escapes the controlled benchmark directory: ${benchmarkProject}`);
107
+ }
108
+ return physicalProject;
109
+ }
110
+ /** The runtime root lives under the experiment output root and must never overlap the controlled benchmarks. */
111
+ export async function resolveSandboxRuntimeRoot(outDir, toolRoot) {
112
+ const runtimeRoot = path.join(path.resolve(outDir), "sandboxes");
113
+ const physicalRuntime = await resolvePhysicalPath(runtimeRoot);
114
+ const physicalBenchmarks = await resolvePhysicalPath(path.join(path.resolve(toolRoot), "benchmarks"));
115
+ if (isSameOrInside(physicalBenchmarks, physicalRuntime) || isSameOrInside(physicalRuntime, physicalBenchmarks)) {
116
+ throw new AgentSuccessRateInputError("The experiment output root must not overlap the controlled benchmark directory.");
117
+ }
118
+ return runtimeRoot;
119
+ }
120
+ // ---------------------------------------------------------------------------------------------------------------
121
+ // Execution
122
+ // ---------------------------------------------------------------------------------------------------------------
123
+ /** Attempt 1 keeps its original identity; every repair attempt derives a distinct sandbox id so no sandbox is ever reused. */
124
+ export function deriveAgentSuccessSandboxId(runId, caseId, treatmentId, attemptNumber = 1) {
125
+ const attemptKey = attemptNumber === 1 ? "" : `\0attempt-${attemptNumber}`;
126
+ const digest = createHash("sha256").update(`${runId}\0${caseId}\0${treatmentId}${attemptKey}`).digest("hex").slice(0, 20);
127
+ return `asr-${digest}-${treatmentId === "raw-full-file" ? "raw" : "ctx"}${attemptNumber === 1 ? "" : `-a${attemptNumber}`}`;
128
+ }
129
+ export function agentSuccessPatchArtifactPath(benchmarkProject, caseId, treatmentId, kind, attemptNumber = 1) {
130
+ return `diffs/${sanitizePathSegment(benchmarkProject)}/${sanitizePathSegment(caseId)}/${sanitizePathSegment(treatmentId)}/attempt-${attemptNumber}-${kind}.patch`;
131
+ }
132
+ /** Bounds a message and replaces any known machine-local root so no absolute path is persisted. */
133
+ export function boundMessage(message, privateRoots) {
134
+ let text = message;
135
+ for (const root of [...privateRoots].filter(Boolean).sort((a, b) => b.length - a.length)) {
136
+ for (const variant of new Set([root, root.replace(/\\/g, "/")]))
137
+ text = text.split(variant).join("<path>");
138
+ }
139
+ text = text.replace(/\s+/g, " ").trim();
140
+ return text.length > MAX_BOUNDED_MESSAGE ? `${text.slice(0, MAX_BOUNDED_MESSAGE)}...` : text;
141
+ }
142
+ function summarizeVerification(phase, privateRoots) {
143
+ const summarize = (result, checkClass) => ({
144
+ checkId: result.checkId,
145
+ class: checkClass,
146
+ status: result.status,
147
+ exitCode: result.exitCode,
148
+ durationMs: result.durationMs,
149
+ failureReason: result.failureReason === null ? null : boundMessage(result.failureReason, privateRoots)
150
+ });
151
+ return {
152
+ phase: phase.phase,
153
+ taskResults: phase.taskResults.map((result) => summarize(result, "task")),
154
+ regressionResults: phase.regressionResults.map((result) => summarize(result, "regression"))
155
+ };
156
+ }
157
+ function summarizeChange(changes) {
158
+ return {
159
+ baselineCommit: changes.baselineCommit,
160
+ changedFiles: changes.changedFiles.map((file) => ({ ...file })),
161
+ addedCount: changes.addedCount,
162
+ modifiedCount: changes.modifiedCount,
163
+ deletedCount: changes.deletedCount,
164
+ changedCount: changes.changedCount,
165
+ totalAdditions: changes.totalAdditions,
166
+ totalDeletions: changes.totalDeletions
167
+ };
168
+ }
169
+ /** Total line count of baseline files that are text (contain no NUL byte). Null when it cannot be determined reliably. */
170
+ async function countBaselineTextLines(sandbox) {
171
+ try {
172
+ let lines = 0;
173
+ for (const entry of sandbox.baseline.manifest) {
174
+ const data = await readFile(path.join(sandbox.projectRoot, ...entry.path.split("/")));
175
+ if (data.includes(0))
176
+ continue;
177
+ for (const byte of data)
178
+ if (byte === 10)
179
+ lines += 1;
180
+ if (data.length > 0 && data[data.length - 1] !== 10)
181
+ lines += 1;
182
+ }
183
+ return lines;
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ /** Compares every protected path with the sandbox baseline manifest after all Lab-run work is finished. */
190
+ async function evaluateProtectedIntegrity(sandbox, task, changes) {
191
+ const protectedPaths = new Set(task.protectedFiles);
192
+ const mutated = new Set();
193
+ for (const file of changes?.changedFiles ?? [])
194
+ if (protectedPaths.has(file.relativePath))
195
+ mutated.add(file.relativePath);
196
+ try {
197
+ const working = new Map((await snapshotProjectTree(sandbox.projectRoot, { excludedNames: [".git"] })).map((entry) => [entry.path, entry.sha256]));
198
+ const baseline = new Map(sandbox.baseline.manifest.map((entry) => [entry.path, entry.sha256]));
199
+ for (const protectedPath of protectedPaths) {
200
+ if (working.get(protectedPath) !== baseline.get(protectedPath))
201
+ mutated.add(protectedPath);
202
+ }
203
+ }
204
+ catch {
205
+ return { status: mutated.size > 0 ? "mutated" : "unproven", mutatedPaths: [...mutated].sort() };
206
+ }
207
+ return { status: mutated.size > 0 ? "mutated" : "intact", mutatedPaths: [...mutated].sort() };
208
+ }
209
+ function emptyPatchEvidence() {
210
+ return { attempted: false, outcome: null, code: null, message: null, appliedFiles: [], rejections: [], attemptedProtectedPaths: [], proposedPatchBytes: 0 };
211
+ }
212
+ function recordPatchOutcome(evidence, result, privateRoots) {
213
+ evidence.outcome = result.outcome;
214
+ switch (result.outcome) {
215
+ case "parse-failure":
216
+ evidence.code = result.code;
217
+ break;
218
+ case "policy-rejection":
219
+ evidence.rejections = result.rejections.map((rejection) => ({ code: rejection.code, path: rejection.path ?? null }));
220
+ evidence.attemptedProtectedPaths = [
221
+ ...new Set(result.rejections.filter((rejection) => rejection.code === "PROTECTED_PATH" && rejection.path).map((rejection) => rejection.path))
222
+ ].sort();
223
+ break;
224
+ case "git-check-failure":
225
+ case "git-apply-failure":
226
+ evidence.message = boundMessage(result.message, privateRoots);
227
+ break;
228
+ case "success":
229
+ evidence.appliedFiles = result.files.map((file) => ({ path: file.path, status: file.status }));
230
+ break;
231
+ }
232
+ }
233
+ function isIndeterminate(result) {
234
+ return result.status === "timeout" || result.status === "error";
235
+ }
236
+ /**
237
+ * Runs one case/treatment: independent sandbox, baseline verification, the task's deterministic fixture patch through
238
+ * the Lab-owned pipeline, change capture, post-edit verification, protected-file integrity, and guarded cleanup.
239
+ * Cleanup is attempted after every successfully created sandbox.
240
+ */
241
+ export async function executeAgentSuccessTreatment(args) {
242
+ const deps = { ...defaultAgentSuccessRateDependencies, ...args.dependencies };
243
+ const { task, treatmentId } = args;
244
+ const privateRoots = [args.runtimeRoot, args.canonicalProjectRoot, ...args.privateRoots];
245
+ const proposalSource = args.proposalSource;
246
+ const fixture = proposalSource ? undefined : task.deterministicFixture;
247
+ const attemptNumber = args.attemptNumber ?? 1;
248
+ const sandboxId = deriveAgentSuccessSandboxId(args.runId, task.id, treatmentId, attemptNumber);
249
+ const started = Date.now();
250
+ const evidence = {
251
+ treatmentId,
252
+ status: "failed",
253
+ availability: "infrastructure-failure",
254
+ sandboxId,
255
+ sandboxBaseline: null,
256
+ baselineTextLineCount: null,
257
+ baselineVerification: null,
258
+ baselineAssessment: null,
259
+ patch: emptyPatchEvidence(),
260
+ change: null,
261
+ postEditVerification: null,
262
+ protectedIntegrity: { status: "unproven", mutatedPaths: [] },
263
+ timing: { agentDurationMs: null, baselineVerificationDurationMs: null, patchPipelineDurationMs: null, postEditVerificationDurationMs: null, evaluationDurationMs: null },
264
+ agentTokenUsage: null,
265
+ cleanup: { attempted: false, removed: false, reason: null },
266
+ proposedPatchPath: null,
267
+ appliedPatchPath: null,
268
+ errors: []
269
+ };
270
+ const patchFiles = [];
271
+ const contextFiles = [];
272
+ const addError = (code, message) => {
273
+ evidence.errors.push({ code, message: boundMessage(message, privateRoots) });
274
+ };
275
+ if (!proposalSource && !fixture) {
276
+ addError("FIXTURE_MISSING", `Task ${task.id} has no deterministicFixture.`);
277
+ return { evidence, patchFiles, contextFiles };
278
+ }
279
+ /** Records that no provider ran, so real-agent evidence is never absent for a treatment. */
280
+ const recordNotInvoked = async (reason) => {
281
+ if (!proposalSource || evidence.realAgent)
282
+ return;
283
+ try {
284
+ const skipped = await proposalSource({ treatmentId, invoke: false, notInvokedReason: reason });
285
+ evidence.realAgent = skipped.realAgent;
286
+ }
287
+ catch {
288
+ // The treatment already carries its own failure evidence.
289
+ }
290
+ };
291
+ let sandbox;
292
+ try {
293
+ sandbox = await deps.createSandbox({ canonicalProjectRoot: args.canonicalProjectRoot, runtimeRoot: args.runtimeRoot, sandboxId });
294
+ }
295
+ catch (error) {
296
+ addError(error instanceof BenchmarkSandboxError ? `SANDBOX_${error.code}` : "SANDBOX_CREATION_FAILED", error instanceof Error ? error.message : String(error));
297
+ evidence.timing.evaluationDurationMs = Date.now() - started;
298
+ await recordNotInvoked("sandbox-unavailable");
299
+ return { evidence, patchFiles, contextFiles };
300
+ }
301
+ let changes = null;
302
+ try {
303
+ evidence.sandboxBaseline = { commit: sandbox.baseline.commit, fileCount: sandbox.baseline.fileCount, digest: sandbox.baseline.digest };
304
+ evidence.baselineTextLineCount = await countBaselineTextLines(sandbox);
305
+ const baselineStarted = Date.now();
306
+ const baseline = await deps.runChecks({ sandbox, task, phase: "baseline" });
307
+ evidence.timing.baselineVerificationDurationMs = Date.now() - baselineStarted;
308
+ evidence.baselineVerification = summarizeVerification(baseline, privateRoots);
309
+ const assessment = assessBaseline(baseline);
310
+ evidence.baselineAssessment = { evaluable: assessment.evaluable, reasons: [...assessment.reasons] };
311
+ if (!assessment.evaluable) {
312
+ evidence.status = "skipped";
313
+ evidence.availability = "baseline-invalid";
314
+ evidence.protectedIntegrity = await evaluateProtectedIntegrity(sandbox, task, null);
315
+ await recordNotInvoked("baseline-not-evaluable");
316
+ }
317
+ else {
318
+ // Exactly one proposal per treatment: the deterministic fixture, or one provider attempt (never both).
319
+ let proposalText = null;
320
+ let cwdCleanupFailed = false;
321
+ if (proposalSource) {
322
+ const proposal = await proposalSource({ treatmentId, invoke: true });
323
+ evidence.realAgent = proposal.realAgent;
324
+ evidence.timing.agentDurationMs = proposal.agentDurationMs;
325
+ evidence.agentTokenUsage = proposal.agentTokenUsage;
326
+ evidence.errors.push(...proposal.errors);
327
+ if (proposal.contextFile)
328
+ contextFiles.push(proposal.contextFile);
329
+ if (!proposal.realAgent.cwdCleanup.removed && proposal.realAgent.cwdCleanup.attempted) {
330
+ cwdCleanupFailed = true;
331
+ addError("AGENT_CWD_CLEANUP_FAILED", proposal.realAgent.cwdCleanup.reason ?? "provider working directory cleanup failed.");
332
+ }
333
+ proposalText = proposal.kind === "proposal" ? proposal.text : null;
334
+ }
335
+ else {
336
+ proposalText = fixture.patch;
337
+ }
338
+ if (proposalText === null) {
339
+ // No usable provider answer: nothing is applied, scored or fabricated.
340
+ evidence.protectedIntegrity = await evaluateProtectedIntegrity(sandbox, task, null);
341
+ if (evidence.protectedIntegrity.status === "unproven")
342
+ addError("PROTECTED_INTEGRITY_UNPROVEN", "Protected-file integrity could not be proven.");
343
+ evidence.status = "partial";
344
+ evidence.availability = "incomplete";
345
+ return { evidence, patchFiles, contextFiles };
346
+ }
347
+ const proposedPath = agentSuccessPatchArtifactPath(task.benchmarkProject, task.id, treatmentId, "proposed", attemptNumber);
348
+ patchFiles.push({ relativePath: proposedPath, content: proposalText });
349
+ evidence.proposedPatchPath = proposedPath;
350
+ evidence.patch.attempted = true;
351
+ evidence.patch.proposedPatchBytes = Buffer.byteLength(proposalText, "utf8");
352
+ const patchStarted = Date.now();
353
+ const applied = await deps.applyPatch({ sandbox, rawProposal: proposalText, protectedFiles: task.protectedFiles });
354
+ recordPatchOutcome(evidence.patch, applied, privateRoots);
355
+ let captureFailed = false;
356
+ if (applied.outcome === "success") {
357
+ try {
358
+ changes = await deps.captureChanges({ sandbox });
359
+ evidence.change = summarizeChange(changes);
360
+ const appliedPath = agentSuccessPatchArtifactPath(task.benchmarkProject, task.id, treatmentId, "applied", attemptNumber);
361
+ patchFiles.push({ relativePath: appliedPath, content: changes.diff });
362
+ evidence.appliedPatchPath = appliedPath;
363
+ }
364
+ catch (error) {
365
+ captureFailed = true;
366
+ addError("CHANGE_CAPTURE_FAILED", error instanceof Error ? error.message : String(error));
367
+ }
368
+ }
369
+ evidence.timing.patchPipelineDurationMs = Date.now() - patchStarted;
370
+ let postIndeterminate = false;
371
+ if (applied.outcome === "success") {
372
+ const postStarted = Date.now();
373
+ const post = await deps.runChecks({ sandbox, task, phase: "post-edit" });
374
+ evidence.timing.postEditVerificationDurationMs = Date.now() - postStarted;
375
+ evidence.postEditVerification = summarizeVerification(post, privateRoots);
376
+ postIndeterminate = [...post.taskResults, ...post.regressionResults].some(isIndeterminate);
377
+ if (postIndeterminate)
378
+ addError("POST_EDIT_CHECK_INDETERMINATE", "At least one post-edit check timed out or errored.");
379
+ }
380
+ evidence.protectedIntegrity = await evaluateProtectedIntegrity(sandbox, task, changes);
381
+ if (evidence.protectedIntegrity.status === "unproven")
382
+ addError("PROTECTED_INTEGRITY_UNPROVEN", "Protected-file integrity could not be proven.");
383
+ const complete = !captureFailed && !postIndeterminate && !cwdCleanupFailed && evidence.protectedIntegrity.status !== "unproven";
384
+ evidence.status = complete ? "completed" : "partial";
385
+ evidence.availability = complete ? "complete" : "incomplete";
386
+ }
387
+ }
388
+ catch (error) {
389
+ evidence.status = "failed";
390
+ evidence.availability = "infrastructure-failure";
391
+ addError("EXECUTION_FAILED", error instanceof Error ? error.message : String(error));
392
+ await recordNotInvoked("execution-failed");
393
+ }
394
+ finally {
395
+ evidence.timing.evaluationDurationMs = Date.now() - started;
396
+ evidence.cleanup.attempted = true;
397
+ try {
398
+ const removal = await deps.removeSandbox({ runtimeRoot: args.runtimeRoot, sandboxId });
399
+ evidence.cleanup.removed = removal.removed;
400
+ if (!removal.removed)
401
+ evidence.cleanup.reason = boundMessage(removal.reason, privateRoots);
402
+ }
403
+ catch (error) {
404
+ evidence.cleanup.removed = false;
405
+ evidence.cleanup.reason = boundMessage(error instanceof Error ? error.message : String(error), privateRoots);
406
+ }
407
+ if (!evidence.cleanup.removed) {
408
+ addError("CLEANUP_FAILED", evidence.cleanup.reason ?? "sandbox cleanup failed.");
409
+ if (evidence.status === "completed") {
410
+ evidence.status = "partial";
411
+ evidence.availability = "incomplete";
412
+ }
413
+ }
414
+ }
415
+ return { evidence, patchFiles, contextFiles };
416
+ }
417
+ /** Executes both mandatory treatments, in fixed order, each in its own sandbox. */
418
+ export async function executeAgentSuccessCase(args) {
419
+ const treatments = [];
420
+ const patchFiles = [];
421
+ const contextFiles = [];
422
+ for (const treatmentId of AGENT_SUCCESS_RATE_TREATMENT_IDS) {
423
+ const result = await executeAgentSuccessTreatment({ ...args, treatmentId });
424
+ treatments.push(result.evidence);
425
+ patchFiles.push(...result.patchFiles);
426
+ contextFiles.push(...result.contextFiles);
427
+ }
428
+ return {
429
+ evidence: {
430
+ caseId: args.task.id,
431
+ caseName: args.task.title,
432
+ benchmarkProject: args.task.benchmarkProject,
433
+ taskLocality: args.task.taskLocality,
434
+ // Real-agent mode never reads the fixture, so it has no fixture identity.
435
+ fixtureId: args.proposalSource ? "" : (args.task.deterministicFixture?.id ?? ""),
436
+ treatments
437
+ },
438
+ patchFiles,
439
+ contextFiles
440
+ };
441
+ }
@@ -0,0 +1,126 @@
1
+ import { AGENT_SUCCESS_MAX_TOTAL_ATTEMPTS } from "./executionTypes.js";
2
+ import { AGENT_SUCCESS_RATE_EXECUTION_MODE, AGENT_SUCCESS_RATE_PLUGIN_ID, AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE, AGENT_SUCCESS_RATE_TREATMENT_IDS } from "./metadata.js";
3
+ export const AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE = "agent-success-rate-execution.json";
4
+ export const AGENT_SUCCESS_RATE_EXECUTION_SCHEMA_VERSION = "my-dev-kit-lab-agent-success-rate-execution-v1";
5
+ export function buildAgentSuccessRateExecutionArtifact(args) {
6
+ return {
7
+ schemaVersion: AGENT_SUCCESS_RATE_EXECUTION_SCHEMA_VERSION,
8
+ runId: args.runId,
9
+ pluginId: args.pluginId,
10
+ pluginSchemaVersion: args.pluginSchemaVersion,
11
+ startedAt: args.startedAt,
12
+ completedAt: args.completedAt,
13
+ executionMode: args.realAgent ? AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE : AGENT_SUCCESS_RATE_EXECUTION_MODE,
14
+ contextEffectEvaluated: args.realAgent ? args.realAgent.contextEffectEvaluated : false,
15
+ treatmentOrder: [...AGENT_SUCCESS_RATE_TREATMENT_IDS],
16
+ cases: structuredClone(args.cases),
17
+ ...(args.realAgent
18
+ ? {
19
+ realAgent: {
20
+ providerId: args.realAgent.providerId,
21
+ timeoutMs: args.realAgent.timeoutMs,
22
+ attemptsPerTreatment: args.realAgent.attemptsPerTreatment,
23
+ repairAttempts: args.realAgent.repairAttempts,
24
+ promptTransport: "stdin"
25
+ }
26
+ }
27
+ : {})
28
+ };
29
+ }
30
+ /** Structural validation of an in-memory execution artifact. Returns problems; empty means valid. */
31
+ export function validateAgentSuccessRateExecutionArtifact(artifact) {
32
+ const problems = [];
33
+ if (artifact.schemaVersion !== AGENT_SUCCESS_RATE_EXECUTION_SCHEMA_VERSION)
34
+ problems.push("execution artifact schemaVersion is not recognized.");
35
+ if (artifact.pluginId !== AGENT_SUCCESS_RATE_PLUGIN_ID)
36
+ problems.push("execution artifact pluginId does not match.");
37
+ const real = artifact.executionMode === AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE;
38
+ if (artifact.executionMode !== AGENT_SUCCESS_RATE_EXECUTION_MODE && !real)
39
+ problems.push("execution artifact executionMode is not recognized.");
40
+ if (real) {
41
+ if (!artifact.realAgent)
42
+ problems.push("real-agent execution artifact is missing the realAgent run settings.");
43
+ if (typeof artifact.contextEffectEvaluated !== "boolean")
44
+ problems.push("execution artifact contextEffectEvaluated must be a boolean.");
45
+ }
46
+ else {
47
+ if (artifact.contextEffectEvaluated !== false)
48
+ problems.push("execution artifact must state contextEffectEvaluated=false.");
49
+ if (artifact.realAgent !== undefined)
50
+ problems.push("deterministic-fixture execution artifact must not carry realAgent settings.");
51
+ }
52
+ if (artifact.treatmentOrder.join(",") !== AGENT_SUCCESS_RATE_TREATMENT_IDS.join(","))
53
+ problems.push("execution artifact treatmentOrder is not the fixed order.");
54
+ const seen = new Set();
55
+ for (const entry of artifact.cases) {
56
+ if (seen.has(entry.caseId))
57
+ problems.push(`duplicate execution case ${entry.caseId}.`);
58
+ seen.add(entry.caseId);
59
+ if (entry.treatments.map((t) => t.treatmentId).join(",") !== AGENT_SUCCESS_RATE_TREATMENT_IDS.join(",")) {
60
+ problems.push(`case ${entry.caseId} does not hold both treatments in fixed order.`);
61
+ }
62
+ const sandboxIds = entry.treatments.map((t) => t.sandboxId);
63
+ if (new Set(sandboxIds).size !== sandboxIds.length)
64
+ problems.push(`case ${entry.caseId} treatments share a sandbox id.`);
65
+ for (const treatment of entry.treatments) {
66
+ if (!real) {
67
+ if (treatment.timing.agentDurationMs !== null || treatment.agentTokenUsage !== null || treatment.realAgent !== undefined) {
68
+ problems.push(`case ${entry.caseId} claims agent evidence in deterministic mode.`);
69
+ }
70
+ continue;
71
+ }
72
+ const evidence = treatment.realAgent;
73
+ if (!evidence) {
74
+ problems.push(`case ${entry.caseId}/${treatment.treatmentId} is missing real-agent evidence.`);
75
+ continue;
76
+ }
77
+ problems.push(...validateAttempts(entry.caseId, treatment, artifact.realAgent));
78
+ if (artifact.realAgent && evidence.providerId !== artifact.realAgent.providerId)
79
+ problems.push(`case ${entry.caseId}/${treatment.treatmentId} provider does not match the run provider.`);
80
+ if (evidence.context.contextMode !== treatment.treatmentId)
81
+ problems.push(`case ${entry.caseId}/${treatment.treatmentId} context mode does not match the treatment.`);
82
+ if (evidence.providerInvoked !== (evidence.agentArtifactDirectory !== null))
83
+ problems.push(`case ${entry.caseId}/${treatment.treatmentId} agent artifact directory disagrees with provider invocation.`);
84
+ if (!evidence.providerInvoked && (treatment.timing.agentDurationMs !== null && evidence.providerStatus === "not-invoked"))
85
+ problems.push(`case ${entry.caseId}/${treatment.treatmentId} has an agent duration without a provider attempt.`);
86
+ if (evidence.finalAnswerAvailable !== treatment.patch.attempted)
87
+ problems.push(`case ${entry.caseId}/${treatment.treatmentId} patch attempt disagrees with the provider final answer.`);
88
+ if (evidence.context.availability !== "unavailable" && evidence.providerInvoked && evidence.context.contextArtifactPath === null)
89
+ problems.push(`case ${entry.caseId}/${treatment.treatmentId} is missing its context artifact reference.`);
90
+ }
91
+ }
92
+ return problems;
93
+ }
94
+ /** Attempts are ordered, contiguous and one-based, bounded by the configured allowance, and never share a sandbox. */
95
+ function validateAttempts(caseId, treatment, run) {
96
+ const problems = [];
97
+ const label = `case ${caseId}/${treatment.treatmentId}`;
98
+ const attempts = treatment.attempts;
99
+ // Evidence recorded without attempt history is a single legacy attempt and is validated by the checks above.
100
+ if (!attempts || attempts.length === 0)
101
+ return problems;
102
+ const allowed = Math.min(1 + (run?.repairAttempts ?? 0), AGENT_SUCCESS_MAX_TOTAL_ATTEMPTS);
103
+ if (attempts.length > allowed)
104
+ problems.push(`${label} executed ${attempts.length} attempts but at most ${allowed} are allowed.`);
105
+ attempts.forEach((attempt, index) => {
106
+ if (attempt.attemptNumber !== index + 1)
107
+ problems.push(`${label} attempt numbering is not contiguous from 1.`);
108
+ if (attempt.evidence.attempts !== undefined)
109
+ problems.push(`${label} attempt ${attempt.attemptNumber} nests further attempts.`);
110
+ if (attempt.evidence.realAgent && attempt.evidence.realAgent.attempt !== attempt.attemptNumber)
111
+ problems.push(`${label} attempt ${attempt.attemptNumber} disagrees with its provider evidence.`);
112
+ if (run && attempt.providerId !== run.providerId)
113
+ problems.push(`${label} attempt ${attempt.attemptNumber} provider does not match the run provider.`);
114
+ if (index > 0) {
115
+ const previous = attempts[index - 1];
116
+ if (!previous.repairEligible || previous.taskSuccess !== false)
117
+ problems.push(`${label} attempt ${attempt.attemptNumber} followed an attempt that was not repair-eligible.`);
118
+ }
119
+ });
120
+ const sandboxIds = attempts.map((attempt) => attempt.evidence.sandboxId);
121
+ if (new Set(sandboxIds).size !== sandboxIds.length)
122
+ problems.push(`${label} attempts share a sandbox id.`);
123
+ if (attempts[attempts.length - 1].evidence.sandboxId !== treatment.sandboxId)
124
+ problems.push(`${label} top-level evidence is not the final attempt.`);
125
+ return problems;
126
+ }
@@ -0,0 +1,2 @@
1
+ export const AGENT_SUCCESS_MAX_REPAIR_ATTEMPTS = 2;
2
+ export const AGENT_SUCCESS_MAX_TOTAL_ATTEMPTS = 3;
@@ -0,0 +1,20 @@
1
+ export * from "./analysis.js";
2
+ export * from "./analysisArtifact.js";
3
+ export * from "./analysisTypes.js";
4
+ export * from "./config.js";
5
+ export * from "./execution.js";
6
+ export * from "./executionArtifact.js";
7
+ export * from "./executionTypes.js";
8
+ export * from "./metadata.js";
9
+ export * from "./metrics.js";
10
+ export * from "./plugin.js";
11
+ export * from "./types.js";
12
+ export * from "./agentTaskProjection.js";
13
+ export * from "./contextGeneration.js";
14
+ export * from "./realAgentExecution.js";
15
+ export * from "./realAgentPrompt.js";
16
+ export * from "./attemptEvidence.js";
17
+ export * from "./repairPolicy.js";
18
+ export * from "./repairFeedback.js";
19
+ export * from "./repairAnalysis.js";
20
+ export * from "./repairExecution.js";
@@ -0,0 +1,28 @@
1
+ export const AGENT_SUCCESS_RATE_PLUGIN_ID = "agent-success-rate";
2
+ export const AGENT_SUCCESS_RATE_TREATMENT_IDS = ["raw-full-file", "context-pack"];
3
+ /** Deterministic-fixture mode executes fixture patches only; no coding agent runs. It is the default mode. */
4
+ export const AGENT_SUCCESS_RATE_EXECUTION_MODE = "deterministic-fixture";
5
+ /** Real-agent mode runs an initial provider attempt (plus bounded repairs, when configured) per case/treatment and evaluates each patch the provider generates. */
6
+ export const AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE = "real-agent";
7
+ export const agentSuccessRateMetadata = {
8
+ id: AGENT_SUCCESS_RATE_PLUGIN_ID,
9
+ name: "Agent Success Rate",
10
+ description: "Evaluate implementation tasks on disposable benchmark copies with trusted verification checks. Deterministic-fixture mode validates the evaluation pipeline and does not measure agent or context performance.",
11
+ schemaVersion: "1.0.0",
12
+ status: "experimental",
13
+ supportedTargets: ["self"],
14
+ supportedOutputs: ["json", "html", "text", "artifact"]
15
+ };
16
+ /** Exactly two mandatory treatments, in fixed order. There is no treatment selector. */
17
+ export const AGENT_SUCCESS_RATE_VARIANTS = [
18
+ {
19
+ id: "raw-full-file",
20
+ name: "Raw full-file context",
21
+ description: "Treatment identity for raw full-file context. In deterministic-fixture mode no context is supplied to any agent."
22
+ },
23
+ {
24
+ id: "context-pack",
25
+ name: "Context pack",
26
+ description: "Treatment identity for a bounded context pack. In deterministic-fixture mode no context is supplied to any agent."
27
+ }
28
+ ];