@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,74 @@
1
+ import { lstat, readFile, rm } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { resolveWithinRoot } from "../../core/pathSafety.js";
4
+ import { isSameOrInside, resolvePhysicalPath, SANDBOX_ID_PATTERN } from "./pathPolicy.js";
5
+ import { BENCHMARK_SANDBOX_OWNER_MARKER_FILE, BENCHMARK_SANDBOX_SCHEMA_VERSION } from "./types.js";
6
+ /**
7
+ * Removes one sandbox, and only a sandbox this subsystem created: the directory must be a real directory
8
+ * directly inside the runtime root and carry an ownership marker whose schema, sandbox id and runtime root
9
+ * match. Any mismatch is a refusal that leaves everything on disk untouched. The runtime root itself is
10
+ * never removed.
11
+ */
12
+ export async function removeBenchmarkSandbox(options) {
13
+ if (!SANDBOX_ID_PATTERN.test(options.sandboxId)) {
14
+ return { removed: false, reason: "invalid sandbox id." };
15
+ }
16
+ const runtimeRoot = path.resolve(options.runtimeRoot);
17
+ let sandboxRoot;
18
+ try {
19
+ sandboxRoot = resolveWithinRoot(runtimeRoot, options.sandboxId);
20
+ }
21
+ catch {
22
+ return { removed: false, reason: "sandbox path escapes the runtime root." };
23
+ }
24
+ let stats;
25
+ try {
26
+ stats = await lstat(sandboxRoot);
27
+ }
28
+ catch (error) {
29
+ if (error.code === "ENOENT") {
30
+ try {
31
+ await lstat(runtimeRoot);
32
+ return { removed: true, alreadyAbsent: true };
33
+ }
34
+ catch {
35
+ return { removed: false, reason: "the runtime root does not exist." };
36
+ }
37
+ }
38
+ return { removed: false, reason: "the sandbox path could not be inspected." };
39
+ }
40
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
41
+ return { removed: false, reason: "the sandbox path is not a plain directory." };
42
+ }
43
+ if (!isSameOrInside(await resolvePhysicalPath(runtimeRoot), await resolvePhysicalPath(sandboxRoot))) {
44
+ return { removed: false, reason: "the sandbox resolves outside the runtime root." };
45
+ }
46
+ let marker;
47
+ try {
48
+ marker = JSON.parse(await readFile(path.join(sandboxRoot, BENCHMARK_SANDBOX_OWNER_MARKER_FILE), "utf8"));
49
+ }
50
+ catch {
51
+ return { removed: false, reason: "no readable ownership marker; the directory is not a recognized sandbox." };
52
+ }
53
+ if (marker.schemaVersion !== BENCHMARK_SANDBOX_SCHEMA_VERSION ||
54
+ marker.sandboxId !== options.sandboxId ||
55
+ typeof marker.runtimeRoot !== "string" ||
56
+ path.resolve(marker.runtimeRoot) !== runtimeRoot) {
57
+ return { removed: false, reason: "the ownership marker does not match this sandbox." };
58
+ }
59
+ try {
60
+ await rm(sandboxRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
61
+ }
62
+ catch (error) {
63
+ return { removed: false, reason: `removal failed: ${error instanceof Error ? error.message : String(error)}` };
64
+ }
65
+ try {
66
+ await lstat(sandboxRoot);
67
+ return { removed: false, reason: "the sandbox directory still exists after removal." };
68
+ }
69
+ catch (error) {
70
+ return error.code === "ENOENT"
71
+ ? { removed: true, alreadyAbsent: false }
72
+ : { removed: false, reason: "the sandbox state could not be verified after removal." };
73
+ }
74
+ }
@@ -0,0 +1,57 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { BenchmarkSandboxError } from "./errors.js";
5
+ export function sha256OfBuffer(data) {
6
+ return createHash("sha256").update(data).digest("hex");
7
+ }
8
+ function compareCodeUnits(left, right) {
9
+ return left < right ? -1 : left > right ? 1 : 0;
10
+ }
11
+ /**
12
+ * Lists every regular file below `root` as sorted project-relative POSIX paths. Entries whose name is in
13
+ * `excludedNames` are skipped at any depth. A symbolic link or junction anywhere else is rejected rather
14
+ * than followed (a link could point outside the project), as is any entry that is neither a file nor a
15
+ * directory.
16
+ */
17
+ export async function scanProjectTree(root, excludedNames) {
18
+ const excluded = new Set(excludedNames);
19
+ const files = [];
20
+ async function walk(absoluteDir, relativeDir) {
21
+ const entries = await readdir(absoluteDir, { withFileTypes: true });
22
+ for (const entry of entries) {
23
+ if (excluded.has(entry.name))
24
+ continue;
25
+ const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
26
+ if (entry.isSymbolicLink()) {
27
+ throw new BenchmarkSandboxError("SYMLINK_REJECTED", `symbolic link or junction at ${relativePath}`);
28
+ }
29
+ if (entry.isDirectory()) {
30
+ await walk(path.join(absoluteDir, entry.name), relativePath);
31
+ }
32
+ else if (entry.isFile()) {
33
+ files.push(relativePath);
34
+ }
35
+ else {
36
+ throw new BenchmarkSandboxError("UNSUPPORTED_ENTRY", `unsupported entry type at ${relativePath}`);
37
+ }
38
+ }
39
+ }
40
+ await walk(root, "");
41
+ return files.sort(compareCodeUnits);
42
+ }
43
+ /** Deterministic content identity of a project tree: sorted paths with sha256 and size of every file. */
44
+ export async function snapshotProjectTree(root, options) {
45
+ const files = await scanProjectTree(root, options.excludedNames);
46
+ const entries = [];
47
+ for (const relativePath of files) {
48
+ const data = await readFile(path.join(root, ...relativePath.split("/")));
49
+ entries.push({ path: relativePath, sha256: sha256OfBuffer(data), size: data.length });
50
+ }
51
+ return entries;
52
+ }
53
+ /** Aggregate digest over sorted "path<TAB>sha256<LF>" lines (same convention as the staleness source state). */
54
+ export function digestTreeSnapshot(entries) {
55
+ const serialized = entries.map((entry) => `${entry.path}\t${entry.sha256}\n`).join("");
56
+ return createHash("sha256").update(Buffer.from(serialized, "utf8")).digest("hex");
57
+ }
@@ -0,0 +1,3 @@
1
+ export const BENCHMARK_SANDBOX_SCHEMA_VERSION = "my-dev-kit-lab-benchmark-sandbox-v1";
2
+ export const BENCHMARK_SANDBOX_OWNER_MARKER_FILE = "sandbox-owner.json";
3
+ export const BENCHMARK_SANDBOX_BASELINE_BRANCH = "sandbox-baseline";
@@ -0,0 +1,98 @@
1
+ import { BenchmarkSandboxError, excerptGitFailure, runSandboxGit, snapshotProjectTree } from "../benchmarkSandbox/index.js";
2
+ import { CHANGE_SET_SCHEMA_VERSION, ChangeSetError } from "./types.js";
3
+ function compareCodeUnits(left, right) {
4
+ return left < right ? -1 : left > right ? 1 : 0;
5
+ }
6
+ async function git(sandbox, args, label) {
7
+ const result = await runSandboxGit(sandbox, args, { label });
8
+ if (!result.ok) {
9
+ throw new ChangeSetError("GIT_FAILED", `git ${args[0] ?? ""} failed: ${excerptGitFailure(result)}`);
10
+ }
11
+ return result;
12
+ }
13
+ /** Parses `git diff --numstat -z --no-renames`: records of "added<TAB>deleted<TAB>path" separated by NUL. */
14
+ function parseNumstat(output) {
15
+ const stats = new Map();
16
+ for (const record of output.split("\0")) {
17
+ if (record === "")
18
+ continue;
19
+ const firstTab = record.indexOf("\t");
20
+ const secondTab = firstTab < 0 ? -1 : record.indexOf("\t", firstTab + 1);
21
+ if (firstTab < 0 || secondTab < 0) {
22
+ throw new ChangeSetError("INCONSISTENT_EVIDENCE", "unrecognized numstat record.");
23
+ }
24
+ const added = record.slice(0, firstTab);
25
+ const deleted = record.slice(firstTab + 1, secondTab);
26
+ stats.set(record.slice(secondTab + 1), {
27
+ additions: added === "-" ? null : Number(added),
28
+ deletions: deleted === "-" ? null : Number(deleted)
29
+ });
30
+ }
31
+ return stats;
32
+ }
33
+ /**
34
+ * Captures what changed in a sandbox relative to its own baseline commit. File status and hashes come from
35
+ * comparing the baseline manifest with the working tree (independent of Git ignore rules and of anything the
36
+ * agent claimed); line counts and the cumulative diff come from Git against the baseline commit with rename
37
+ * detection disabled. The two views must agree or capture fails rather than reporting doubtful evidence.
38
+ *
39
+ * Call it right after patch application: it stages the working tree (`git add -A --force`) and reflects
40
+ * whatever exists on disk at that moment, including files produced by later verification runs.
41
+ */
42
+ export async function captureChangeSet(options) {
43
+ const { sandbox } = options;
44
+ let working;
45
+ try {
46
+ working = await snapshotProjectTree(sandbox.projectRoot, { excludedNames: [".git"] });
47
+ }
48
+ catch (error) {
49
+ if (error instanceof BenchmarkSandboxError && (error.code === "SYMLINK_REJECTED" || error.code === "UNSUPPORTED_ENTRY")) {
50
+ throw new ChangeSetError("SYMLINK_IN_SANDBOX", error.message);
51
+ }
52
+ throw error;
53
+ }
54
+ const baselineByPath = new Map(sandbox.baseline.manifest.map((entry) => [entry.path, entry.sha256]));
55
+ const workingByPath = new Map(working.map((entry) => [entry.path, entry.sha256]));
56
+ const records = [];
57
+ for (const [relativePath, before] of baselineByPath) {
58
+ const after = workingByPath.get(relativePath);
59
+ if (after === undefined) {
60
+ records.push({ relativePath, status: "deleted", beforeSha256: before, afterSha256: null });
61
+ }
62
+ else if (after !== before) {
63
+ records.push({ relativePath, status: "modified", beforeSha256: before, afterSha256: after });
64
+ }
65
+ }
66
+ for (const [relativePath, after] of workingByPath) {
67
+ if (!baselineByPath.has(relativePath)) {
68
+ records.push({ relativePath, status: "added", beforeSha256: null, afterSha256: after });
69
+ }
70
+ }
71
+ records.sort((left, right) => compareCodeUnits(left.relativePath, right.relativePath));
72
+ await git(sandbox, ["add", "-A", "--force"], "stage");
73
+ const numstat = parseNumstat((await git(sandbox, ["diff", "--cached", "--numstat", "--no-renames", "-z", sandbox.baseline.commit], "numstat")).stdout);
74
+ const diffResult = await git(sandbox, ["diff", "--cached", "--no-renames", "--no-ext-diff", "--no-color", "--src-prefix=a/", "--dst-prefix=b/", sandbox.baseline.commit], "diff");
75
+ const changedFiles = records.map((record) => {
76
+ const stats = numstat.get(record.relativePath);
77
+ if (stats === undefined) {
78
+ throw new ChangeSetError("INCONSISTENT_EVIDENCE", `Git reports no change for ${record.relativePath}, which differs from the baseline manifest.`);
79
+ }
80
+ return { ...record, additions: stats.additions, deletions: stats.deletions };
81
+ });
82
+ const count = (status) => changedFiles.filter((file) => file.status === status).length;
83
+ const sum = (pick) => changedFiles.reduce((total, file) => total + (pick(file) ?? 0), 0);
84
+ const diff = diffResult.stdout === "" || diffResult.stdout.endsWith("\n") ? diffResult.stdout : `${diffResult.stdout}\n`;
85
+ return {
86
+ schemaVersion: CHANGE_SET_SCHEMA_VERSION,
87
+ sandboxId: sandbox.sandboxId,
88
+ baselineCommit: sandbox.baseline.commit,
89
+ changedFiles,
90
+ addedCount: count("added"),
91
+ modifiedCount: count("modified"),
92
+ deletedCount: count("deleted"),
93
+ changedCount: changedFiles.length,
94
+ totalAdditions: sum((file) => file.additions),
95
+ totalDeletions: sum((file) => file.deletions),
96
+ diff
97
+ };
98
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export { captureChangeSet } from "./captureChangeSet.js";
@@ -0,0 +1,9 @@
1
+ export const CHANGE_SET_SCHEMA_VERSION = "my-dev-kit-lab-change-set-v1";
2
+ export class ChangeSetError extends Error {
3
+ code;
4
+ constructor(code, message) {
5
+ super(`Change set error (${code}): ${message.length > 500 ? `${message.slice(0, 500)}...` : message}`);
6
+ this.name = "ChangeSetError";
7
+ this.code = code;
8
+ }
9
+ }
@@ -1,4 +1,5 @@
1
1
  import { ExperimentPluginRegistry } from "./registry.js";
2
+ import { agentSuccessRatePlugin } from "./plugins/agentSuccessRate/index.js";
2
3
  import { contextStrategyComparisonPlugin } from "./plugins/contextStrategyComparison/index.js";
3
4
  import { contextPackGenerationPlugin } from "./plugins/contextPackGeneration/index.js";
4
5
  import { contextWindowScalingPlugin } from "./plugins/contextWindowScaling/index.js";
@@ -15,5 +16,6 @@ export function createDefaultExperimentPluginRegistry() {
15
16
  registry.register(retrievalPrecisionRecallPlugin);
16
17
  registry.register(retrievalQueryStrategyComparisonPlugin);
17
18
  registry.register(contextPackGenerationPlugin);
19
+ registry.register(agentSuccessRatePlugin);
18
20
  return registry;
19
21
  }
@@ -0,0 +1,12 @@
1
+ export const AGENT_FACING_TASK_FIELDS = ["caseId", "benchmarkProject", "title", "instruction", "query", "taskLocality"];
2
+ /** Builds a fresh object from named fields. It never spreads, serializes or iterates the source task. */
3
+ export function projectAgentFacingTask(task) {
4
+ return Object.freeze({
5
+ caseId: task.id,
6
+ benchmarkProject: task.benchmarkProject,
7
+ title: task.title,
8
+ instruction: task.instruction,
9
+ query: task.query,
10
+ taskLocality: task.taskLocality
11
+ });
12
+ }