@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,67 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { INVALID_PROJECT, createTaskBoard } from "../src/index.js";
4
+
5
+ test("a project's completed count never exceeds its total", () => {
6
+ const { service, summarize } = createTaskBoard();
7
+ service.createTask({ title: "Solo", projectId: "alpha" });
8
+ for (const title of ["x", "y", "z"]) {
9
+ const task = service.createTask({ title, projectId: "beta" });
10
+ service.completeTask(task.id);
11
+ }
12
+ const summary = summarize("alpha");
13
+ assert.equal(summary.total, 1);
14
+ assert.equal(summary.completed, 0);
15
+ assert.ok(summary.completed <= summary.total);
16
+ assert.equal(summary.open, 1);
17
+ });
18
+
19
+ test("unrelated tasks in the store do not change a project's summary", () => {
20
+ const { service, summarize } = createTaskBoard();
21
+ const a = service.createTask({ title: "A", projectId: "core" });
22
+ service.createTask({ title: "B", projectId: "core" });
23
+ service.completeTask(a.id);
24
+ const before = summarize("core");
25
+ const noise = service.createTask({ title: "Noise", projectId: "core-extras" });
26
+ service.createTask({ title: "More noise", projectId: "CORE2" });
27
+ service.completeTask(noise.id);
28
+ service.importTasks([{ externalId: "n-1", title: "Imported noise", projectId: "c" }]);
29
+ assert.deepEqual(summarize("core"), before);
30
+ });
31
+
32
+ test("an unknown project has an empty, consistent summary", () => {
33
+ const { service, summarize } = createTaskBoard();
34
+ service.createTask({ title: "A", projectId: "known" });
35
+ assert.deepEqual(summarize("missing"), { projectId: "missing", total: 0, completed: 0, open: 0, taskIds: [] });
36
+ });
37
+
38
+ test("blank project identities are rejected consistently", () => {
39
+ const { service, store, summarize } = createTaskBoard();
40
+ assert.throws(() => service.createTask({ title: "Task", projectId: " " }), { code: INVALID_PROJECT });
41
+ assert.throws(() => service.createTask({ title: "Task", projectId: "" }), { code: INVALID_PROJECT });
42
+ assert.throws(
43
+ () => service.importTasks([{ externalId: "ext-1", title: "Task", projectId: " " }]),
44
+ { code: INVALID_PROJECT }
45
+ );
46
+ assert.throws(() => summarize(" "), { code: INVALID_PROJECT });
47
+ assert.equal(store.list().length, 0);
48
+ });
49
+
50
+ test("summaries are deterministic and list ids in creation order", () => {
51
+ const build = () => {
52
+ const { service, summarize } = createTaskBoard();
53
+ service.createTask({ title: "1", projectId: "p" });
54
+ service.createTask({ title: "2", projectId: "q" });
55
+ service.createTask({ title: "3", projectId: "P" });
56
+ service.importTasks([{ externalId: "e-1", title: "4", projectId: "p " }]);
57
+ return summarize("p");
58
+ };
59
+ assert.deepEqual(build(), build());
60
+ assert.deepEqual(build().taskIds, ["task-1", "task-3", "task-4"]);
61
+ });
62
+
63
+ test("the default project is used for tasks created without one", () => {
64
+ const { service, summarize } = createTaskBoard();
65
+ service.createTask({ title: "No project given" });
66
+ assert.equal(summarize("inbox").total, 1);
67
+ });
@@ -0,0 +1,54 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createTaskBoard } from "../src/index.js";
4
+
5
+ test("tasks created with differently written project names share one project identity", () => {
6
+ const { service, store, summarize } = createTaskBoard();
7
+ service.createTask({ title: "One", projectId: "web" });
8
+ service.createTask({ title: "Two", projectId: " WEB " });
9
+ service.createTask({ title: "Three", projectId: "Web" });
10
+ assert.deepEqual(
11
+ store.list().map((task) => task.projectId),
12
+ ["web", "web", "web"]
13
+ );
14
+ const summary = summarize("web");
15
+ assert.equal(summary.projectId, "web");
16
+ assert.equal(summary.total, 3);
17
+ });
18
+
19
+ test("a summary only counts tasks of the requested project", () => {
20
+ const { service, summarize } = createTaskBoard();
21
+ const web = service.createTask({ title: "Web task", projectId: "web" });
22
+ service.createTask({ title: "Web app task", projectId: "web-app" });
23
+ service.createTask({ title: "Webhook task", projectId: "webhooks" });
24
+ const other = service.createTask({ title: "Docs task", projectId: "docs" });
25
+ service.completeTask(other.id);
26
+ const summary = summarize("web");
27
+ assert.equal(summary.total, 1);
28
+ assert.equal(summary.completed, 0);
29
+ assert.equal(summary.open, 1);
30
+ assert.deepEqual(summary.taskIds, [web.id]);
31
+ });
32
+
33
+ test("completed and open counts are correct for the requested project", () => {
34
+ const { service, summarize } = createTaskBoard();
35
+ const a = service.createTask({ title: "A", projectId: "ops" });
36
+ service.createTask({ title: "B", projectId: "ops" });
37
+ service.createTask({ title: "C", projectId: "ops" });
38
+ service.createTask({ title: "Elsewhere", projectId: "labs" });
39
+ service.completeTask(a.id);
40
+ assert.deepEqual(summarize(" OPS "), {
41
+ projectId: "ops",
42
+ total: 3,
43
+ completed: 1,
44
+ open: 2,
45
+ taskIds: ["task-1", "task-2", "task-3"]
46
+ });
47
+ });
48
+
49
+ test("imported tasks join the same normalized project identity", () => {
50
+ const { service, summarize } = createTaskBoard();
51
+ service.createTask({ title: "Manual", projectId: "ops" });
52
+ service.importTasks([{ externalId: "ext-1", title: "Imported", projectId: " OPS" }]);
53
+ assert.equal(summarize("ops").total, 2);
54
+ });
@@ -0,0 +1,39 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { INVALID_TITLE, ValidationError, createTaskBoard, validateTitle } from "../src/index.js";
4
+
5
+ test("rejected titles leave the store and id sequence untouched", () => {
6
+ const { service, store } = createTaskBoard();
7
+ assert.throws(() => service.createTask({ title: " \t " }), { code: INVALID_TITLE });
8
+ assert.throws(() => service.createTask({ title: "" }), { code: INVALID_TITLE });
9
+ assert.equal(store.list().length, 0);
10
+ const task = service.createTask({ title: "First" });
11
+ assert.equal(task.id, "task-1");
12
+ assert.equal(store.list().length, 1);
13
+ });
14
+
15
+ test("interior whitespace is preserved while surrounding whitespace is removed", () => {
16
+ const { service } = createTaskBoard();
17
+ assert.equal(service.createTask({ title: " Fix the bug " }).title, "Fix the bug");
18
+ assert.equal(service.createTask({ title: "\n\tPlan sprint\t\n" }).title, "Plan sprint");
19
+ });
20
+
21
+ test("already-trimmed titles keep their meaning", () => {
22
+ const { service } = createTaskBoard();
23
+ assert.equal(service.createTask({ title: "Ship it" }).title, "Ship it");
24
+ assert.equal(service.createTask({ title: "a" }).title, "a");
25
+ });
26
+
27
+ test("validateTitle returns the normalized title and throws the documented error", () => {
28
+ assert.equal(validateTitle(" Triage "), "Triage");
29
+ assert.throws(
30
+ () => validateTitle(" "),
31
+ (error) => error instanceof ValidationError && error.code === INVALID_TITLE
32
+ );
33
+ });
34
+
35
+ test("imported task titles are normalized the same way", () => {
36
+ const { service } = createTaskBoard();
37
+ const { imported } = service.importTasks([{ externalId: "ext-1", title: " Imported item " }]);
38
+ assert.equal(imported[0].title, "Imported item");
39
+ });
@@ -0,0 +1,29 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { INVALID_TITLE, ValidationError, createTaskBoard } from "../src/index.js";
4
+
5
+ test("createTask trims surrounding whitespace from the title", () => {
6
+ const { service } = createTaskBoard();
7
+ const task = service.createTask({ title: " Write release notes " });
8
+ assert.equal(task.title, "Write release notes");
9
+ });
10
+
11
+ test("the stored task retains the normalized title", () => {
12
+ const { service, store } = createTaskBoard();
13
+ const task = service.createTask({ title: "\tReview backlog " });
14
+ assert.equal(store.get(task.id).title, "Review backlog");
15
+ assert.deepEqual(
16
+ store.list().map((entry) => entry.title),
17
+ ["Review backlog"]
18
+ );
19
+ });
20
+
21
+ test("a whitespace-only title is rejected with the invalid-title error", () => {
22
+ const { service } = createTaskBoard();
23
+ for (const title of [" ", "\t", "\n \r\n"]) {
24
+ assert.throws(
25
+ () => service.createTask({ title }),
26
+ (error) => error instanceof ValidationError && error.code === INVALID_TITLE
27
+ );
28
+ }
29
+ });
@@ -156,6 +156,10 @@ export function renderExperimentRunHelp() {
156
156
  " --case <ids> Comma-separated case ids to run",
157
157
  " --benchmark-project <ids> Comma-separated benchmark project ids to run",
158
158
  "",
159
+ "Options are accepted per plugin: every plugin rejects the options it does not support instead of ignoring them. The",
160
+ "generic controlled-experiment options (such as --agents, --strategies, --complexities, --max-runs and the command",
161
+ "templates) belong to context-strategy-comparison and warm-index-reuse only.",
162
+ "",
159
163
  "my-dev-kit command override (warm-index-reuse, incremental-change-staleness, context-window-scaling, retrieval-precision-recall, retrieval-query-strategy-comparison, and context-pack-generation):",
160
164
  " --kit-command <command> my-dev-kit command used by the selected experiment plugin",
161
165
  " (defaults: warm-index-reuse and context-window-scaling use",
@@ -273,7 +277,40 @@ export function renderExperimentRunHelp() {
273
277
  " --command-template-codex <template> Command template used to invoke the codex agent",
274
278
  " --command-template-claude <template> Command template used to invoke the claude agent",
275
279
  " --no-screenshot Accepted for compatibility; the plugin-aware report",
276
- " path does not capture screenshots yet"
280
+ " path does not capture screenshots yet",
281
+ "",
282
+ "agent-success-rate only:",
283
+ " Self-target only (the Lab itself); --target is rejected. Runs the",
284
+ " bundled six-task implementation corpus (two canonical benchmark",
285
+ " projects) with both source-context treatments, raw-full-file and",
286
+ " context-pack, for every selected task. The default is",
287
+ " deterministic-fixture mode, which validates the patch evaluation",
288
+ " pipeline, invokes no coding agent and needs no provider installed.",
289
+ " Accepted options: --out, --case, --benchmark-project,",
290
+ " --kit-command, --agent, --include-real-agents, --timeout-ms and",
291
+ " --repair-attempts; --case and --benchmark-project narrow the",
292
+ " catalog without reordering it.",
293
+ " --agent <codex|claude> Select exactly one real provider. Real-agent mode is entered only",
294
+ " when --agent is combined with --include-real-agents; neither flag",
295
+ " works alone. The provider receives source-only context and returns",
296
+ " a unified diff; it never edits files. The Lab applies the diff in a",
297
+ " disposable sandbox with a guarded patch policy and decides task",
298
+ " success from trusted checks on the changed state.",
299
+ " --include-real-agents Explicit authorization to invoke the selected provider. Without it",
300
+ " no provider is ever launched.",
301
+ " --timeout-ms <ms> Per provider attempt (real-agent mode only): a positive integer,",
302
+ " default 240000, maximum 1800000.",
303
+ " --repair-attempts <0|1|2> Repairs allowed after the initial attempt (real-agent mode only;",
304
+ " default 0). At most 3 attempts per treatment. A repair runs only",
305
+ " after an evaluable implementation failure, in a fresh sandbox,",
306
+ " with fixed-category feedback and the untrusted previous diff; it",
307
+ " never follows a provider, sandbox or verification infrastructure",
308
+ " problem.",
309
+ " Writes report.json, report.html and report.txt plus",
310
+ " agent-success-rate-execution.json and",
311
+ " agent-success-rate-analysis.json. Initial-attempt and final success",
312
+ " are reported separately. Results are descriptive for the finite",
313
+ " corpus: no winner, ranking, composite score or significance claim."
277
314
  ].join("\n");
278
315
  }
