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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +19 -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/agents/adapters/claudeAdapter.js +6 -3
  36. package/dist/src/agents/adapters/codexAdapter.js +51 -10
  37. package/dist/src/agents/index.js +1 -0
  38. package/dist/src/agents/normalizeAgentTelemetry.js +50 -0
  39. package/dist/src/agents/parseAgentTokenUsage.js +71 -49
  40. package/dist/src/agents/providerBuildObservation.js +97 -0
  41. package/dist/src/audits/core/projectInventory.js +1 -0
  42. package/dist/src/cli/help.js +44 -1
  43. package/dist/src/commands/runExperimentDescribeCommand.js +83 -1
  44. package/dist/src/commands/runExperimentRunCommand.js +309 -6
  45. package/dist/src/core/processTree.js +55 -0
  46. package/dist/src/core/runMeasuredCommand.js +108 -22
  47. package/dist/src/evaluation/agentSuccess/applyPatchToSandbox.js +37 -0
  48. package/dist/src/evaluation/agentSuccess/assessBaseline.js +23 -0
  49. package/dist/src/evaluation/agentSuccess/corpusTypes.js +74 -0
  50. package/dist/src/evaluation/agentSuccess/extractPatchCandidate.js +46 -0
  51. package/dist/src/evaluation/agentSuccess/index.js +14 -0
  52. package/dist/src/evaluation/agentSuccess/parseUnifiedDiff.js +210 -0
  53. package/dist/src/evaluation/agentSuccess/patchTypes.js +5 -0
  54. package/dist/src/evaluation/agentSuccess/readAgentSuccessCorpus.js +183 -0
  55. package/dist/src/evaluation/agentSuccess/runVerificationCheck.js +100 -0
  56. package/dist/src/evaluation/agentSuccess/taskPaths.js +34 -0
  57. package/dist/src/evaluation/agentSuccess/taskTypes.js +14 -0
  58. package/dist/src/evaluation/agentSuccess/validateAgentSuccessCorpus.js +307 -0
  59. package/dist/src/evaluation/agentSuccess/validateAgentSuccessTask.js +297 -0
  60. package/dist/src/evaluation/agentSuccess/validatePatchPolicy.js +66 -0
  61. package/dist/src/evaluation/agentSuccess/verificationTypes.js +1 -0
  62. package/dist/src/evaluation/benchmarkSandbox/createBenchmarkSandbox.js +127 -0
  63. package/dist/src/evaluation/benchmarkSandbox/errors.js +11 -0
  64. package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +62 -0
  65. package/dist/src/evaluation/benchmarkSandbox/index.js +8 -0
  66. package/dist/src/evaluation/benchmarkSandbox/minimalHostEnv.js +37 -0
  67. package/dist/src/evaluation/benchmarkSandbox/pathPolicy.js +49 -0
  68. package/dist/src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.js +74 -0
  69. package/dist/src/evaluation/benchmarkSandbox/treeSnapshot.js +57 -0
  70. package/dist/src/evaluation/benchmarkSandbox/types.js +3 -0
  71. package/dist/src/evaluation/changeSet/captureChangeSet.js +98 -0
  72. package/dist/src/evaluation/changeSet/index.js +2 -0
  73. package/dist/src/evaluation/changeSet/types.js +9 -0
  74. package/dist/src/experiments/defaultRegistry.js +2 -0
  75. package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
  76. package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
  77. package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
  78. package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
  79. package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
  80. package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpoint.js +216 -0
  81. package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +588 -0
  82. package/dist/src/experiments/plugins/agentSuccessRate/campaignEvidenceQuality.js +77 -0
  83. package/dist/src/experiments/plugins/agentSuccessRate/campaignExecution.js +314 -0
  84. package/dist/src/experiments/plugins/agentSuccessRate/campaignIdentity.js +111 -0
  85. package/dist/src/experiments/plugins/agentSuccessRate/campaignProgress.js +173 -0
  86. package/dist/src/experiments/plugins/agentSuccessRate/campaignQueue.js +11 -0
  87. package/dist/src/experiments/plugins/agentSuccessRate/campaignRecovery.js +256 -0
  88. package/dist/src/experiments/plugins/agentSuccessRate/campaignResumableExecution.js +207 -0
  89. package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
  90. package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
  91. package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
  92. package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
  93. package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
  94. package/dist/src/experiments/plugins/agentSuccessRate/index.js +26 -0
  95. package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
  96. package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
  97. package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +517 -0
  98. package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +272 -0
  99. package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
  100. package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
  101. package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
  102. package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
  103. package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
  104. package/dist/src/experiments/plugins/agentSuccessRate/timeoutPresets.js +9 -0
  105. package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
  106. package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
  107. package/dist/src/report/experiments/agentSuccessCampaignProgressReportModel.js +1 -0
  108. package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
  109. package/dist/src/report/experiments/buildAgentSuccessCampaignProgressReport.js +6 -0
  110. package/dist/src/report/experiments/buildAgentSuccessRateReport.js +266 -0
  111. package/dist/src/report/experiments/buildPluginExperimentReport.js +27 -2
  112. package/dist/src/report/experiments/index.js +4 -0
  113. package/dist/src/report/experiments/renderAgentSuccessCampaignProgressHtml.js +6 -0
  114. package/dist/src/report/experiments/renderAgentSuccessCampaignProgressText.js +25 -0
  115. package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +171 -0
  116. package/dist/src/report/experiments/renderAgentSuccessRateText.js +146 -0
  117. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  118. package/dist/src/report/experiments/renderPluginExperimentReportText.js +5 -0
  119. package/dist/src/report/experiments/writeAgentSuccessCampaignProgressReport.js +135 -0
  120. package/dist/src/report/experiments/writePluginExperimentReports.js +4 -3
  121. package/docs/METRICS.md +79 -0
  122. package/package.json +1 -1
