@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.
- package/CHANGELOG.md +10 -0
- package/README.md +18 -3
- package/benchmarks/contracts/agent-success-rate-project-profiles.json +33 -0
- package/benchmarks/contracts/agent-success-rate-tasks.json +671 -0
- package/benchmarks/projects/agent-success-inventory-node/.gitattributes +2 -0
- package/benchmarks/projects/agent-success-inventory-node/README.md +74 -0
- package/benchmarks/projects/agent-success-inventory-node/package.json +14 -0
- package/benchmarks/projects/agent-success-inventory-node/src/fulfillmentReport.js +38 -0
- package/benchmarks/projects/agent-success-inventory-node/src/index.js +26 -0
- package/benchmarks/projects/agent-success-inventory-node/src/inventoryStore.js +114 -0
- package/benchmarks/projects/agent-success-inventory-node/src/quantity.js +19 -0
- package/benchmarks/projects/agent-success-inventory-node/src/reservationService.js +45 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/quantity-edge.check.mjs +51 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/quantity-primary.check.mjs +46 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/regression.check.mjs +115 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/report-edge.check.mjs +109 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/report-primary.check.mjs +47 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/reservation-edge.check.mjs +63 -0
- package/benchmarks/projects/agent-success-inventory-node/tests/reservation-primary.check.mjs +44 -0
- package/benchmarks/projects/agent-success-task-board-node/.gitattributes +2 -0
- package/benchmarks/projects/agent-success-task-board-node/README.md +77 -0
- package/benchmarks/projects/agent-success-task-board-node/package.json +14 -0
- package/benchmarks/projects/agent-success-task-board-node/src/index.js +24 -0
- package/benchmarks/projects/agent-success-task-board-node/src/projectSummary.js +15 -0
- package/benchmarks/projects/agent-success-task-board-node/src/taskService.js +58 -0
- package/benchmarks/projects/agent-success-task-board-node/src/taskStore.js +58 -0
- package/benchmarks/projects/agent-success-task-board-node/src/validation.js +31 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/import-edge.check.mjs +65 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/import-primary.check.mjs +31 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/regression.check.mjs +107 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/summary-edge.check.mjs +67 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/summary-primary.check.mjs +54 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/title-edge.check.mjs +39 -0
- package/benchmarks/projects/agent-success-task-board-node/tests/title-primary.check.mjs +29 -0
- package/dist/src/cli/help.js +38 -1
- package/dist/src/commands/runExperimentDescribeCommand.js +52 -1
- package/dist/src/commands/runExperimentRunCommand.js +118 -2
- package/dist/src/core/processTree.js +55 -0
- package/dist/src/core/runMeasuredCommand.js +104 -22
- package/dist/src/evaluation/agentSuccess/applyPatchToSandbox.js +37 -0
- package/dist/src/evaluation/agentSuccess/assessBaseline.js +23 -0
- package/dist/src/evaluation/agentSuccess/corpusTypes.js +74 -0
- package/dist/src/evaluation/agentSuccess/extractPatchCandidate.js +46 -0
- package/dist/src/evaluation/agentSuccess/index.js +14 -0
- package/dist/src/evaluation/agentSuccess/parseUnifiedDiff.js +210 -0
- package/dist/src/evaluation/agentSuccess/patchTypes.js +5 -0
- package/dist/src/evaluation/agentSuccess/readAgentSuccessCorpus.js +183 -0
- package/dist/src/evaluation/agentSuccess/runVerificationCheck.js +100 -0
- package/dist/src/evaluation/agentSuccess/taskPaths.js +34 -0
- package/dist/src/evaluation/agentSuccess/taskTypes.js +14 -0
- package/dist/src/evaluation/agentSuccess/validateAgentSuccessCorpus.js +307 -0
- package/dist/src/evaluation/agentSuccess/validateAgentSuccessTask.js +297 -0
- package/dist/src/evaluation/agentSuccess/validatePatchPolicy.js +66 -0
- package/dist/src/evaluation/agentSuccess/verificationTypes.js +1 -0
- package/dist/src/evaluation/benchmarkSandbox/createBenchmarkSandbox.js +127 -0
- package/dist/src/evaluation/benchmarkSandbox/errors.js +11 -0
- package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +60 -0
- package/dist/src/evaluation/benchmarkSandbox/index.js +8 -0
- package/dist/src/evaluation/benchmarkSandbox/minimalHostEnv.js +37 -0
- package/dist/src/evaluation/benchmarkSandbox/pathPolicy.js +49 -0
- package/dist/src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.js +74 -0
- package/dist/src/evaluation/benchmarkSandbox/treeSnapshot.js +57 -0
- package/dist/src/evaluation/benchmarkSandbox/types.js +3 -0
- package/dist/src/evaluation/changeSet/captureChangeSet.js +98 -0
- package/dist/src/evaluation/changeSet/index.js +2 -0
- package/dist/src/evaluation/changeSet/types.js +9 -0
- package/dist/src/experiments/defaultRegistry.js +2 -0
- package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
- package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
- package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
- package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
- package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
- package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
- package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
- package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
- package/dist/src/experiments/plugins/agentSuccessRate/index.js +20 -0
- package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
- package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
- package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +507 -0
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +250 -0
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
- package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
- package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
- package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
- package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
- package/dist/src/report/experiments/buildAgentSuccessRateReport.js +255 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +27 -2
- package/dist/src/report/experiments/index.js +4 -0
- package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +169 -0
- package/dist/src/report/experiments/renderAgentSuccessRateText.js +139 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +5 -0
- package/docs/METRICS.md +70 -0
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const PREFIX = "agent-success-rate";
|
|
2
|
+
/** Typed metric constructors. An absent measurement is never represented as zero. */
|
|
3
|
+
export function availableMetric(id, value, unit) {
|
|
4
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
5
|
+
return unavailableMetric(id, unit, "the computed value was not finite.");
|
|
6
|
+
}
|
|
7
|
+
return { id, availability: "available", value, unit, reason: null };
|
|
8
|
+
}
|
|
9
|
+
export function unavailableMetric(id, unit, reason) {
|
|
10
|
+
return { id, availability: "unavailable", value: null, unit, reason: reason.trim() || "evidence is unavailable." };
|
|
11
|
+
}
|
|
12
|
+
export function notApplicableMetric(id, unit, reason) {
|
|
13
|
+
return { id, availability: "not-applicable", value: null, unit, reason: reason.trim() || "not applicable." };
|
|
14
|
+
}
|
|
15
|
+
export function triMetric(id, value, unknownReason) {
|
|
16
|
+
return value === "unknown" ? unavailableMetric(id, "boolean", unknownReason) : availableMetric(id, value, "boolean");
|
|
17
|
+
}
|
|
18
|
+
/** ratio = numerator / denominator, or not-applicable when the denominator is zero. */
|
|
19
|
+
export function ratioMetric(id, numerator, denominator, zeroReason) {
|
|
20
|
+
return denominator === 0 ? notApplicableMetric(id, "ratio", zeroReason) : availableMetric(id, numerator / denominator, "ratio");
|
|
21
|
+
}
|
|
22
|
+
/** Structural check of the metric availability contract. Returns problems; empty means valid. */
|
|
23
|
+
export function validateAgentSuccessMetric(metric) {
|
|
24
|
+
const problems = [];
|
|
25
|
+
if (metric.availability === "available") {
|
|
26
|
+
if (metric.value === null)
|
|
27
|
+
problems.push(`${metric.id}: available metric has a null value.`);
|
|
28
|
+
if (typeof metric.value === "number" && !Number.isFinite(metric.value))
|
|
29
|
+
problems.push(`${metric.id}: available metric is not finite.`);
|
|
30
|
+
if (metric.reason !== null)
|
|
31
|
+
problems.push(`${metric.id}: available metric must not carry a reason.`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (metric.value !== null)
|
|
35
|
+
problems.push(`${metric.id}: ${metric.availability} metric must have a null value.`);
|
|
36
|
+
if (typeof metric.reason !== "string" || metric.reason.trim() === "")
|
|
37
|
+
problems.push(`${metric.id}: ${metric.availability} metric needs a reason.`);
|
|
38
|
+
}
|
|
39
|
+
return problems;
|
|
40
|
+
}
|
|
41
|
+
/** Flattening to the generic ExperimentMetric happens only here, for generic run/outcome compatibility. */
|
|
42
|
+
export function toExperimentMetric(metric, scope) {
|
|
43
|
+
return {
|
|
44
|
+
id: `${PREFIX}.${metric.id}`,
|
|
45
|
+
name: scope.name ?? metric.id,
|
|
46
|
+
value: metric.value,
|
|
47
|
+
unit: metric.unit,
|
|
48
|
+
description: metric.availability === "available" ? "available" : `${metric.availability}: ${metric.reason ?? ""}`,
|
|
49
|
+
...(scope.variantId === undefined ? {} : { variantId: scope.variantId }),
|
|
50
|
+
...(scope.caseId === undefined ? {} : { caseId: scope.caseId })
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function toAgentSuccessOutcomeMetrics(analysis) {
|
|
54
|
+
const scope = { caseId: analysis.caseId, variantId: analysis.treatmentId };
|
|
55
|
+
const metrics = Object.keys(analysis.metrics).map((id) => toExperimentMetric(analysis.metrics[id], scope));
|
|
56
|
+
const repair = analysis.repair;
|
|
57
|
+
if (repair) {
|
|
58
|
+
// Initial-attempt and final outcomes are separate measurements; attempt counts are plain counts.
|
|
59
|
+
metrics.push(toExperimentMetric(repair.initialAttemptTaskSuccess, scope), toExperimentMetric(repair.finalTaskSuccess, scope), toExperimentMetric(repair.repairSucceeded, scope), toExperimentMetric(availableMetric("attemptCount", repair.attemptCount, "count"), scope), toExperimentMetric(availableMetric("repairAttemptCount", repair.repairAttemptCount, "count"), scope), toExperimentMetric(repair.totalProviderDurationMs, scope), toExperimentMetric(repair.totalProviderTokens, scope), toExperimentMetric(repair.totalEvaluationDurationMs, scope));
|
|
60
|
+
}
|
|
61
|
+
return metrics;
|
|
62
|
+
}
|
|
63
|
+
export function toAgentSuccessRunMetrics(analysis) {
|
|
64
|
+
const metrics = [];
|
|
65
|
+
for (const aggregate of analysis.aggregates) {
|
|
66
|
+
const scope = { variantId: aggregate.treatmentId };
|
|
67
|
+
metrics.push(toExperimentMetric(availableMetric("evaluableCaseCount", aggregate.evaluableCaseCount, "count"), scope), toExperimentMetric(availableMetric("successfulCaseCount", aggregate.successfulCaseCount, "count"), scope), toExperimentMetric(aggregate.taskSuccessRate, scope), toExperimentMetric(aggregate.initialAttemptSuccessRate, scope));
|
|
68
|
+
for (const mean of Object.values(aggregate.means))
|
|
69
|
+
metrics.push(toExperimentMetric(mean.metric, scope));
|
|
70
|
+
const repair = aggregate.repair;
|
|
71
|
+
if (repair) {
|
|
72
|
+
metrics.push(toExperimentMetric(availableMetric("initialAttemptSuccessfulCount", repair.initialAttemptSuccessfulCount, "count"), scope), toExperimentMetric(repair.finalTaskSuccessRate, scope), toExperimentMetric(availableMetric("finalSuccessfulCount", repair.finalSuccessfulCount, "count"), scope), toExperimentMetric(availableMetric("repairAttemptedCaseCount", repair.repairAttemptedCaseCount, "count"), scope), toExperimentMetric(availableMetric("repairedCaseCount", repair.repairedCaseCount, "count"), scope), toExperimentMetric(repair.repairSuccessRate, scope), toExperimentMetric(repair.meanAttemptsPerEvaluableCase, scope));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return metrics;
|
|
76
|
+
}
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { access, mkdir, readFile, rm, rmdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { runAgentPrompt } from "../../../agents/index.js";
|
|
5
|
+
import { runMeasuredCommand } from "../../../core/runMeasuredCommand.js";
|
|
6
|
+
import { resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
7
|
+
import { buildMyDevKitIndex, probeMyDevKitVersion } from "../../../evaluation/runMyDevKitRetrieval.js";
|
|
8
|
+
import { summarizeExperimentRun } from "../../results.js";
|
|
9
|
+
import { analyzeAgentSuccessRate } from "./analysis.js";
|
|
10
|
+
import { projectAgentFacingTask } from "./agentTaskProjection.js";
|
|
11
|
+
import { AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE, buildAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateArtifactFamily } from "./analysisArtifact.js";
|
|
12
|
+
import { agentSuccessRateConfigDefinition, defaultAgentSuccessRateConfig, validateAgentSuccessRateConfig } from "./config.js";
|
|
13
|
+
import { AgentSuccessPackContextBuilder, DEFAULT_AGENT_SUCCESS_KIT_COMMAND, buildContextTaskInput, buildRawFullFileContext, collectForbiddenAgentValues } from "./contextGeneration.js";
|
|
14
|
+
import { AGENT_SUCCESS_RATE_SELF_ONLY_MESSAGE, AgentSuccessRateInputError, boundMessage, executeAgentSuccessCase, readAgentSuccessTasksInput, resolveControlledBenchmarkProject, resolveSandboxRuntimeRoot, selectAgentSuccessTasks } from "./execution.js";
|
|
15
|
+
import { AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE, buildAgentSuccessRateExecutionArtifact, validateAgentSuccessRateExecutionArtifact } from "./executionArtifact.js";
|
|
16
|
+
import { attemptEvidenceOf } from "./attemptEvidence.js";
|
|
17
|
+
import { executeAgentSuccessRepairCase } from "./repairExecution.js";
|
|
18
|
+
import { AGENT_SUCCESS_RATE_EXECUTION_MODE, AGENT_SUCCESS_RATE_PLUGIN_ID, AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE, AGENT_SUCCESS_RATE_VARIANTS, agentSuccessRateMetadata } from "./metadata.js";
|
|
19
|
+
import { toAgentSuccessOutcomeMetrics, toAgentSuccessRunMetrics } from "./metrics.js";
|
|
20
|
+
import { AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS, createRealAgentProposalSource } from "./realAgentExecution.js";
|
|
21
|
+
export const AGENT_SUCCESS_RATE_PERSISTENCE_FAILURE_MESSAGE = "Agent success rate artifact persistence failed.";
|
|
22
|
+
export const AGENT_SUCCESS_RATE_ARTIFACT_STATE_MESSAGE = "Agent success rate artifact state was inconsistent; nothing was written.";
|
|
23
|
+
const defaultArtifactIo = {
|
|
24
|
+
ensureDirectory: async (directory) => {
|
|
25
|
+
await mkdir(directory, { recursive: true });
|
|
26
|
+
},
|
|
27
|
+
exists: async (filePath) => {
|
|
28
|
+
try {
|
|
29
|
+
await access(filePath);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
writeFile: (filePath, content) => writeFile(filePath, content, "utf8"),
|
|
37
|
+
removeFile: (filePath) => rm(filePath, { force: true })
|
|
38
|
+
};
|
|
39
|
+
export const agentSuccessRatePlugin = {
|
|
40
|
+
metadata: agentSuccessRateMetadata,
|
|
41
|
+
defaultConfig: defaultAgentSuccessRateConfig,
|
|
42
|
+
configDefinition: agentSuccessRateConfigDefinition,
|
|
43
|
+
supportedVariants: AGENT_SUCCESS_RATE_VARIANTS.map((variant) => variant.id),
|
|
44
|
+
validateConfig: validateAgentSuccessRateConfig,
|
|
45
|
+
async run(context) {
|
|
46
|
+
const startedAt = context.startedAt.toISOString();
|
|
47
|
+
// The runner does not enforce supportedTargets; refuse before any filesystem mutation.
|
|
48
|
+
if (context.target.kind !== "self" || context.target.isSelf !== true) {
|
|
49
|
+
throw new AgentSuccessRateInputError(AGENT_SUCCESS_RATE_SELF_ONLY_MESSAGE);
|
|
50
|
+
}
|
|
51
|
+
const inputs = context.inputs;
|
|
52
|
+
// Fail closed on inputs and project resolution before any sandbox exists.
|
|
53
|
+
const real = readRealAgentConfig(context.config);
|
|
54
|
+
const loadedTasks = selectAgentSuccessTasks(readAgentSuccessTasksInput(inputs, { requireFixture: real === null }), context.config);
|
|
55
|
+
// Real-agent mode seals each task: the fixture is captured only as a leak-guard value and then removed, so nothing
|
|
56
|
+
// downstream can read the reference patch.
|
|
57
|
+
const forbiddenByTask = new Map();
|
|
58
|
+
const tasks = real === null ? loadedTasks : loadedTasks.map((task) => sealTask(task, forbiddenByTask));
|
|
59
|
+
const executionMode = real === null ? AGENT_SUCCESS_RATE_EXECUTION_MODE : AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE;
|
|
60
|
+
const projectRoots = new Map();
|
|
61
|
+
for (const task of tasks) {
|
|
62
|
+
if (!projectRoots.has(task.benchmarkProject))
|
|
63
|
+
projectRoots.set(task.benchmarkProject, await resolveControlledBenchmarkProject(context.toolRoot, task.benchmarkProject));
|
|
64
|
+
}
|
|
65
|
+
const outDir = path.resolve(context.outputRoot ?? path.resolve(context.toolRoot, context.config.outDir));
|
|
66
|
+
const runtimeRoot = await resolveSandboxRuntimeRoot(outDir, context.toolRoot);
|
|
67
|
+
const io = readArtifactIo(inputs);
|
|
68
|
+
const removeAttemptDirectory = readRemoveAttemptDirectoryInput(inputs);
|
|
69
|
+
const dependencies = readDependenciesInput(inputs);
|
|
70
|
+
await io.ensureDirectory(outDir);
|
|
71
|
+
const runtimeRootExisted = await io.exists(runtimeRoot);
|
|
72
|
+
const caseEvidence = [];
|
|
73
|
+
const patchFiles = [];
|
|
74
|
+
const contextFiles = [];
|
|
75
|
+
const warnings = [];
|
|
76
|
+
const packBuilder = real === null
|
|
77
|
+
? null
|
|
78
|
+
: new AgentSuccessPackContextBuilder(real.kitCommand, { ...defaultContextDependencies(), ...readContextDependenciesInput(inputs) });
|
|
79
|
+
const settings = real === null
|
|
80
|
+
? null
|
|
81
|
+
: {
|
|
82
|
+
providerId: real.providerId,
|
|
83
|
+
timeoutMs: real.timeoutMs,
|
|
84
|
+
env: readAgentEnvInput(inputs),
|
|
85
|
+
runAgent: readRunAgentInput(inputs),
|
|
86
|
+
outDir,
|
|
87
|
+
protectedRoots: [context.toolRoot, outDir, runtimeRoot, packageRoot(), ...projectRoots.values()],
|
|
88
|
+
privateRoots: [outDir, context.toolRoot],
|
|
89
|
+
...readRemoveDirectoryInput(inputs)
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
for (const task of tasks) {
|
|
93
|
+
const canonicalProjectRoot = projectRoots.get(task.benchmarkProject);
|
|
94
|
+
let repairContexts = null;
|
|
95
|
+
if (real !== null && settings !== null && packBuilder !== null) {
|
|
96
|
+
// Both treatment contexts come from the same clean canonical project state, before any sandbox exists.
|
|
97
|
+
const contextInput = buildContextTaskInput(task);
|
|
98
|
+
const contexts = {
|
|
99
|
+
"raw-full-file": await buildRawFullFileContext(contextInput, canonicalProjectRoot),
|
|
100
|
+
"context-pack": await packBuilder.build(contextInput, canonicalProjectRoot)
|
|
101
|
+
};
|
|
102
|
+
repairContexts = contexts;
|
|
103
|
+
}
|
|
104
|
+
const result = real !== null && settings !== null && repairContexts !== null
|
|
105
|
+
? await executeAgentSuccessRepairCase({
|
|
106
|
+
task,
|
|
107
|
+
runId: context.runId,
|
|
108
|
+
canonicalProjectRoot,
|
|
109
|
+
runtimeRoot,
|
|
110
|
+
privateRoots: [outDir, context.toolRoot],
|
|
111
|
+
dependencies,
|
|
112
|
+
// Each attempt is built from the same public task, the same original treatment context and (for repairs)
|
|
113
|
+
// only the harness-authored feedback about the previous attempt.
|
|
114
|
+
repairFor: () => ({
|
|
115
|
+
providerId: real.providerId,
|
|
116
|
+
repairAttempts: real.repairAttempts,
|
|
117
|
+
createProposalSource: ({ attemptNumber, feedback }) => createRealAgentProposalSource({
|
|
118
|
+
settings,
|
|
119
|
+
runId: context.runId,
|
|
120
|
+
task: projectAgentFacingTask(task),
|
|
121
|
+
contexts: repairContexts,
|
|
122
|
+
forbidden: forbiddenByTask.get(task.id) ?? [],
|
|
123
|
+
attemptNumber,
|
|
124
|
+
...(feedback ? { repair: { feedback, maxAttempts: 1 + real.repairAttempts } } : {})
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
: await executeAgentSuccessCase({
|
|
129
|
+
task,
|
|
130
|
+
runId: context.runId,
|
|
131
|
+
canonicalProjectRoot,
|
|
132
|
+
runtimeRoot,
|
|
133
|
+
privateRoots: [outDir, context.toolRoot],
|
|
134
|
+
dependencies
|
|
135
|
+
});
|
|
136
|
+
caseEvidence.push(result.evidence);
|
|
137
|
+
patchFiles.push(...result.patchFiles);
|
|
138
|
+
contextFiles.push(...result.contextFiles);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
// Only an empty runtime root this run created is removed; rmdir refuses a non-empty directory.
|
|
143
|
+
if (!runtimeRootExisted) {
|
|
144
|
+
// rmdir refuses a non-empty directory, so a leftover sandbox keeps the root; that is reported, never hidden.
|
|
145
|
+
await rmdir(runtimeRoot).catch((error) => {
|
|
146
|
+
warnings.push({ code: "sandbox-runtime-root-not-removed", message: boundMessage(`The sandbox runtime root could not be removed: ${error instanceof Error ? error.message : String(error)}`, [outDir, context.toolRoot]) });
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const disposeReason = packBuilder === null ? null : await packBuilder.dispose();
|
|
150
|
+
if (disposeReason !== null)
|
|
151
|
+
warnings.push({ code: "context-work-directory-not-removed", message: disposeReason });
|
|
152
|
+
}
|
|
153
|
+
// Scientific analysis is calculated once, in memory, before persistence; reports never recalculate it.
|
|
154
|
+
const analysis = analyzeAgentSuccessRate(tasks, caseEvidence, executionMode);
|
|
155
|
+
const completedAt = new Date().toISOString();
|
|
156
|
+
const common = { runId: context.runId, pluginId: AGENT_SUCCESS_RATE_PLUGIN_ID, pluginSchemaVersion: agentSuccessRateMetadata.schemaVersion, startedAt, completedAt };
|
|
157
|
+
const executionArtifact = buildAgentSuccessRateExecutionArtifact({
|
|
158
|
+
...common,
|
|
159
|
+
cases: caseEvidence,
|
|
160
|
+
realAgent: real === null
|
|
161
|
+
? undefined
|
|
162
|
+
: {
|
|
163
|
+
providerId: real.providerId,
|
|
164
|
+
timeoutMs: real.timeoutMs,
|
|
165
|
+
attemptsPerTreatment: (1 + real.repairAttempts),
|
|
166
|
+
repairAttempts: real.repairAttempts,
|
|
167
|
+
promptTransport: "stdin",
|
|
168
|
+
contextEffectEvaluated: analysis.contextEffectEvaluated
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
const analysisArtifact = buildAgentSuccessRateAnalysisArtifact({ ...common, analysis });
|
|
172
|
+
const problems = [
|
|
173
|
+
...validateAgentSuccessRateExecutionArtifact(executionArtifact),
|
|
174
|
+
...validateAgentSuccessRateAnalysisArtifact(analysisArtifact),
|
|
175
|
+
...validateAgentSuccessRateArtifactFamily(executionArtifact, analysisArtifact),
|
|
176
|
+
...validatePatchFiles(caseEvidence, patchFiles),
|
|
177
|
+
...validateContextFiles(caseEvidence, contextFiles),
|
|
178
|
+
...(await validateAgentArtifacts(caseEvidence, outDir, io))
|
|
179
|
+
];
|
|
180
|
+
if (problems.length > 0) {
|
|
181
|
+
throw new Error(AGENT_SUCCESS_RATE_ARTIFACT_STATE_MESSAGE + (await removeAgentAttemptDirectories(caseEvidence, outDir, removeAttemptDirectory)));
|
|
182
|
+
}
|
|
183
|
+
// Context and patch artifacts, then execution, then analysis last. Pre-existing files are never overwritten.
|
|
184
|
+
const targets = [
|
|
185
|
+
...contextFiles.map((file) => ({ filePath: resolveWithinRoot(outDir, file.relativePath), content: file.content })),
|
|
186
|
+
...patchFiles.map((file) => ({ filePath: resolveWithinRoot(outDir, file.relativePath), content: file.content })),
|
|
187
|
+
{ filePath: resolveWithinRoot(outDir, AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE), content: `${JSON.stringify(executionArtifact, null, 2)}\n` },
|
|
188
|
+
{ filePath: resolveWithinRoot(outDir, AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE), content: `${JSON.stringify(analysisArtifact, null, 2)}\n` }
|
|
189
|
+
];
|
|
190
|
+
for (const target of targets) {
|
|
191
|
+
if (await io.exists(target.filePath)) {
|
|
192
|
+
throw new Error(AGENT_SUCCESS_RATE_PERSISTENCE_FAILURE_MESSAGE + (await removeAgentAttemptDirectories(caseEvidence, outDir, removeAttemptDirectory)));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const created = [];
|
|
196
|
+
try {
|
|
197
|
+
for (const target of targets) {
|
|
198
|
+
await io.ensureDirectory(path.dirname(target.filePath));
|
|
199
|
+
await io.writeFile(target.filePath, target.content);
|
|
200
|
+
created.push(target.filePath);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Remove only files this attempt created; never touch anything else.
|
|
205
|
+
for (const filePath of created.reverse()) {
|
|
206
|
+
try {
|
|
207
|
+
await io.removeFile(filePath);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// best effort: the failure below is the outcome
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
throw new Error(AGENT_SUCCESS_RATE_PERSISTENCE_FAILURE_MESSAGE + (await removeAgentAttemptDirectories(caseEvidence, outDir, removeAttemptDirectory)));
|
|
214
|
+
}
|
|
215
|
+
return mapAgentSuccessRateToRun({
|
|
216
|
+
runId: context.runId,
|
|
217
|
+
startedAt,
|
|
218
|
+
completedAt,
|
|
219
|
+
target: context.target,
|
|
220
|
+
caseEvidence,
|
|
221
|
+
analysis,
|
|
222
|
+
patchArtifactPaths: patchFiles.map((file) => file.relativePath),
|
|
223
|
+
contextArtifactPaths: contextFiles.map((file) => file.relativePath),
|
|
224
|
+
providerId: real?.providerId ?? null,
|
|
225
|
+
realSettings: real === null ? undefined : { timeoutMs: real.timeoutMs, repairAttempts: real.repairAttempts },
|
|
226
|
+
warnings
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
summarize(result) {
|
|
230
|
+
return result.summary ?? summarizeExperimentRun(result);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
/** Every patch artifact must be promised by exactly one treatment's evidence, and vice versa. */
|
|
234
|
+
function validatePatchFiles(caseEvidence, patchFiles) {
|
|
235
|
+
const problems = [];
|
|
236
|
+
const promised = caseEvidence
|
|
237
|
+
.flatMap((entry) => entry.treatments.flatMap((t) => attemptEvidenceOf(t).flatMap((attempt) => [attempt.proposedPatchPath, attempt.appliedPatchPath])))
|
|
238
|
+
.filter((p) => p !== null);
|
|
239
|
+
const produced = patchFiles.map((file) => file.relativePath);
|
|
240
|
+
if (new Set(produced).size !== produced.length)
|
|
241
|
+
problems.push("duplicate patch artifact path.");
|
|
242
|
+
if ([...promised].sort().join("\n") !== [...produced].sort().join("\n"))
|
|
243
|
+
problems.push("patch artifacts do not match the paths promised by execution evidence.");
|
|
244
|
+
return problems;
|
|
245
|
+
}
|
|
246
|
+
export function mapAgentSuccessRateToRun(args) {
|
|
247
|
+
const realMode = args.analysis.executionMode === AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE;
|
|
248
|
+
const patchOwner = new Map();
|
|
249
|
+
for (const entry of args.caseEvidence) {
|
|
250
|
+
for (const t of entry.treatments) {
|
|
251
|
+
for (const attempt of attemptEvidenceOf(t)) {
|
|
252
|
+
for (const p of [attempt.proposedPatchPath, attempt.appliedPatchPath])
|
|
253
|
+
if (p)
|
|
254
|
+
patchOwner.set(p, { caseId: entry.caseId, variantId: t.treatmentId });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const cases = args.caseEvidence.map((entry, caseIndex) => ({
|
|
259
|
+
id: entry.caseId,
|
|
260
|
+
name: entry.caseName,
|
|
261
|
+
outcomes: entry.treatments.map((treatment, treatmentIndex) => buildOutcome(entry, treatment, args.analysis.cases[caseIndex].treatments[treatmentIndex], args.analysis.executionMode, args.analysis.contextEffectEvaluated)),
|
|
262
|
+
metadata: { benchmarkProject: entry.benchmarkProject, taskLocality: entry.taskLocality, ...(realMode ? {} : { fixtureId: entry.fixtureId }) }
|
|
263
|
+
}));
|
|
264
|
+
const run = {
|
|
265
|
+
runId: args.runId,
|
|
266
|
+
pluginId: AGENT_SUCCESS_RATE_PLUGIN_ID,
|
|
267
|
+
startedAt: args.startedAt,
|
|
268
|
+
completedAt: args.completedAt,
|
|
269
|
+
status: aggregateTreatmentStatus(args.caseEvidence.flatMap((entry) => entry.treatments.map((treatment) => treatment.status))),
|
|
270
|
+
target: args.target,
|
|
271
|
+
variants: AGENT_SUCCESS_RATE_VARIANTS.map((variant) => ({ ...variant })),
|
|
272
|
+
cases,
|
|
273
|
+
metrics: toAgentSuccessRunMetrics(args.analysis),
|
|
274
|
+
artifacts: [
|
|
275
|
+
{
|
|
276
|
+
id: "agent-success-rate-execution",
|
|
277
|
+
label: "Agent success rate execution evidence",
|
|
278
|
+
path: AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE,
|
|
279
|
+
kind: "artifact",
|
|
280
|
+
mimeType: "application/json",
|
|
281
|
+
description: "Per-case, per-treatment execution evidence without source text, patch bodies or raw command output."
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: "agent-success-rate-analysis",
|
|
285
|
+
label: "Agent success rate scientific analysis",
|
|
286
|
+
path: AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE,
|
|
287
|
+
kind: "artifact",
|
|
288
|
+
mimeType: "application/json",
|
|
289
|
+
description: "Task-success, edit-quality and blast-radius metrics with explicit availability, plus matched-case aggregates."
|
|
290
|
+
},
|
|
291
|
+
...(args.contextArtifactPaths ?? []).map((contextPath) => ({
|
|
292
|
+
id: `context:${contextPath}`,
|
|
293
|
+
label: "Treatment source context supplied to the provider",
|
|
294
|
+
path: contextPath,
|
|
295
|
+
kind: "artifact",
|
|
296
|
+
mimeType: "text/plain"
|
|
297
|
+
})),
|
|
298
|
+
...args.patchArtifactPaths.map((patchPath) => ({
|
|
299
|
+
id: `patch:${patchPath}`,
|
|
300
|
+
label: patchLabel(patchPath, realMode),
|
|
301
|
+
path: patchPath,
|
|
302
|
+
kind: "artifact",
|
|
303
|
+
mimeType: "text/x-diff",
|
|
304
|
+
...patchOwner.get(patchPath)
|
|
305
|
+
}))
|
|
306
|
+
],
|
|
307
|
+
warnings: [...(args.warnings ?? [])],
|
|
308
|
+
// Outcome failures are the authoritative per-treatment failure location.
|
|
309
|
+
failures: [],
|
|
310
|
+
metadata: {
|
|
311
|
+
executionMode: args.analysis.executionMode,
|
|
312
|
+
...(args.providerId ? { providerId: args.providerId } : {}),
|
|
313
|
+
...(args.realSettings ? { timeoutMs: args.realSettings.timeoutMs, repairAttempts: args.realSettings.repairAttempts } : {}),
|
|
314
|
+
contextEffectEvaluated: args.analysis.contextEffectEvaluated,
|
|
315
|
+
executionArtifactPath: AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE,
|
|
316
|
+
analysisArtifactPath: AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE
|
|
317
|
+
},
|
|
318
|
+
caseExecutionEvidence: structuredClone(args.caseEvidence),
|
|
319
|
+
analysis: structuredClone(args.analysis)
|
|
320
|
+
};
|
|
321
|
+
run.summary = summarizeExperimentRun(run);
|
|
322
|
+
return run;
|
|
323
|
+
}
|
|
324
|
+
function buildOutcome(caseEvidence, treatment, treatmentAnalysis, executionMode, contextEffectEvaluated) {
|
|
325
|
+
const { caseId } = caseEvidence;
|
|
326
|
+
const { treatmentId } = treatment;
|
|
327
|
+
const success = treatmentAnalysis.metrics.taskSuccess;
|
|
328
|
+
return {
|
|
329
|
+
id: `${caseId}:${treatmentId}`,
|
|
330
|
+
caseId,
|
|
331
|
+
variantId: treatmentId,
|
|
332
|
+
status: treatment.status,
|
|
333
|
+
metrics: toAgentSuccessOutcomeMetrics(treatmentAnalysis),
|
|
334
|
+
artifacts: [],
|
|
335
|
+
warnings: treatment.availability === "baseline-invalid"
|
|
336
|
+
? [
|
|
337
|
+
{
|
|
338
|
+
code: "baseline-invalid",
|
|
339
|
+
message: "The benchmark baseline is not evaluable; no implementation attempt was scored.",
|
|
340
|
+
variantId: treatmentId,
|
|
341
|
+
caseId,
|
|
342
|
+
details: { reasons: treatment.baselineAssessment?.reasons ?? [] }
|
|
343
|
+
}
|
|
344
|
+
]
|
|
345
|
+
: [],
|
|
346
|
+
failures: treatment.errors.map((error) => ({ code: error.code, message: error.message, variantId: treatmentId, caseId, recoverable: false })),
|
|
347
|
+
metadata: {
|
|
348
|
+
evidenceAvailability: treatment.availability,
|
|
349
|
+
// taskSuccess is a measurement, not an infrastructure status; null means it could not be determined.
|
|
350
|
+
taskSuccess: success.availability === "available" ? success.value : null,
|
|
351
|
+
executionMode,
|
|
352
|
+
...(treatment.realAgent ? { providerId: treatment.realAgent.providerId, providerStatus: treatment.realAgent.providerStatus } : {}),
|
|
353
|
+
...(treatmentAnalysis.repair
|
|
354
|
+
? {
|
|
355
|
+
attemptCount: treatmentAnalysis.repair.attemptCount,
|
|
356
|
+
repairAttemptCount: treatmentAnalysis.repair.repairAttemptCount,
|
|
357
|
+
initialAttemptTaskSuccess: treatmentAnalysis.repair.initialAttemptTaskSuccess.availability === "available" ? treatmentAnalysis.repair.initialAttemptTaskSuccess.value : null,
|
|
358
|
+
repairSucceeded: treatmentAnalysis.repair.repairSucceeded.availability === "available" ? treatmentAnalysis.repair.repairSucceeded.value : null
|
|
359
|
+
}
|
|
360
|
+
: {}),
|
|
361
|
+
contextEffectEvaluated
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function aggregateTreatmentStatus(statuses) {
|
|
366
|
+
if (statuses.length === 0)
|
|
367
|
+
return "skipped";
|
|
368
|
+
if (statuses.every((status) => status === "completed"))
|
|
369
|
+
return "completed";
|
|
370
|
+
if (statuses.every((status) => status === "skipped"))
|
|
371
|
+
return "skipped";
|
|
372
|
+
if (statuses.every((status) => status === "failed"))
|
|
373
|
+
return "failed";
|
|
374
|
+
return "partial";
|
|
375
|
+
}
|
|
376
|
+
function readDependenciesInput(inputs) {
|
|
377
|
+
const value = inputs?.agentSuccessDependencies;
|
|
378
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
379
|
+
}
|
|
380
|
+
function readArtifactIo(inputs) {
|
|
381
|
+
const value = inputs?.agentSuccessArtifactIo;
|
|
382
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...defaultArtifactIo, ...value } : defaultArtifactIo;
|
|
383
|
+
}
|
|
384
|
+
/** Real-agent mode exists only when a provider is named; the closed config validator has already enforced the opt-in. */
|
|
385
|
+
function readRealAgentConfig(config) {
|
|
386
|
+
if (config.agentId === undefined)
|
|
387
|
+
return null;
|
|
388
|
+
return {
|
|
389
|
+
providerId: config.agentId,
|
|
390
|
+
timeoutMs: config.timeoutMs ?? AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS,
|
|
391
|
+
kitCommand: config.kitCommand ?? DEFAULT_AGENT_SUCCESS_KIT_COMMAND,
|
|
392
|
+
repairAttempts: config.repairAttempts ?? 0
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/** Captures hidden values for the leak guard, then returns a copy of the task with the reference fixture removed. */
|
|
396
|
+
function sealTask(task, forbiddenByTask) {
|
|
397
|
+
forbiddenByTask.set(task.id, collectForbiddenAgentValues(task));
|
|
398
|
+
const sealed = { ...task };
|
|
399
|
+
delete sealed.deterministicFixture;
|
|
400
|
+
return sealed;
|
|
401
|
+
}
|
|
402
|
+
/** The installed package root (two levels above src/ or dist/ output of this module). */
|
|
403
|
+
function packageRoot() {
|
|
404
|
+
return fileURLToPath(new URL("../../../../", import.meta.url));
|
|
405
|
+
}
|
|
406
|
+
function defaultContextDependencies() {
|
|
407
|
+
return {
|
|
408
|
+
buildIndex: buildMyDevKitIndex,
|
|
409
|
+
runCommand: runMeasuredCommand,
|
|
410
|
+
readSymbolIndex: async (indexDir) => JSON.parse(await readFile(path.join(indexDir, "symbol-index.json"), "utf8")),
|
|
411
|
+
probeVersion: probeMyDevKitVersion
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function readContextDependenciesInput(inputs) {
|
|
415
|
+
const value = inputs?.agentSuccessContextDependencies;
|
|
416
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
417
|
+
}
|
|
418
|
+
function readRunAgentInput(inputs) {
|
|
419
|
+
const value = inputs?.agentSuccessRunAgent;
|
|
420
|
+
return typeof value === "function" ? value : runAgentPrompt;
|
|
421
|
+
}
|
|
422
|
+
function readAgentEnvInput(inputs) {
|
|
423
|
+
const value = inputs?.agentSuccessAgentEnv;
|
|
424
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
425
|
+
}
|
|
426
|
+
/** Every context artifact must be promised by exactly one treatment's evidence, and vice versa. */
|
|
427
|
+
function validateContextFiles(caseEvidence, contextFiles) {
|
|
428
|
+
// The treatment context is persisted once, by the initial attempt, and repairs reuse it unchanged.
|
|
429
|
+
const promised = caseEvidence.flatMap((entry) => entry.treatments.map((t) => attemptEvidenceOf(t)[0]?.realAgent?.context.contextArtifactPath ?? null)).filter((p) => p !== null);
|
|
430
|
+
const produced = contextFiles.map((file) => file.relativePath);
|
|
431
|
+
const problems = [];
|
|
432
|
+
if (new Set(produced).size !== produced.length)
|
|
433
|
+
problems.push("duplicate context artifact path.");
|
|
434
|
+
if ([...promised].sort().join("\n") !== [...produced].sort().join("\n"))
|
|
435
|
+
problems.push("context artifacts do not match the paths promised by execution evidence.");
|
|
436
|
+
return problems;
|
|
437
|
+
}
|
|
438
|
+
/** Agent attempt files are written by the shared agent runner; each referenced file must exist and attempts are single. */
|
|
439
|
+
async function validateAgentArtifacts(caseEvidence, outDir, io) {
|
|
440
|
+
const problems = [];
|
|
441
|
+
const directories = new Set();
|
|
442
|
+
for (const entry of caseEvidence) {
|
|
443
|
+
for (const treatment of entry.treatments) {
|
|
444
|
+
for (const attempt of attemptEvidenceOf(treatment)) {
|
|
445
|
+
const real = attempt.realAgent;
|
|
446
|
+
if (!real?.agentArtifactDirectory)
|
|
447
|
+
continue;
|
|
448
|
+
if (directories.has(real.agentArtifactDirectory))
|
|
449
|
+
problems.push("duplicate agent attempt directory.");
|
|
450
|
+
directories.add(real.agentArtifactDirectory);
|
|
451
|
+
if (!real.agentArtifactDirectory.endsWith(`/attempt-${real.attempt}`))
|
|
452
|
+
problems.push(`agent attempt directory is not attempt-${real.attempt}.`);
|
|
453
|
+
for (const reference of Object.values(real.agentArtifacts)) {
|
|
454
|
+
if (reference === null)
|
|
455
|
+
continue;
|
|
456
|
+
try {
|
|
457
|
+
if (!(await io.exists(resolveWithinRoot(outDir, reference))))
|
|
458
|
+
problems.push(`agent artifact ${reference} is missing.`);
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
problems.push("agent artifact path is invalid.");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return problems;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Removes only attempt directories this run created (those recorded as invoked). Removal is best effort, but a directory
|
|
471
|
+
* that could not be removed is counted and reported in the returned suffix so cleanup is never presented as complete.
|
|
472
|
+
*/
|
|
473
|
+
async function removeAgentAttemptDirectories(caseEvidence, outDir, removeDirectory) {
|
|
474
|
+
let failures = 0;
|
|
475
|
+
for (const entry of caseEvidence) {
|
|
476
|
+
for (const treatment of entry.treatments) {
|
|
477
|
+
for (const attempt of attemptEvidenceOf(treatment)) {
|
|
478
|
+
const directory = attempt.realAgent?.agentArtifactDirectory;
|
|
479
|
+
if (!directory)
|
|
480
|
+
continue;
|
|
481
|
+
try {
|
|
482
|
+
await removeDirectory(resolveWithinRoot(outDir, directory));
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
failures += 1;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return failures > 0 ? ` ${failures} agent attempt director${failures === 1 ? "y" : "ies"} could not be removed.` : "";
|
|
491
|
+
}
|
|
492
|
+
function patchLabel(patchPath, realMode) {
|
|
493
|
+
const attempt = /attempt-(\d+)-/.exec(patchPath)?.[1];
|
|
494
|
+
const suffix = realMode && attempt && attempt !== "1" ? ` (attempt ${attempt})` : "";
|
|
495
|
+
if (patchPath.endsWith("-applied.patch"))
|
|
496
|
+
return `Applied patch (from change evidence)${suffix}`;
|
|
497
|
+
return `${realMode ? "Proposed patch (provider final answer)" : "Proposed deterministic fixture patch"}${suffix}`;
|
|
498
|
+
}
|
|
499
|
+
/** Removal seam for failed-persistence attempt directories so a removal failure can be proven on every platform. */
|
|
500
|
+
function readRemoveAttemptDirectoryInput(inputs) {
|
|
501
|
+
const value = inputs?.agentSuccessRemoveAttemptDirectory;
|
|
502
|
+
return typeof value === "function" ? value : (directory) => rm(directory, { recursive: true, force: true });
|
|
503
|
+
}
|
|
504
|
+
function readRemoveDirectoryInput(inputs) {
|
|
505
|
+
const value = inputs?.agentSuccessRemoveDirectory;
|
|
506
|
+
return typeof value === "function" ? { removeDirectory: value } : {};
|
|
507
|
+
}
|