279
316
  export function renderExperimentControlledHelp() {
@@ -1,4 +1,5 @@
1
1
  import { createDefaultExperimentPluginRegistry } from "../experiments/index.js";
2
+ import { AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS, AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS, AGENT_SUCCESS_RATE_REAL_AGENT_IDS, AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES, AGENT_SUCCESS_RATE_TREATMENT_IDS } from "../experiments/plugins/agentSuccessRate/index.js";
2
3
  // ---------------------------------------------------------------------------
3
4
  // v0.4.6 Batch 4 -- reusable experiment-describe command owner.
4
5
  //
@@ -57,7 +58,27 @@ function buildDescription(plugin) {
57
58
  optionalConfigFields: readConfigFields(plugin, false),
58
59
  targetBehavior: describeTargetBehavior(plugin),
59
60
  expectedReports: describeExpectedReports(plugin),
60
- examples: describeExamples(plugin)
61
+ examples: describeExamples(plugin),
62
+ ...(plugin.metadata.id === AGENT_SUCCESS_RATE_ID ? { agentSuccessRate: describeAgentSuccessRate() } : {})
63
+ };
64
+ }
65
+ const AGENT_SUCCESS_RATE_ID = "agent-success-rate";
66
+ /** Fixed, public description of the implemented behavior. It names no task check, fact, edit scope or reference patch. */
67
+ function describeAgentSuccessRate() {
68
+ return {
69
+ treatments: [...AGENT_SUCCESS_RATE_TREATMENT_IDS],
70
+ corpus: "Bundled six-task implementation corpus over two canonical benchmark projects (localized, cross-module and broad-change tasks), resolved from the installed package.",
71
+ defaultMode: "deterministic-fixture: validates the patch evaluation pipeline and the benchmark corpus; no coding agent runs and no provider is required.",
72
+ realAgentMode: "Opt-in only: --agent <codex|claude> together with --include-real-agents. Neither flag alone enables it; real execution is never the default.",
73
+ providers: [...AGENT_SUCCESS_RATE_REAL_AGENT_IDS],
74
+ timeoutMs: { default: AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS, maximum: AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS, scope: "each provider attempt" },
75
+ repairAttempts: { allowed: [...AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES], default: 0, maximumTotalAttemptsPerTreatment: 3, mode: "real-agent only" },
76
+ agentContext: "Source-only: the provider sees the public task text and the treatment's source context, never tests, trusted checks or reference patches, and runs in a neutral working directory.",
77
+ patchApplication: "Guarded: the provider returns a unified diff only. The Lab validates it against a patch policy and applies it in a fresh disposable sandbox; canonical benchmark projects are never modified.",
78
+ taskSuccess: "Decided by trusted checks on the actual changed repository state (required behavior, no regressions, protected files intact). A provider's claim of completion is never evidence. Initial-attempt success and final success are reported separately.",
79
+ reportOutputs: ["report.json", "report.html", "report.txt", "agent-success-rate-execution.json", "agent-success-rate-analysis.json"],
80
+ targetPolicy: "Self-target only; external-local targets are rejected.",
81
+ interpretation: "Descriptive matched comparison of two source-context treatments on a finite corpus: no winner, ranking, composite score, causal or statistical-significance claim."
61
82
  };
62
83
  }