@@ -1,12 +1,14 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { lstat, mkdir, readFile, readdir, realpath } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { parseAgentCommandTemplate } from "../agents/index.js";
4
4
  import { parseAgentId } from "../agents/agentRegistry.js";
5
5
  import { readBenchmarkProjectProfiles, readEvaluationCases } from "../evaluation/index.js";
6
+ import { AGENT_SUCCESS_PROJECT_PROFILES_PATH, AGENT_SUCCESS_TASK_CATALOG_PATH, assertReadAgentSuccessCorpus } from "../evaluation/agentSuccess/index.js";
6
7
  import { summarizeRetrievalGroundTruthIssuesSafely, validateRetrievalPrecisionRecallCorpus } from "../evaluation/retrievalQuality/index.js";
7
8
  import { loadLocalRepositorySubject } from "../evaluation/localRepositorySubject/index.js";
8
9
  import { contextStrategyComparisonPlugin, contextWindowScalingPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, incrementalChangeStalenessPlugin, parseContextBudgetsCliValue, parseWarmIndexCampaignPresetId, resolveExperimentTarget, retrievalPrecisionRecallPlugin, retrievalQueryStrategyComparisonPlugin, runExperiment, warmIndexReusePlugin } from "../experiments/index.js";
9
10
  import { contextPackGenerationPlugin } from "../experiments/plugins/contextPackGeneration/index.js";
11
+ import { AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS, AGENT_SUCCESS_RATE_REAL_AGENT_IDS, agentSuccessRatePlugin, selectAgentSuccessTasks } from "../experiments/plugins/agentSuccessRate/index.js";
10
12
  import { prepareSyntheticContextWindowScalingInputs } from "../experiments/plugins/contextWindowScaling/index.js";
11
13
  import { projectExternalLocalTarget, projectRunForExternalLocalPersistence, } from "../experiments/plugins/contextWindowScaling/localSubjectPrivacy.js";
12
14
  import { assertWorkRootOutsideTarget } from "../experiments/plugins/contextWindowScaling/localSubjectScratch.js";
@@ -15,6 +17,18 @@ import { parsePromptComplexityLevel, parsePromptStrategy } from "../prompts/inde
15
17
  import { writePluginExperimentReports } from "../report/index.js";
16
18
  import { createLabExecutionContext, resolvePackageResource } from "../runtime/index.js";
17
19
  import { runWarmIndexCampaignPresentation } from "./runWarmIndexCampaignPresentation.js";
20
+ import { observeProviderBuild } from "../agents/providerBuildObservation.js";
21
+ import { resolveAgentTimeoutPreset } from "../experiments/plugins/agentSuccessRate/timeoutPresets.js";
22
+ import { validateAgentSuccessRateConfig } from "../experiments/plugins/agentSuccessRate/config.js";
23
+ import { AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS } from "../experiments/plugins/agentSuccessRate/realAgentExecution.js";
24
+ import { AGENT_SUCCESS_RATE_TREATMENT_IDS } from "../experiments/plugins/agentSuccessRate/metadata.js";
25
+ import { executeAgentSuccessCampaignV2, prepareAgentSuccessCampaign } from "../experiments/plugins/agentSuccessRate/campaignExecution.js";
26
+ import { readCampaignCheckpointV2, validateCampaignOutputPath } from "../experiments/plugins/agentSuccessRate/campaignCheckpointStore.js";
27
+ import { validateCampaignLimitAuthorization } from "../experiments/plugins/agentSuccessRate/campaignRecovery.js";
28
+ import { campaignIdentifier } from "../experiments/plugins/agentSuccessRate/campaignIdentity.js";
29
+ import { readCampaignProgress } from "../experiments/plugins/agentSuccessRate/campaignProgress.js";
30
+ import { writeAgentSuccessCampaignProgressReport } from "../report/experiments/writeAgentSuccessCampaignProgressReport.js";
31
+ import { boundMessage } from "../experiments/plugins/agentSuccessRate/execution.js";
18
32
  // ---------------------------------------------------------------------------
19
33
  // v0.4.6 Batch 4 -- reusable experiment-run command owner.
20
34
  //