63
84
  function printDescription(plugin) {
@@ -84,6 +105,24 @@ function printDescription(plugin) {
84
105
  for (const example of description.examples) {
85
106
  console.log(` ${example}`);
86
107
  }
108
+ const agentSuccessRate = description.agentSuccessRate; // eslint-disable-line @typescript-eslint/no-explicit-any
109
+ if (agentSuccessRate) {
110
+ console.log("");
111
+ console.log("Agent success rate details:");
112
+ console.log(` Treatments: ${agentSuccessRate.treatments.join(", ")}`);
113
+ console.log(` Corpus: ${agentSuccessRate.corpus}`);
114
+ console.log(` Default mode: ${agentSuccessRate.defaultMode}`);
115
+ console.log(` Real-agent mode: ${agentSuccessRate.realAgentMode}`);
116
+ console.log(` Providers: ${agentSuccessRate.providers.join(", ")}`);
117
+ console.log(` Timeout per attempt: default ${agentSuccessRate.timeoutMs.default} ms, maximum ${agentSuccessRate.timeoutMs.maximum} ms`);
118
+ console.log(` Repair attempts: ${agentSuccessRate.repairAttempts.allowed.join(", ")} (default ${agentSuccessRate.repairAttempts.default}; at most ${agentSuccessRate.repairAttempts.maximumTotalAttemptsPerTreatment} attempts per treatment; real-agent mode only)`);
119
+ console.log(` Agent context: ${agentSuccessRate.agentContext}`);
120
+ console.log(` Patch application: ${agentSuccessRate.patchApplication}`);
121
+ console.log(` Task success: ${agentSuccessRate.taskSuccess}`);
122
+ console.log(` Report outputs: ${agentSuccessRate.reportOutputs.join(", ")}`);
123
+ console.log(` Target policy: ${agentSuccessRate.targetPolicy}`);
124
+ console.log(` Interpretation: ${agentSuccessRate.interpretation}`);
125
+ }
87
126
  }
88
127
  function printConfigSection(title, fields) {
89
128
  console.log(`${title}:`);
@@ -123,12 +162,18 @@ function readArrayField(value, key) {
123
162
  return Array.isArray(field) ? field : [];
124
163
  }
125
164
  function describePurpose(plugin) {
165
+ if (plugin.metadata.id === AGENT_SUCCESS_RATE_ID) {
166
+ return "Measure whether implementation patches resolve benchmark tasks under two source-context treatments (raw-full-file and context-pack) using trusted checks on disposable benchmark copies. Deterministic-fixture mode (default) validates the evaluation pipeline only; real-agent mode (opt-in) evaluates the patches a selected provider returns, with optional bounded repair.";
167
+ }
126
168
  if (plugin.metadata.id === "context-strategy-comparison") {
127
169
  return "Compare raw full-file prompts with my-dev-kit-guided retrieval prompts using the existing controlled experiment workflow.";
128
170
  }
129
171
  return plugin.metadata.description;
130
172
  }
131
173
  function describeTargetBehavior(plugin) {
174
+ if (plugin.metadata.id === AGENT_SUCCESS_RATE_ID) {
175
+ return "Self-target only: the bundled corpus is evaluated against the Lab itself and an explicit --target is rejected. Sandboxes and artifacts are written beneath the output directory (beneath the workspace when installed), never into the installed package or the canonical benchmark projects. Accepted options: --out, --case, --benchmark-project, --kit-command, --agent, --include-real-agents, --timeout-ms and --repair-attempts; every other option is rejected.";
176
+ }
132
177
  const supportsExternal = plugin.metadata.supportedTargets.includes("external-local");
133
178
  if (plugin.metadata.id === "context-window-scaling") {
134
179
  return "Three subject modes. Bundled (default): the bundled fixed scaling corpus against the Lab itself. Synthetic: caller-supplied deterministic synthetic repositories via --synthetic-config <path> (a SyntheticRepositoryConfigV1 JSON file, mutually exclusive with --case) written beneath the experiment output directory. External local repository: an explicitly selected local Git worktree via --target <path> together with --local-subject-config <path> (a LocalRepositorySubjectConfigV1 JSON file); the repository is never modified, the output directory must be outside it, and durable output omits the repository path, source text, and file names. Operational context-fit measurement only; no retrieval precision, recall, or ranking quality is measured.";
@@ -151,6 +196,9 @@ function describeTargetBehavior(plugin) {
151
196
  return "Runs against the current lab repository.";
152
197
  }
153
198
  function describeExpectedReports(plugin) {
199
+ if (plugin.metadata.id === AGENT_SUCCESS_RATE_ID) {
200
+ return "Writes plugin-aware report.json, report.html and report.txt with a typed agent-success-rate section (initial-attempt and final success, repair history, edit quality, provider duration and tokens, scientific limitations), plus the separate agent-success-rate-execution.json and agent-success-rate-analysis.json artifacts.";
201
+ }
154
202
  if (plugin.metadata.supportedOutputs.includes("html") && plugin.metadata.supportedOutputs.includes("json")) {
155
203
  return "Writes plugin-aware JSON and HTML reports with plugin, target, variant, case, metric, artifact, warning, skip, and failure metadata.";
156
204
  }
@@ -185,6 +233,9 @@ function describeExamples(plugin) {
185
233
  if (plugin.metadata.id === "context-pack-generation") {
186
234
  examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --case <case-id> --out <run-dir>`, `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target <local-git-repository> --local-subject-config <path-to-local-subject-config.json> --out <run-dir-outside-the-repository>`);
187
235
  }
236
+ if (plugin.metadata.id === AGENT_SUCCESS_RATE_ID) {
237
+ examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --case <case-id> --out <run-dir>`, `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --agent codex --include-real-agents --repair-attempts 2 --out <run-dir>`);
238
+ }
188
239
  if (plugin.metadata.id === "context-window-scaling") {
189
240
  examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --synthetic-config <path-to-config.json> --out <run-dir>`, `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target <local-git-repository> --local-subject-config <path-to-local-subject-config.json> --out <run-dir-outside-the-repository>`);
190
241
  }
@@ -3,10 +3,12 @@ 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";
@@ -71,6 +73,27 @@ const RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXTERNAL_ALLOWED_FLAGS = ["--experimen
71
73
  const CONTEXT_PACK_GENERATION_CASES_RESOURCE = "benchmarks/contracts/warm-index-benchmark-cases.json";
72
74
  const CONTEXT_PACK_GENERATION_ALLOWED_FLAGS = ["--experiment", "--out", "--case", "--benchmark-project", "--kit-command"];
73
75
  const CONTEXT_PACK_GENERATION_EXTERNAL_ALLOWED_FLAGS = ["--experiment", "--out", "--target", "--local-subject-config", "--kit-command"];
76
+ // agent-success-rate (v0.9.0) owns the bundled six-task implementation corpus (task catalog and project profiles), both
77
+ // resolved from the package root, never the cwd. It runs against the Lab itself only and accepts exactly these options;
78
+ // every other flag is rejected, not ignored. Real providers run only with --agent together with --include-real-agents.
79
+ const AGENT_SUCCESS_RATE_ALLOWED_FLAGS = [
80
+ "--experiment",
81
+ "--out",
82
+ "--case",
83
+ "--benchmark-project",
84
+ "--kit-command",
85
+ "--agent",
86
+ "--include-real-agents",
87
+ "--timeout-ms",
88
+ "--repair-attempts"
89
+ ];
90
+ export function assertAgentSuccessRateFlags(argv) {
91
+ const flags = argv.filter((arg) => arg.startsWith("--"));
92
+ const unsupported = [...new Set(flags.filter((flag) => !AGENT_SUCCESS_RATE_ALLOWED_FLAGS.includes(flag)))];
93
+ if (unsupported.length > 0) {
94
+ 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(", ")}.`);
95
+ }
96
+ }
74
97
  export async function runExperimentRunCommandFromArgs(argv, options = {}) {
75
98
  try {
76
99
  const args = parseRunExperimentArgs(argv);
@@ -151,7 +174,8 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
151
174
  `Target root: ${result.target.targetRoot}`,
152
175
  `Output: ${String(result.metadata?.outputRoot ?? "")}`,
153
176
  `Report JSON: ${reports.outputPaths.jsonPath}`,
154
- `Report HTML: ${reports.outputPaths.htmlPath}`
177
+ `Report HTML: ${reports.outputPaths.htmlPath}`,
178
+ ...(args.experimentId === agentSuccessRatePlugin.metadata.id ? agentSuccessRateSummaryLines(result, reports.outputPaths.textPath) : [])
155
179
  ];
156
180
  // Automatic presentation (report already produced above) applies only to warm-index-reuse
157
181
  // campaign runs; legacy fake-agent warm-index runs and context-strategy-comparison stay
@@ -225,8 +249,14 @@ export function parseRunExperimentArgs(argv) {
225
249
  let contextBudgets;
226
250
  let syntheticConfigPath;
227
251
  let localSubjectConfigPath;
252
+ const agentFlagValues = [];
253
+ let repairAttempts;
228
254
  const seenFlags = [];
229
255
  const commandTemplates = {};
256
+ // The flag whitelist runs before any value is interpreted so an unsupported flag is reported as such.
257
+ const preScanIndex = argv.indexOf("--experiment");
258
+ if (preScanIndex >= 0 && argv[preScanIndex + 1] === agentSuccessRatePlugin.metadata.id)
259
+ assertAgentSuccessRateFlags(argv);
230
260
  for (let index = 0; index < argv.length; index += 1) {
231
261
  const arg = argv[index];
232
262
  if (arg !== undefined && arg.startsWith("--"))
@@ -264,6 +294,12 @@ export function parseRunExperimentArgs(argv) {
264
294
  else if (arg === "--benchmark-project") {
265
295
  benchmarkProjects.push(...splitList(readRequiredValue(argv, ++index, "--benchmark-project")));
266
296
  }
297
+ else if (arg === "--agent") {
298
+ agentFlagValues.push(readRequiredValue(argv, ++index, "--agent"));
299
+ }
300
+ else if (arg === "--repair-attempts") {
301
+ repairAttempts = parseRepairAttempts(readRequiredValue(argv, ++index, "--repair-attempts"));
302
+ }
267
303
  else if (arg === "--agents") {
268
304
  agents = splitList(readRequiredValue(argv, ++index, "--agents")).map((value) => parseAgentId(value));
269
305
  }
@@ -399,6 +435,49 @@ export function parseRunExperimentArgs(argv) {
399
435
  throw new Error("--benchmark-project must list at least one benchmark project id.");
400
436
  }
401
437
  }
438
+ if (experimentId === agentSuccessRatePlugin.metadata.id) {
439
+ // Mode matrix: no flags = deterministic-fixture (default); --agent together with --include-real-agents = real-agent mode.
440
+ // Everything else is rejected, never coerced: a provider needs both flags, and real-agent options need a provider.
441
+ if (agentFlagValues.length > 1) {
442
+ throw new Error("--agent may be supplied only once; agent-success-rate runs exactly one provider.");
443
+ }
444
+ const agentId = agentFlagValues[0];
445
+ if (agentId !== undefined && !AGENT_SUCCESS_RATE_REAL_AGENT_IDS.includes(agentId)) {
446
+ throw new Error(`--agent must be exactly one of: ${AGENT_SUCCESS_RATE_REAL_AGENT_IDS.join(", ")}.`);
447
+ }
448
+ if (agentId !== undefined && includeRealAgents !== true) {
449
+ throw new Error("--agent requires --include-real-agents; real providers are invoked only with explicit authorization.");
450
+ }
451
+ if (agentId === undefined && includeRealAgents === true) {
452
+ throw new Error("--include-real-agents requires --agent <codex|claude> for agent-success-rate.");
453
+ }
454
+ if (agentId === undefined && timeoutMs !== undefined) {
455
+ throw new Error("--timeout-ms is only supported together with --agent and --include-real-agents (real-agent mode).");
456
+ }
457
+ if (agentId === undefined && kitCommand !== undefined) {
458
+ throw new Error("--kit-command is only supported together with --agent and --include-real-agents (real-agent mode).");
459
+ }
460
+ if (agentId === undefined && repairAttempts !== undefined && repairAttempts > 0) {
461
+ throw new Error("--repair-attempts greater than 0 requires --agent and --include-real-agents (real-agent mode).");
462
+ }
463
+ if (timeoutMs !== undefined && timeoutMs > AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS) {
464
+ throw new Error(`--timeout-ms must be a positive integer no greater than ${AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS}.`);
465
+ }
466
+ if (seenFlags.includes("--case") && caseIds.length === 0) {
467
+ throw new Error("--case must list at least one case id.");
468
+ }
469
+ if (seenFlags.includes("--benchmark-project") && benchmarkProjects.length === 0) {
470
+ throw new Error("--benchmark-project must list at least one benchmark project id.");
471
+ }
472
+ }
473
+ else {
474
+ if (agentFlagValues.length > 0) {
475
+ throw new Error(`--agent is only supported for --experiment ${agentSuccessRatePlugin.metadata.id}.`);
476
+ }
477
+ if (repairAttempts !== undefined) {
478
+ throw new Error(`--repair-attempts is only supported for --experiment ${agentSuccessRatePlugin.metadata.id}.`);
479
+ }
480
+ }
402
481
  if (contextBudgets !== undefined && experimentId !== contextWindowScalingPlugin.metadata.id) {
403
482
  throw new Error(`--context-budgets is only supported for --experiment ${contextWindowScalingPlugin.metadata.id}.`);
404
483
  }
@@ -441,7 +520,8 @@ export function parseRunExperimentArgs(argv) {
441
520
  throw new Error(`External ${experimentId} targets require --local-subject-config.`);
442
521
  }
443
522
  }
444
- if (kitCommand !== undefined && !KIT_COMMAND_PLUGIN_IDS.includes(experimentId)) {
523
+ // agent-success-rate accepts --kit-command in real-agent mode; its own validation above owns that rule and message.
524
+ if (kitCommand !== undefined && experimentId !== agentSuccessRatePlugin.metadata.id && !KIT_COMMAND_PLUGIN_IDS.includes(experimentId)) {
445
525
  throw new Error(`--kit-command is only supported for --experiment ${KIT_COMMAND_PLUGIN_IDS.join(" or ")}.`);
446
526
  }
447
527
  if (campaignPreset !== undefined) {
@@ -478,6 +558,8 @@ export function parseRunExperimentArgs(argv) {
478
558
  requireAgents,
479
559
  includeRealAgents,
480
560
  commandTemplates: Object.keys(commandTemplates).length > 0 ? commandTemplates : undefined,
561
+ agentId: agentFlagValues[0],
562
+ repairAttempts,
481
563
  kitCommand,
482
564
  campaignPreset,
483
565
  contextBudgets
@@ -542,8 +624,27 @@ async function loadPluginInputs(args, toolRoot, context, outputRoot) {
542
624
  ? loadRetrievalQueryStrategyComparisonLocalSubjectInputs(args, toolRoot, context, outputRoot)
543
625
  : loadRetrievalQueryStrategyComparisonInputs(args, toolRoot, context);
544
626
  }
627
+ if (args.experimentId === agentSuccessRatePlugin.metadata.id) {
628
+ return loadAgentSuccessRateInputs(args, context);
629
+ }
545
630
  return undefined;
546
631
  }
632
+ // agent-success-rate: the bundled task catalog and project profiles are located through the established package-resource
633
+ // resolver (never the cwd) and validated by the corpus reader, which also checks the canonical projects on disk. The
634
+ // validated tasks reach the plugin through its existing programmatic input; the plugin applies --case/--benchmark-project
635
+ // selection in catalog order and owns execution, repair, science and artifacts. No task file is accepted from the CLI.
636
+ async function loadAgentSuccessRateInputs(args, context) {
637
+ const validation = agentSuccessRatePlugin.validateConfig(args.config);
638
+ if (!validation.valid || !validation.config) {
639
+ throw new Error(`Invalid agent success rate config: ${validation.errors.join("; ")}`);
640
+ }
641
+ resolvePackageResource(context, AGENT_SUCCESS_TASK_CATALOG_PATH);
642
+ resolvePackageResource(context, AGENT_SUCCESS_PROJECT_PROFILES_PATH);
643
+ const corpus = assertReadAgentSuccessCorpus(context.resourceRoot);
644
+ // Fail on unknown case or project ids before any output directory exists.
645
+ selectAgentSuccessTasks(corpus.tasks, validation.config);
646
+ return { agentSuccessTasks: corpus.tasks };
647
+ }
547
648
  // context-pack-generation (bundled/self): the frozen warm-index corpus through the established resource resolver, profile
548
649
  // and corpus validation. The plugin applies --case/--benchmark-project selection and owns execution, science and artifacts.
549
650
  async function loadContextPackGenerationInputs(args, toolRoot, context) {
@@ -815,6 +916,12 @@ function readRequiredValue(argv, index, label) {
815
916
  }
816
917
  return value;
817
918
  }
919
+ function parseRepairAttempts(value) {
920
+ if (!/^[0-2]$/.test(value)) {
921
+ throw new Error("--repair-attempts must be 0, 1 or 2.");
922
+ }
923
+ return Number(value);
924
+ }
818
925
  function parsePositiveInteger(label, value) {
819
926
  const parsed = Number(value);
820
927
  if (!Number.isInteger(parsed) || parsed <= 0) {
@@ -822,6 +929,15 @@ function parsePositiveInteger(label, value) {
822
929
  }
823
930
  return parsed;
824
931
  }
932
+ function agentSuccessRateSummaryLines(result, textPath) {
933
+ const mode = readMetadataString(result.metadata?.executionMode) ?? "deterministic-fixture";
934
+ const provider = readMetadataString(result.metadata?.providerId);
935
+ return [
936
+ `Report Text: ${textPath}`,
937
+ `Execution mode: ${mode}`,
938
+ ...(provider ? [`Provider: ${provider} (invoked by explicit --include-real-agents authorization)`] : ["Provider: none (deterministic-fixture; no coding agent was invoked)"])
939
+ ];
940
+ }
825
941
  function readMetadataString(value) {
826
942
  return typeof value === "string" && value.trim() ? value : undefined;
827
943
  }
@@ -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
  *