@@ -71,9 +85,36 @@ const RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXTERNAL_ALLOWED_FLAGS = ["--experimen
71
85
  const CONTEXT_PACK_GENERATION_CASES_RESOURCE = "benchmarks/contracts/warm-index-benchmark-cases.json";
72
86
  const CONTEXT_PACK_GENERATION_ALLOWED_FLAGS = ["--experiment", "--out", "--case", "--benchmark-project", "--kit-command"];
73
87
  const CONTEXT_PACK_GENERATION_EXTERNAL_ALLOWED_FLAGS = ["--experiment", "--out", "--target", "--local-subject-config", "--kit-command"];
88
+ // agent-success-rate (v0.9.0) owns the bundled six-task implementation corpus (task catalog and project profiles), both
89
+ // resolved from the package root, never the cwd. It runs against the Lab itself only and accepts exactly these options;
90
+ // every other flag is rejected, not ignored. Real providers run only with --agent together with --include-real-agents.
91
+ const AGENT_SUCCESS_RATE_ALLOWED_FLAGS = [
92
+ "--experiment",
93
+ "--out",
94
+ "--case",
95
+ "--benchmark-project",
96
+ "--kit-command",
97
+ "--agent",
98
+ "--include-real-agents",
99
+ "--timeout-ms",
100
+ "--repair-attempts",
101
+ "--agent-campaign", "--one-case", "--campaign-source-root", "--timeout-preset",
102
+ "--expected-checkpoint-revision", "--limit-case", "--limit-treatment", "--limit-epoch", "--authorize-additional-invocation"
103
+ ];
104
+ const AGENT_CAMPAIGN_FLAGS = ["--agent-campaign", "--one-case", "--campaign-source-root", "--expected-checkpoint-revision", "--limit-case", "--limit-treatment", "--limit-epoch", "--authorize-additional-invocation"];
105
+ export function assertAgentSuccessRateFlags(argv) {
106
+ const flags = argv.filter((arg) => arg.startsWith("--"));
107
+ const unsupported = [...new Set(flags.filter((flag) => !AGENT_SUCCESS_RATE_ALLOWED_FLAGS.includes(flag)))];
108
+ if (unsupported.length > 0) {
109
+ throw new Error(`${unsupported.join(", ")} ${unsupported.length === 1 ? "is" : "are"} not supported for --experiment ${agentSuccessRatePlugin.metadata.id}; supported options: ${AGENT_SUCCESS_RATE_ALLOWED_FLAGS.join(", ")}.`);
110
+ }
111
+ }
74
112
  export async function runExperimentRunCommandFromArgs(argv, options = {}) {
113
+ let parsed = false;
114
+ const campaignUsage = argv.includes("--agent-campaign") || argv.some(a => AGENT_CAMPAIGN_FLAGS.includes(a));
75
115
  try {
76
116
  const args = parseRunExperimentArgs(argv);
117
+ parsed = true;
77
118
  const context = options.context ?? createLabExecutionContext();
78
119
  const toolRoot = context.packageRoot;
79
120
  const registry = createDefaultExperimentPluginRegistry();
@@ -96,6 +137,8 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
96
137
  });
97
138
  }
98
139
  const inputs = await loadPluginInputs(args, toolRoot, context, outputRoot);
140
+ if (args.agentCampaign)
141
+ return await runAgentCampaignCommand(args, context, outputRoot, inputs, runId, options);
99
142
  const result = await runExperiment({
100
143
  pluginId: args.experimentId,
101
144
  registry,
@@ -151,7 +194,8 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
151
194
  `Target root: ${result.target.targetRoot}`,
152
195
  `Output: ${String(result.metadata?.outputRoot ?? "")}`,
153
196
  `Report JSON: ${reports.outputPaths.jsonPath}`,
154
- `Report HTML: ${reports.outputPaths.htmlPath}`
197
+ `Report HTML: ${reports.outputPaths.htmlPath}`,
198
+ ...(args.experimentId === agentSuccessRatePlugin.metadata.id ? agentSuccessRateSummaryLines(result, reports.outputPaths.textPath) : [])
155
199
  ];
156
200
  // Automatic presentation (report already produced above) applies only to warm-index-reuse
157
201
  // campaign runs; legacy fake-agent warm-index runs and context-strategy-comparison stay
@@ -195,16 +239,125 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
195
239
  return 0;
196
240
  }
197
241
  catch (error) {
198
- if (process.env.DEBUG) {
242
+ if (campaignUsage) {
243
+ const sourceIndex = argv.indexOf("--campaign-source-root");
244
+ const source = sourceIndex >= 0 ? argv[sourceIndex + 1] : undefined;
245
+ const message = error instanceof Error ? error.message : "CAMPAIGN_FAILED";
246
+ console.error(boundMessage(message, [process.cwd(), options.context?.packageRoot ?? "", ...(source ? [path.resolve(options.context?.invocationCwd ?? process.cwd(), source)] : [])]));
247
+ }
248
+ else if (process.env.DEBUG) {
199
249
  console.error(error);
200
250
  }
201
251
  else {
202
252
  console.error(error instanceof Error ? error.message : String(error));
203
253
  }
204
- return 1;
254
+ return campaignUsage && !parsed ? 2 : 1;
255
+ }
256
+ }
257
+ async function runAgentCampaignCommand(args, context, outputRoot, inputs, runId, options) {
258
+ const request = args.agentCampaign;
259
+ const sourceRoot = path.resolve(context.invocationCwd, request.sourceRoot ?? context.packageRoot);
260
+ try {
261
+ const packageName = "@dailephd/my-dev-kit-lab";
262
+ const validPackageVersion = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
263
+ const executingMetadata = JSON.parse(await readFile(path.join(context.packageRoot, "package.json"), "utf8"));
264
+ if (executingMetadata.name !== packageName || typeof executingMetadata.version !== "string" || !validPackageVersion.test(executingMetadata.version))
265
+ throw new Error();
266
+ if (!(await lstat(path.join(sourceRoot, "src"))).isDirectory() || path.resolve(await realpath(sourceRoot)).toLowerCase() !== sourceRoot.toLowerCase())
267
+ throw new Error();
268
+ const metadata = JSON.parse(await readFile(path.join(sourceRoot, "package.json"), "utf8"));
269
+ if (metadata.name !== packageName || metadata.version !== executingMetadata.version)
270
+ throw new Error();
271
+ }
272
+ catch {
273
+ throw new Error("CAMPAIGN_SOURCE_UNVERIFIABLE: supply --campaign-source-root with a current my-dev-kit-lab source checkout; installed source bytes cannot be guessed.");
274
+ }
275
+ const boundary = { toolRoot: context.packageRoot, outputRoot };
276
+ await validateCampaignOutputPath(boundary, sourceRoot);
277
+ let entries = [];
278
+ try {
279
+ entries = await readdir(outputRoot);
280
+ }
281
+ catch (error) {
282
+ if (error.code !== "ENOENT")
283
+ throw error;
284
+ if (request.mode !== "new")
285
+ throw new Error("RESUMABLE_CAMPAIGN_REQUIRED: --out must contain an existing version 2 campaign.");
286
+ }
287
+ if (request.mode === "new" && entries.length > 0)
288
+ throw new Error("CAMPAIGN_OUTPUT_EXISTS: new requires an empty output root; existing artifacts are preserved.");
289
+ if (request.mode !== "new" && !entries.includes("campaign-checkpoints-v2"))
290
+ throw new Error("RESUMABLE_CAMPAIGN_REQUIRED: no version 2 checkpoint in --out.");
291
+ const validation = validateAgentSuccessRateConfig(args.config);
292
+ if (!validation.valid || !validation.config)
293
+ throw new Error("INVALID_CAMPAIGN_CONFIG");
294
+ const config = validation.config;
295
+ const tasks = selectAgentSuccessTasks(inputs.agentSuccessTasks, config);
296
+ const runtime = options.campaignRuntime;
297
+ const provider = config.agentId ? { kind: "real-agent", providerId: config.agentId, includeRealAgents: true, timeoutMs: config.timeoutMs ?? AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS, repairAttempts: (config.repairAttempts ?? 0), kitCommand: config.kitCommand, contextDependencies: runtime?.contextDependencies, env: runtime?.env } : { kind: "deterministic-fixture" };
298
+ const preparation = { boundary, sourceRoot, corpusFile: resolvePackageResource(context, AGENT_SUCCESS_TASK_CATALOG_PATH), corpusRoot: context.packageRoot, tasks, treatments: AGENT_SUCCESS_RATE_TREATMENT_IDS, provider, dependencies: runtime?.dependencies };
299
+ // Source, corpus and canonical project byte checks precede even a task-free version probe.
300
+ let prepared = await prepareAgentSuccessCampaign(preparation);
301
+ if (provider.kind === "real-agent") {
302
+ provider.trustedExecutable = await observeProviderBuild(provider.providerId, { env: runtime?.env });
303
+ prepared = await prepareAgentSuccessCampaign(preparation);
304
+ }
305
+ let intent = request.mode === "new" ? { kind: "new-resumable" } : { kind: "resume" };
306
+ let reused = 0;
307
+ const previousEpochs = new Set();
308
+ if (request.mode !== "new") {
309
+ // Identity-scoped stores are not discovery: a different digest cannot create another campaign on resume.
310
+ const identities = await readdir(path.join(outputRoot, "campaign-checkpoints-v2"));
311
+ if (!identities.includes(prepared.identity.digest))
312
+ throw new Error("CAMPAIGN_IDENTITY_MISMATCH: current inputs do not match this output root.");
313
+ const stored = await readCampaignCheckpointV2(boundary, prepared.identity, tasks);
314
+ if (!stored.reusable || !stored.checkpoint || stored.identityVerification !== "MATCH")
315
+ throw new Error("RESUMABLE_CAMPAIGN_REQUIRED_MATCH");
316
+ const cp = stored.checkpoint;
317
+ cp.units.flatMap(u => u.epochs).forEach(e => previousEpochs.add(e.id));
318
+ reused = cp.units.filter(u => u.state === "completed").length;
319
+ if (request.mode === "continue-limit") {
320
+ const unit = cp.units.find(u => u.caseId === request.limitCase && u.treatmentId === request.limitTreatment);
321
+ if (cp.generation !== request.expectedRevision || !unit || unit.epochs.at(-1)?.id !== request.limitEpoch)
322
+ throw new Error("LIMIT_AUTHORIZATION_MISMATCH");
323
+ const authorization = { identityDigest: cp.identity.digest, generation: request.expectedRevision, revision: cp.integrity, caseId: request.limitCase, treatmentId: request.limitTreatment, acknowledgeInvocation: true, acknowledgeExternalLimit: true };
324
+ validateCampaignLimitAuthorization(authorization, cp, unit);
325
+ intent = { kind: "authorize-limit-continuation", authorization };
326
+ }
327
+ }
328
+ else {
329
+ await mkdir(outputRoot, { recursive: true });
330
+ await validateCampaignOutputPath(boundary, sourceRoot);
331
+ if ((await readdir(outputRoot)).length !== 0)
332
+ throw new Error("CAMPAIGN_OUTPUT_EXISTS");
333
+ }
334
+ const result = await executeAgentSuccessCampaignV2(prepared, { intent, runId, limit: request.oneCase ? "one-case" : "all-selected-cases", hooks: runtime?.hooks });
335
+ const progressLines = [];
336
+ let progressFailed = false;
337
+ let progressValidated = false;
338
+ try {
339
+ const snapshot = await readCampaignProgress(boundary, prepared.identity, tasks);
340
+ progressValidated = true;
341
+ const progress = await writeAgentSuccessCampaignProgressReport({ snapshot, boundary });
342
+ progressLines.push(`Progress status: ${progress.model.state}`, `Progress checkpoint revision: ${progress.model.checkpointGeneration} (${progress.model.checkpointRevision})`, `Completed evaluations: ${progress.model.unitCounts.completed}/${progress.model.totalTreatmentUnits}`, `Completed cases: ${progress.model.caseCounts.completed}/${progress.model.selectedCaseCount}`, `Progress JSON: ${progress.outputPaths.jsonPath}`, `Progress Text: ${progress.outputPaths.textPath}`, `Progress HTML: ${progress.outputPaths.htmlPath}`);
343
+ }
344
+ catch {
345
+ progressFailed = true;
346
+ progressLines.push(progressValidated ? "Progress persistence: failed (validated checkpoint retained)." : "Progress status: blocked-integrity (checkpoint unavailable or untrusted).");
347
+ progressLines.push("Progress publication: failed; checkpoint/scientific execution outcome is reported separately.");
205
348
  }
349
+ const newEpochs = result.checkpoint?.units.flatMap(u => u.epochs).filter(e => !previousEpochs.has(e.id)) ?? [];
350
+ // Provider evidence, never mere creation of a reservation, establishes invocation.
351
+ const newWork = newEpochs.some(e => e.terminal?.evidence.realAgent?.providerInvoked === true) ? "invoked" : newEpochs.some(e => e.phase === "invocation-authorized" || e.phase === "uncertain") ? "unconfirmed" : "none";
352
+ console.log(["Experiment: agent-success-rate", `Campaign mode: ${request.mode}`, `Identity: ${prepared.identity.digest.slice(0, 16)}`, `Status: ${result.status === "provider-limit" ? "paused-provider-limit" : result.status}`, `New provider work: ${newWork}`, `Validated work reused: ${reused > 0 ? "yes" : "no"}`, `Checkpoint revision: ${result.checkpoint?.generation ?? "unavailable"}`, `Final scientific artifacts: ${result.scientificArtifactsWritten ? "written" : "not written"}`, `Output: ${outputRoot}`, ...result.errors.map(e => `Failure: ${e}`)].join("\n"));
353
+ console.log(progressLines.join("\n"));
354
+ return !progressFailed && ["completed", "already-complete", "partial"].includes(result.status) ? 0 : 1;
206
355
  }
207
356
  export function parseRunExperimentArgs(argv) {
357
+ let agentCampaignMode;
358
+ let oneCase = false, authorize = false;
359
+ let campaignSourceRoot, limitCase, limitTreatment, limitEpoch, timeoutPreset;
360
+ let expectedRevision;
208
361
  let experimentId = "";
209
362
  let targetPath;
210
363
  let outDir;
@@ -225,13 +378,51 @@ export function parseRunExperimentArgs(argv) {
225
378
  let contextBudgets;
226
379
  let syntheticConfigPath;
227
380
  let localSubjectConfigPath;
381
+ const agentFlagValues = [];
382
+ let repairAttempts;
228
383
  const seenFlags = [];
229
384
  const commandTemplates = {};
385
+ // The flag whitelist runs before any value is interpreted so an unsupported flag is reported as such.
386
+ const preScanIndex = argv.indexOf("--experiment");
387
+ if (preScanIndex >= 0 && argv[preScanIndex + 1] === agentSuccessRatePlugin.metadata.id)
388
+ assertAgentSuccessRateFlags(argv);
230
389
  for (let index = 0; index < argv.length; index += 1) {
231
390
  const arg = argv[index];
391
+ if (arg !== undefined && arg.startsWith("--") && seenFlags.includes(arg) && (AGENT_CAMPAIGN_FLAGS.includes(arg) || arg === "--timeout-preset" || (argv.includes("agent-success-rate") && arg !== "--case" && arg !== "--benchmark-project")))
392
+ throw new Error(`${arg} may be supplied only once.`);
232
393
  if (arg !== undefined && arg.startsWith("--"))
233
394
  seenFlags.push(arg);
234
- if (arg === "--experiment") {
395
+ if (arg === "--agent-campaign") {
396
+ agentCampaignMode = readRequiredValue(argv, ++index, arg);
397
+ }
398
+ else if (arg === "--one-case") {
399
+ oneCase = true;
400
+ }
401
+ else if (arg === "--authorize-additional-invocation") {
402
+ authorize = true;
403
+ }
404
+ else if (arg === "--campaign-source-root") {
405
+ campaignSourceRoot = readRequiredValue(argv, ++index, arg);
406
+ }
407
+ else if (arg === "--expected-checkpoint-revision") {
408
+ const value = readRequiredValue(argv, ++index, arg);
409
+ if (!/^[1-9]\d*$/.test(value))
410
+ throw new Error("--expected-checkpoint-revision must be a positive integer.");
411
+ expectedRevision = parsePositiveInteger(arg, value);
412
+ }
413
+ else if (arg === "--limit-case") {
414
+ limitCase = readRequiredValue(argv, ++index, arg);
415
+ }
416
+ else if (arg === "--limit-treatment") {
417
+ limitTreatment = readRequiredValue(argv, ++index, arg);
418
+ }
419
+ else if (arg === "--limit-epoch") {
420
+ limitEpoch = readRequiredValue(argv, ++index, arg);
421
+ }
422
+ else if (arg === "--timeout-preset") {
423
+ timeoutPreset = readRequiredValue(argv, ++index, arg);
424
+ }
425
+ else if (arg === "--experiment") {
235
426
  experimentId = readRequiredValue(argv, ++index, "--experiment");
236
427
  }
237
428
  else if (arg === "--target") {
@@ -264,6 +455,12 @@ export function parseRunExperimentArgs(argv) {
264
455
  else if (arg === "--benchmark-project") {
265
456
  benchmarkProjects.push(...splitList(readRequiredValue(argv, ++index, "--benchmark-project")));
266
457
  }
458
+ else if (arg === "--agent") {
459
+ agentFlagValues.push(readRequiredValue(argv, ++index, "--agent"));
460
+ }
461
+ else if (arg === "--repair-attempts") {
462
+ repairAttempts = parseRepairAttempts(readRequiredValue(argv, ++index, "--repair-attempts"));
463
+ }
267
464
  else if (arg === "--agents") {
268
465
  agents = splitList(readRequiredValue(argv, ++index, "--agents")).map((value) => parseAgentId(value));
269
466
  }
@@ -317,6 +514,31 @@ export function parseRunExperimentArgs(argv) {
317
514
  if (!experimentId) {
318
515
  throw new Error("Usage: --experiment <id> [--target <path>] [--out <directory>]");
319
516
  }
517
+ const campaignFlagsPresent = seenFlags.some(f => AGENT_CAMPAIGN_FLAGS.includes(f));
518
+ if ((campaignFlagsPresent || timeoutPreset !== undefined) && experimentId !== agentSuccessRatePlugin.metadata.id)
519
+ throw new Error("Agent campaign and timeout preset flags are only supported for --experiment agent-success-rate.");
520
+ if (campaignFlagsPresent && agentCampaignMode === undefined)
521
+ throw new Error("Campaign flags require --agent-campaign <new|resume|continue-limit>.");
522
+ if (agentCampaignMode !== undefined && !["new", "resume", "continue-limit"].includes(agentCampaignMode))
523
+ throw new Error("--agent-campaign must be new, resume or continue-limit.");
524
+ if (agentCampaignMode !== undefined && !outDir?.trim())
525
+ throw new Error("--agent-campaign requires a stable --out directory.");
526
+ const limitPresent = expectedRevision !== undefined || limitCase !== undefined || limitTreatment !== undefined || limitEpoch !== undefined || authorize;
527
+ if (agentCampaignMode !== "continue-limit" && limitPresent)
528
+ throw new Error("Limit authorization flags require --agent-campaign continue-limit.");
529
+ if (agentCampaignMode === "continue-limit") {
530
+ if (expectedRevision === undefined || !limitCase || !limitTreatment || !limitEpoch || !authorize)
531
+ throw new Error("continue-limit requires --expected-checkpoint-revision, --limit-case, --limit-treatment, --limit-epoch and --authorize-additional-invocation.");
532
+ if (expectedRevision > 1000000 || !campaignIdentifier(limitCase) || !["raw-full-file", "context-pack"].includes(limitTreatment) || !/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(limitEpoch))
533
+ throw new Error("Invalid limit revision, case, treatment or epoch identifier.");
534
+ }
535
+ if (timeoutPreset !== undefined) {
536
+ if (timeoutMs !== undefined)
537
+ throw new Error("--timeout-preset and --timeout-ms cannot be combined.");
538
+ if (agentFlagValues.length !== 1 || includeRealAgents !== true || !["codex", "claude"].includes(agentFlagValues[0]))
539
+ throw new Error("--timeout-preset requires --agent <codex|claude> and --include-real-agents.");
540
+ timeoutMs = resolveAgentTimeoutPreset(agentFlagValues[0], timeoutPreset);
541
+ }
320
542
  const KIT_COMMAND_PLUGIN_IDS = [
321
543
  warmIndexReusePlugin.metadata.id,
322
544
  incrementalChangeStalenessPlugin.metadata.id,
@@ -399,6 +621,49 @@ export function parseRunExperimentArgs(argv) {
399
621
  throw new Error("--benchmark-project must list at least one benchmark project id.");
400
622
  }
401
623
  }
624
+ if (experimentId === agentSuccessRatePlugin.metadata.id) {
625
+ // Mode matrix: no flags = deterministic-fixture (default); --agent together with --include-real-agents = real-agent mode.
626
+ // Everything else is rejected, never coerced: a provider needs both flags, and real-agent options need a provider.
627
+ if (agentFlagValues.length > 1) {
628
+ throw new Error("--agent may be supplied only once; agent-success-rate runs exactly one provider.");
629
+ }
630
+ const agentId = agentFlagValues[0];
631
+ if (agentId !== undefined && !AGENT_SUCCESS_RATE_REAL_AGENT_IDS.includes(agentId)) {
632
+ throw new Error(`--agent must be exactly one of: ${AGENT_SUCCESS_RATE_REAL_AGENT_IDS.join(", ")}.`);
633
+ }
634
+ if (agentId !== undefined && includeRealAgents !== true) {
635
+ throw new Error("--agent requires --include-real-agents; real providers are invoked only with explicit authorization.");
636
+ }
637
+ if (agentId === undefined && includeRealAgents === true) {
638
+ throw new Error("--include-real-agents requires --agent <codex|claude> for agent-success-rate.");
639
+ }
640
+ if (agentId === undefined && timeoutMs !== undefined) {
641
+ throw new Error("--timeout-ms is only supported together with --agent and --include-real-agents (real-agent mode).");
642
+ }
643
+ if (agentId === undefined && kitCommand !== undefined) {
644
+ throw new Error("--kit-command is only supported together with --agent and --include-real-agents (real-agent mode).");
645
+ }
646
+ if (agentId === undefined && repairAttempts !== undefined && repairAttempts > 0) {
647
+ throw new Error("--repair-attempts greater than 0 requires --agent and --include-real-agents (real-agent mode).");
648
+ }
649
+ if (timeoutMs !== undefined && timeoutMs > AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS) {
650
+ throw new Error(`--timeout-ms must be a positive integer no greater than ${AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS}.`);
651
+ }
652
+ if (seenFlags.includes("--case") && caseIds.length === 0) {
653
+ throw new Error("--case must list at least one case id.");
654
+ }
655
+ if (seenFlags.includes("--benchmark-project") && benchmarkProjects.length === 0) {
656
+ throw new Error("--benchmark-project must list at least one benchmark project id.");
657
+ }
658
+ }
659
+ else {
660
+ if (agentFlagValues.length > 0) {
661
+ throw new Error(`--agent is only supported for --experiment ${agentSuccessRatePlugin.metadata.id}.`);
662
+ }
663
+ if (repairAttempts !== undefined) {
664
+ throw new Error(`--repair-attempts is only supported for --experiment ${agentSuccessRatePlugin.metadata.id}.`);
665
+ }
666
+ }
402
667
  if (contextBudgets !== undefined && experimentId !== contextWindowScalingPlugin.metadata.id) {
403
668
  throw new Error(`--context-budgets is only supported for --experiment ${contextWindowScalingPlugin.metadata.id}.`);
404
669
  }
@@ -441,7 +706,8 @@ export function parseRunExperimentArgs(argv) {
441
706
  throw new Error(`External ${experimentId} targets require --local-subject-config.`);
442
707
  }
443
708
  }
444
- if (kitCommand !== undefined && !KIT_COMMAND_PLUGIN_IDS.includes(experimentId)) {
709
+ // agent-success-rate accepts --kit-command in real-agent mode; its own validation above owns that rule and message.
710
+ if (kitCommand !== undefined && experimentId !== agentSuccessRatePlugin.metadata.id && !KIT_COMMAND_PLUGIN_IDS.includes(experimentId)) {
445
711
  throw new Error(`--kit-command is only supported for --experiment ${KIT_COMMAND_PLUGIN_IDS.join(" or ")}.`);
446
712
  }
447
713
  if (campaignPreset !== undefined) {
@@ -460,6 +726,7 @@ export function parseRunExperimentArgs(argv) {
460
726
  }
461
727
  return {
462
728
  experimentId,
729
+ ...(agentCampaignMode ? { agentCampaign: { mode: agentCampaignMode, oneCase, sourceRoot: campaignSourceRoot, expectedRevision, limitCase, limitTreatment: limitTreatment, limitEpoch, authorize } } : {}),
463
730
  targetPath,
464
731
  outDir,
465
732
  syntheticConfigPath,
@@ -478,6 +745,8 @@ export function parseRunExperimentArgs(argv) {
478
745
  requireAgents,
479
746
  includeRealAgents,
480
747
  commandTemplates: Object.keys(commandTemplates).length > 0 ? commandTemplates : undefined,
748
+ agentId: agentFlagValues[0],
749
+ repairAttempts,
481
750
  kitCommand,
482
751
  campaignPreset,
483
752
  contextBudgets
@@ -542,8 +811,27 @@ async function loadPluginInputs(args, toolRoot, context, outputRoot) {
542
811
  ? loadRetrievalQueryStrategyComparisonLocalSubjectInputs(args, toolRoot, context, outputRoot)
543
812
  : loadRetrievalQueryStrategyComparisonInputs(args, toolRoot, context);
544
813
  }
814
+ if (args.experimentId === agentSuccessRatePlugin.metadata.id) {
815
+ return loadAgentSuccessRateInputs(args, context);
816
+ }
545
817
  return undefined;
546
818
  }
819
+ // agent-success-rate: the bundled task catalog and project profiles are located through the established package-resource
820
+ // resolver (never the cwd) and validated by the corpus reader, which also checks the canonical projects on disk. The
821
+ // validated tasks reach the plugin through its existing programmatic input; the plugin applies --case/--benchmark-project
822
+ // selection in catalog order and owns execution, repair, science and artifacts. No task file is accepted from the CLI.
823
+ async function loadAgentSuccessRateInputs(args, context) {
824
+ const validation = agentSuccessRatePlugin.validateConfig(args.config);
825
+ if (!validation.valid || !validation.config) {
826
+ throw new Error(`Invalid agent success rate config: ${validation.errors.join("; ")}`);
827
+ }
828
+ resolvePackageResource(context, AGENT_SUCCESS_TASK_CATALOG_PATH);
829
+ resolvePackageResource(context, AGENT_SUCCESS_PROJECT_PROFILES_PATH);
830
+ const corpus = assertReadAgentSuccessCorpus(context.resourceRoot);
831
+ // Fail on unknown case or project ids before any output directory exists.
832
+ selectAgentSuccessTasks(corpus.tasks, validation.config);
833
+ return { agentSuccessTasks: corpus.tasks };
834
+ }
547
835
  // context-pack-generation (bundled/self): the frozen warm-index corpus through the established resource resolver, profile
548
836
  // and corpus validation. The plugin applies --case/--benchmark-project selection and owns execution, science and artifacts.
549
837
  async function loadContextPackGenerationInputs(args, toolRoot, context) {
@@ -815,6 +1103,12 @@ function readRequiredValue(argv, index, label) {
815
1103
  }
816
1104
  return value;
817
1105
  }
1106
+ function parseRepairAttempts(value) {
1107
+ if (!/^[0-2]$/.test(value)) {
1108
+ throw new Error("--repair-attempts must be 0, 1 or 2.");
1109
+ }
1110
+ return Number(value);
1111
+ }
818
1112
  function parsePositiveInteger(label, value) {
819
1113
  const parsed = Number(value);
820
1114
  if (!Number.isInteger(parsed) || parsed <= 0) {
@@ -822,6 +1116,15 @@ function parsePositiveInteger(label, value) {
822
1116
  }
823
1117
  return parsed;
824
1118
  }
1119
+ function agentSuccessRateSummaryLines(result, textPath) {
1120
+ const mode = readMetadataString(result.metadata?.executionMode) ?? "deterministic-fixture";
1121
+ const provider = readMetadataString(result.metadata?.providerId);
1122
+ return [
1123
+ `Report Text: ${textPath}`,
1124
+ `Execution mode: ${mode}`,
1125
+ ...(provider ? [`Provider: ${provider} (invoked by explicit --include-real-agents authorization)`] : ["Provider: none (deterministic-fixture; no coding agent was invoked)"])
1126
+ ];
1127
+ }
825
1128
  function readMetadataString(value) {
826
1129
  return typeof value === "string" && value.trim() ? value : undefined;
827
1130
  }
@@ -20,6 +20,61 @@ export function requestProcessTermination(pid) {
20
20
  // The process may have exited between the liveness check and this signal.
21
21
  }
22
22
  }
23
+ /**
24
+ * Opt-in, awaitable descendant-tree termination for bounded execution
25
+ * (see `terminateProcessTree` in `runMeasuredCommand`). The legacy
26
+ * `forceTerminateProcess` above is intentionally unchanged: it stays
27
+ * fire-and-forget and, off Windows, signals one pid only.
28
+ *
29
+ * Windows: `taskkill /pid <pid> /T /F` (shell:false) is awaited, bounded by
30
+ * `waitTimeoutMs`, so the caller knows the tree was asked to die before it
31
+ * proceeds.
32
+ *
33
+ * Other platforms: the child must have been spawned `detached` so it leads its
34
+ * own process group; the whole group is signalled via the negative pid. If no
35
+ * group exists, the single pid is signalled as a fallback.
36
+ *
37
+ * Never throws: the target may already have exited.
38
+ */
39
+ export async function terminateProcessTree(pid, options = {}) {
40
+ if (!pid) {
41
+ return;
42
+ }
43
+ const platform = options.platform ?? process.platform;
44
+ if (platform === "win32") {
45
+ await new Promise((resolve) => {
46
+ let timer;
47
+ const finish = () => {
48
+ if (timer)
49
+ clearTimeout(timer);
50
+ resolve();
51
+ };
52
+ const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
53
+ shell: false,
54
+ stdio: "ignore",
55
+ windowsHide: true
56
+ });
57
+ timer = setTimeout(finish, options.waitTimeoutMs ?? 5000);
58
+ killer.on("error", finish);
59
+ killer.on("close", finish);
60
+ });
61
+ return;
62
+ }
63
+ const signal = options.posixSignal ?? "SIGKILL";
64
+ try {
65
+ process.kill(-pid, signal);
66
+ return;
67
+ }
68
+ catch {
69
+ // No process group for this pid (or it already exited); fall back to the single pid.
70
+ }
71
+ try {
72
+ process.kill(pid, signal);
73
+ }
74
+ catch {
75
+ // The process may have exited between the liveness check and this signal.
76
+ }
77
+ }
23
78
  /**
24
79
  * Forced cleanup for a process that did not stop on request.
25
80
  *