@dailephd/my-dev-kit-lab 0.6.3 → 0.7.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 (123) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +7 -4
  3. package/benchmarks/contracts/context-window-scaling-cases.json +220 -0
  4. package/benchmarks/projects/README.md +3 -1
  5. package/benchmarks/projects/context-window-scaling-fixed-ts/README.md +14 -0
  6. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/carrier-depot-catalog-01.ts +302 -0
  7. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/course-module-catalog-06.ts +302 -0
  8. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/library-shelf-catalog-07.ts +302 -0
  9. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/sensor-station-catalog-05.ts +302 -0
  10. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/staff-rota-catalog-03.ts +302 -0
  11. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/supplier-contract-catalog-02.ts +302 -0
  12. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/vehicle-route-catalog-04.ts +302 -0
  13. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/archive-box-catalog-12.ts +302 -0
  14. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/clinic-ward-catalog-08.ts +302 -0
  15. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/greenhouse-bay-catalog-13.ts +302 -0
  16. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/harbor-berth-catalog-10.ts +302 -0
  17. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/kitchen-section-catalog-15.ts +302 -0
  18. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/market-stall-catalog-09.ts +302 -0
  19. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/station-platform-catalog-14.ts +302 -0
  20. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/workshop-bench-catalog-11.ts +302 -0
  21. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/archive-box-catalog-28.ts +302 -0
  22. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/carrier-depot-catalog-17.ts +302 -0
  23. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/clinic-ward-catalog-24.ts +302 -0
  24. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/course-module-catalog-22.ts +302 -0
  25. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/greenhouse-bay-catalog-29.ts +302 -0
  26. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/harbor-berth-catalog-26.ts +302 -0
  27. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/kitchen-section-catalog-31.ts +302 -0
  28. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/library-shelf-catalog-23.ts +302 -0
  29. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/market-stall-catalog-25.ts +302 -0
  30. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/sensor-station-catalog-21.ts +302 -0
  31. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/staff-rota-catalog-19.ts +302 -0
  32. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/station-platform-catalog-30.ts +302 -0
  33. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/supplier-contract-catalog-18.ts +302 -0
  34. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/vehicle-route-catalog-20.ts +302 -0
  35. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/warehouse-zone-catalog-16.ts +302 -0
  36. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/workshop-bench-catalog-27.ts +302 -0
  37. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/archive-box-catalog-44.ts +302 -0
  38. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-33.ts +302 -0
  39. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-49.ts +302 -0
  40. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-40.ts +302 -0
  41. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-56.ts +302 -0
  42. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-38.ts +302 -0
  43. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-54.ts +302 -0
  44. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/greenhouse-bay-catalog-45.ts +302 -0
  45. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-42.ts +302 -0
  46. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-58.ts +302 -0
  47. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/kitchen-section-catalog-47.ts +302 -0
  48. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-39.ts +302 -0
  49. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-55.ts +302 -0
  50. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-41.ts +302 -0
  51. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-57.ts +302 -0
  52. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-37.ts +302 -0
  53. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-53.ts +302 -0
  54. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-35.ts +302 -0
  55. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-51.ts +302 -0
  56. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/station-platform-catalog-46.ts +302 -0
  57. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-34.ts +302 -0
  58. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-50.ts +302 -0
  59. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-36.ts +302 -0
  60. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-52.ts +302 -0
  61. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-32.ts +302 -0
  62. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-48.ts +302 -0
  63. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/workshop-bench-catalog-43.ts +302 -0
  64. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/auditRedaction.ts +25 -0
  65. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/inventoryReservation.ts +26 -0
  66. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/invoiceTotals.ts +25 -0
  67. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/shippingQuote.ts +32 -0
  68. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/auditRedaction.test.ts +17 -0
  69. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/inventoryReservation.test.ts +19 -0
  70. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/invoiceTotals.test.ts +14 -0
  71. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/shippingQuote.test.ts +19 -0
  72. package/dist/src/cli/help.js +19 -3
  73. package/dist/src/commands/runExperimentDescribeCommand.js +6 -0
  74. package/dist/src/commands/runExperimentRunCommand.js +118 -7
  75. package/dist/src/evaluation/syntheticRepository/config.js +304 -0
  76. package/dist/src/evaluation/syntheticRepository/evaluationCase.js +43 -0
  77. package/dist/src/evaluation/syntheticRepository/identity.js +41 -0
  78. package/dist/src/evaluation/syntheticRepository/index.js +16 -0
  79. package/dist/src/evaluation/syntheticRepository/inspection.js +92 -0
  80. package/dist/src/evaluation/syntheticRepository/layout.js +50 -0
  81. package/dist/src/evaluation/syntheticRepository/manifest.js +281 -0
  82. package/dist/src/evaluation/syntheticRepository/manifestVerification.js +112 -0
  83. package/dist/src/evaluation/syntheticRepository/materialize.js +170 -0
  84. package/dist/src/evaluation/syntheticRepository/planning.js +479 -0
  85. package/dist/src/evaluation/syntheticRepository/prng.js +47 -0
  86. package/dist/src/evaluation/syntheticRepository/renderPython.js +68 -0
  87. package/dist/src/evaluation/syntheticRepository/renderRepository.js +39 -0
  88. package/dist/src/evaluation/syntheticRepository/renderShared.js +93 -0
  89. package/dist/src/evaluation/syntheticRepository/renderTypeScript.js +82 -0
  90. package/dist/src/evaluation/syntheticRepository/topology.js +96 -0
  91. package/dist/src/evaluation/syntheticRepository/types.js +26 -0
  92. package/dist/src/experiments/defaultRegistry.js +2 -0
  93. package/dist/src/experiments/plugins/contextWindowScaling/cliBudgets.js +31 -0
  94. package/dist/src/experiments/plugins/contextWindowScaling/config.js +84 -0
  95. package/dist/src/experiments/plugins/contextWindowScaling/contextBudget.js +43 -0
  96. package/dist/src/experiments/plugins/contextWindowScaling/evaluation.js +36 -0
  97. package/dist/src/experiments/plugins/contextWindowScaling/execution.js +187 -0
  98. package/dist/src/experiments/plugins/contextWindowScaling/executionArtifact.js +15 -0
  99. package/dist/src/experiments/plugins/contextWindowScaling/executionArtifactReader.js +186 -0
  100. package/dist/src/experiments/plugins/contextWindowScaling/index.js +15 -0
  101. package/dist/src/experiments/plugins/contextWindowScaling/metadata.js +12 -0
  102. package/dist/src/experiments/plugins/contextWindowScaling/metrics.js +149 -0
  103. package/dist/src/experiments/plugins/contextWindowScaling/plugin.js +185 -0
  104. package/dist/src/experiments/plugins/contextWindowScaling/projectProfile.js +51 -0
  105. package/dist/src/experiments/plugins/contextWindowScaling/relevantFiles.js +76 -0
  106. package/dist/src/experiments/plugins/contextWindowScaling/successEvidence.js +60 -0
  107. package/dist/src/experiments/plugins/contextWindowScaling/syntheticInputs.js +56 -0
  108. package/dist/src/experiments/plugins/contextWindowScaling/types.js +1 -0
  109. package/dist/src/experiments/plugins/index.js +1 -0
  110. package/dist/src/experiments/plugins/warmIndexReuse/agentEvaluation.js +1 -1
  111. package/dist/src/plots/buildContextWindowScalingPlotData.js +96 -0
  112. package/dist/src/plots/index.js +1 -0
  113. package/dist/src/plots/writePlotArtifacts.js +12 -3
  114. package/dist/src/report/experiments/buildContextWindowScalingReport.js +84 -0
  115. package/dist/src/report/experiments/buildPluginExperimentReport.js +22 -2
  116. package/dist/src/report/experiments/contextWindowScalingReportModel.js +3 -0
  117. package/dist/src/report/experiments/index.js +4 -0
  118. package/dist/src/report/experiments/renderContextWindowScalingHtml.js +99 -0
  119. package/dist/src/report/experiments/renderContextWindowScalingText.js +108 -0
  120. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  121. package/dist/src/report/experiments/renderPluginExperimentReportText.js +8 -0
  122. package/docs/METRICS.md +29 -1
  123. package/package.json +1 -1
@@ -0,0 +1,185 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { tokenCountMethod } from "../../../core/countTokens.js";
4
+ import { resolveWithinRoot } from "../../../core/pathSafety.js";
5
+ import { summarizeExperimentRun } from "../../results.js";
6
+ import { aggregateStatus } from "../warmIndexReuse/execution.js";
7
+ import { contextWindowScalingConfigDefinition, defaultContextWindowScalingConfig, validateContextWindowScalingConfig, } from "./config.js";
8
+ import { executeContextWindowScalingCases, } from "./execution.js";
9
+ import { buildContextWindowScalingExecutionArtifact, CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE, } from "./executionArtifact.js";
10
+ import { CONTEXT_WINDOW_SCALING_TREATMENT_IDS, contextWindowScalingMetadata } from "./metadata.js";
11
+ import { aggregateContextWindowScaling, toAggregateExperimentMetrics, toOutcomeBudgetMetrics, } from "./metrics.js";
12
+ import { resolveScalingProjectProfiles } from "./projectProfile.js";
13
+ const VARIANTS = [
14
+ {
15
+ id: "raw-full-file",
16
+ name: "Raw full file",
17
+ description: "Every file matched by the case's raw include globs, constructed once per case.",
18
+ },
19
+ {
20
+ id: "my-dev-kit-guided",
21
+ name: "my-dev-kit guided",
22
+ description: "my-dev-kit retrieval context, constructed once per case.",
23
+ },
24
+ ];
25
+ /** Internal-only seam: tests inject construction/evaluation owners through inputs.dependencies. */
26
+ export const contextWindowScalingPlugin = {
27
+ metadata: contextWindowScalingMetadata,
28
+ defaultConfig: defaultContextWindowScalingConfig,
29
+ configDefinition: contextWindowScalingConfigDefinition,
30
+ supportedVariants: CONTEXT_WINDOW_SCALING_TREATMENT_IDS,
31
+ validateConfig: validateContextWindowScalingConfig,
32
+ async run(context) {
33
+ const startedAt = context.startedAt.toISOString();
34
+ const cases = readCasesInput(context.inputs);
35
+ const projectProfiles = resolveScalingProjectProfiles(cases, readProjectProfilesInput(context.inputs));
36
+ const outDir = context.outputRoot ?? path.resolve(context.toolRoot, "lab-output", "context-window-scaling");
37
+ await mkdir(outDir, { recursive: true });
38
+ const executionEvidence = await executeContextWindowScalingCases({
39
+ cases,
40
+ contextBudgets: context.config.contextBudgets,
41
+ kitCommand: context.config.kitCommand,
42
+ outputRoot: outDir,
43
+ projectProfiles,
44
+ cwd: context.target.targetRoot,
45
+ env: readEnvInput(context.inputs),
46
+ dependencies: readDependenciesInput(context.inputs),
47
+ });
48
+ const completedAt = new Date().toISOString();
49
+ const artifact = buildContextWindowScalingExecutionArtifact({
50
+ runId: context.runId,
51
+ pluginId: contextWindowScalingMetadata.id,
52
+ pluginSchemaVersion: contextWindowScalingMetadata.schemaVersion,
53
+ startedAt,
54
+ completedAt,
55
+ tokenCountMethod,
56
+ contextBudgets: context.config.contextBudgets,
57
+ cases: executionEvidence,
58
+ });
59
+ const artifactPath = resolveWithinRoot(outDir, CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE);
60
+ await writeFile(artifactPath, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
61
+ return mapExecutionToRun({
62
+ runId: context.runId,
63
+ startedAt,
64
+ completedAt,
65
+ target: context.target,
66
+ contextBudgets: context.config.contextBudgets,
67
+ executionEvidence,
68
+ artifactPath,
69
+ });
70
+ },
71
+ summarize(result) {
72
+ return result.summary ?? summarizeExperimentRun(result);
73
+ },
74
+ };
75
+ export function mapExecutionToRun(args) {
76
+ const cases = args.executionEvidence.map((caseEvidence) => ({
77
+ id: caseEvidence.caseId,
78
+ name: caseEvidence.caseName,
79
+ outcomes: caseEvidence.treatments.map((treatment) => buildOutcome(caseEvidence, treatment)),
80
+ metadata: { benchmarkProject: caseEvidence.benchmarkProject },
81
+ }));
82
+ const aggregate = aggregateContextWindowScaling({ contextBudgets: args.contextBudgets, cases: args.executionEvidence });
83
+ const run = {
84
+ runId: args.runId,
85
+ pluginId: contextWindowScalingMetadata.id,
86
+ startedAt: args.startedAt,
87
+ completedAt: args.completedAt,
88
+ status: aggregateStatus(cases.flatMap((experimentCase) => experimentCase.outcomes.map((outcome) => outcome.status))),
89
+ target: args.target,
90
+ variants: VARIANTS.map((variant) => ({ ...variant })),
91
+ cases,
92
+ metrics: toAggregateExperimentMetrics(aggregate),
93
+ artifacts: [
94
+ {
95
+ id: "context-window-scaling-execution",
96
+ label: "Context window scaling execution evidence",
97
+ path: args.artifactPath,
98
+ kind: "artifact",
99
+ mimeType: "application/json",
100
+ description: "Per-case, per-treatment, per-budget fit, correctness, success, and omitted-file evidence without context text.",
101
+ },
102
+ ],
103
+ warnings: [],
104
+ failures: [],
105
+ metadata: { executionArtifactPath: args.artifactPath },
106
+ contextBudgets: [...args.contextBudgets],
107
+ executionEvidence: structuredClone([...args.executionEvidence]),
108
+ aggregate,
109
+ };
110
+ run.summary = summarizeExperimentRun(run);
111
+ return run;
112
+ }
113
+ function buildOutcome(caseEvidence, treatment) {
114
+ const { caseId } = caseEvidence;
115
+ const variantId = treatment.variantId;
116
+ const metrics = [];
117
+ if (treatment.context.estimatedTokens !== null) {
118
+ metrics.push({
119
+ id: "context-estimated-tokens",
120
+ name: "Context estimated tokens",
121
+ value: treatment.context.estimatedTokens,
122
+ unit: "tokens",
123
+ variantId,
124
+ caseId,
125
+ });
126
+ }
127
+ if (treatment.relevantFileEvidence.omittedRelevantFileCount !== null) {
128
+ metrics.push({
129
+ id: "omitted-relevant-file-count",
130
+ name: "Omitted relevant files",
131
+ value: treatment.relevantFileEvidence.omittedRelevantFileCount,
132
+ unit: "files",
133
+ variantId,
134
+ caseId,
135
+ });
136
+ }
137
+ metrics.push(...toOutcomeBudgetMetrics(treatment, caseId));
138
+ const warnings = [
139
+ ...treatment.context.warnings.map((message) => ({ code: "guided-retrieval-warning", message, variantId, caseId })),
140
+ ...(treatment.evaluation.status === "unavailable"
141
+ ? [{ code: "deterministic-evaluation-unavailable", message: treatment.evaluation.reason ?? "Evaluation unavailable.", variantId, caseId }]
142
+ : []),
143
+ ];
144
+ const failures = treatment.errors.map((error) => ({
145
+ code: error.code,
146
+ message: error.message,
147
+ variantId,
148
+ caseId,
149
+ recoverable: true,
150
+ }));
151
+ return {
152
+ id: `${caseId}:${variantId}`,
153
+ caseId,
154
+ variantId,
155
+ status: treatment.status,
156
+ metrics,
157
+ artifacts: [],
158
+ warnings,
159
+ failures,
160
+ metadata: {
161
+ benchmarkProject: caseEvidence.benchmarkProject,
162
+ contextStatus: treatment.context.status,
163
+ evaluationStatus: treatment.evaluation.status,
164
+ },
165
+ };
166
+ }
167
+ function readCasesInput(inputs) {
168
+ const value = inputs?.cases;
169
+ if (!Array.isArray(value) || value.length === 0) {
170
+ throw new Error("Context window scaling requires a non-empty cases input.");
171
+ }
172
+ return value;
173
+ }
174
+ function readProjectProfilesInput(inputs) {
175
+ const value = inputs?.projectProfiles;
176
+ return Array.isArray(value) ? value : [];
177
+ }
178
+ function readEnvInput(inputs) {
179
+ const env = inputs?.env;
180
+ return env && typeof env === "object" && !Array.isArray(env) ? env : undefined;
181
+ }
182
+ function readDependenciesInput(inputs) {
183
+ const value = inputs?.dependencies;
184
+ return value && typeof value === "object" ? value : undefined;
185
+ }
@@ -0,0 +1,51 @@
1
+ import path from "node:path";
2
+ import { calculateProjectComplexityScore, computeProjectComplexityMetrics, PROJECT_COMPLEXITY_FORMULA, } from "../../../evaluation/projectComplexity.js";
3
+ import { buildProjectFileTree } from "../../../evaluation/projectFileTree.js";
4
+ /**
5
+ * The fixed scaling project has no entry in the shared benchmark-project-profiles contract. The
6
+ * existing prompt owner requires a profile per case, so a missing one is derived in memory from
7
+ * the existing file-tree and complexity owners. Supplied profiles always win; nothing is written.
8
+ */
9
+ export function resolveScalingProjectProfiles(cases, supplied) {
10
+ const profiles = [...supplied];
11
+ const known = new Set(profiles.map((profile) => profile.projectId));
12
+ for (const evaluationCase of cases) {
13
+ const profileId = evaluationCase.projectProfileRef ?? evaluationCase.benchmarkProject;
14
+ if (known.has(profileId))
15
+ continue;
16
+ known.add(profileId);
17
+ profiles.push(deriveProfile(profileId, evaluationCase, cases));
18
+ }
19
+ return profiles;
20
+ }
21
+ function deriveProfile(profileId, evaluationCase, cases) {
22
+ const siblings = cases.filter((candidate) => (candidate.projectProfileRef ?? candidate.benchmarkProject) === profileId);
23
+ const fileTree = buildProjectFileTree(evaluationCase.absoluteTargetRoot);
24
+ const average = (values) => (values.length === 0 ? 0 : values.reduce((a, b) => a + b, 0) / values.length);
25
+ const complexityMetrics = computeProjectComplexityMetrics(evaluationCase.absoluteTargetRoot, fileTree, {
26
+ taskCount: siblings.length,
27
+ expectedRelevantFilesAverage: average(siblings.map((candidate) => candidate.expectedFiles.length)),
28
+ expectedRelevantSymbolsAverage: average(siblings.map((candidate) => candidate.expectedSymbols.length)),
29
+ });
30
+ const languages = [
31
+ ...new Set(fileTree.entries.map((entry) => entry.language).filter((language) => Boolean(language))),
32
+ ].sort();
33
+ return {
34
+ projectId: profileId,
35
+ displayName: profileId,
36
+ description: "Profile derived in memory for the context-window-scaling fixed corpus.",
37
+ languageMix: languages.length <= 1 ? `single-language ${languages[0] ?? "unknown"}` : "multi-language",
38
+ primaryLanguage: languages[0] ?? "unknown",
39
+ languages,
40
+ complexityLevel: "large",
41
+ complexityScore: calculateProjectComplexityScore(complexityMetrics),
42
+ complexityMetrics,
43
+ complexityFormula: PROJECT_COMPLEXITY_FORMULA,
44
+ rootPath: path.posix.normalize(evaluationCase.targetRoot.replace(/\\/g, "/")),
45
+ sourceRoots: [...evaluationCase.sourceRoots],
46
+ testRoots: ["tests"],
47
+ fileTree,
48
+ benchmarkPurpose: "Context-window scaling evaluation.",
49
+ expectedUseCases: ["context budget fit evaluation"],
50
+ };
51
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Bounded repository-relative identity: "\" becomes "/", leading "./" is removed, case is kept.
3
+ * Returns null for absolute paths, drive paths, parent traversal, empty segments, or oversized values.
4
+ */
5
+ export function normalizeRepositoryRelativePath(value) {
6
+ if (typeof value !== "string" || value.length === 0 || value.length > 1024) {
7
+ return null;
8
+ }
9
+ let normalized = value.replace(/\\/g, "/");
10
+ while (normalized.startsWith("./")) {
11
+ normalized = normalized.slice(2);
12
+ }
13
+ if (normalized.length === 0 || normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) {
14
+ return null;
15
+ }
16
+ if (normalized.split("/").some((segment) => segment === ".." || segment === "")) {
17
+ return null;
18
+ }
19
+ return normalized;
20
+ }
21
+ /** observedFiles === null means observed-file provenance is unavailable. */
22
+ export function computeRelevantFileEvidence(args) {
23
+ if (args.expectedFiles.length === 0) {
24
+ return {
25
+ status: "not-applicable",
26
+ expectedRelevantFiles: [],
27
+ expectedRelevantFileCount: 0,
28
+ observedExpectedFileCount: null,
29
+ omittedRelevantFileCount: null,
30
+ omittedRelevantFiles: [],
31
+ reason: "No expected relevant files are defined for this case.",
32
+ };
33
+ }
34
+ const expected = [];
35
+ for (const file of args.expectedFiles) {
36
+ const normalized = normalizeRepositoryRelativePath(file);
37
+ if (normalized === null) {
38
+ return unavailable([...args.expectedFiles], "An expected relevant file is not a bounded repository-relative path.");
39
+ }
40
+ if (!expected.includes(normalized)) {
41
+ expected.push(normalized);
42
+ }
43
+ }
44
+ if (args.observedFiles === null) {
45
+ return unavailable(expected, "Observed-file provenance is unavailable.");
46
+ }
47
+ const observed = new Set();
48
+ for (const file of args.observedFiles) {
49
+ const normalized = normalizeRepositoryRelativePath(file);
50
+ if (normalized === null) {
51
+ return unavailable(expected, "An observed file is not a bounded repository-relative path.");
52
+ }
53
+ observed.add(normalized);
54
+ }
55
+ const omitted = expected.filter((file) => !observed.has(file));
56
+ return {
57
+ status: "available",
58
+ expectedRelevantFiles: expected,
59
+ expectedRelevantFileCount: expected.length,
60
+ observedExpectedFileCount: expected.length - omitted.length,
61
+ omittedRelevantFileCount: omitted.length,
62
+ omittedRelevantFiles: omitted,
63
+ reason: null,
64
+ };
65
+ }
66
+ function unavailable(expected, reason) {
67
+ return {
68
+ status: "unavailable",
69
+ expectedRelevantFiles: expected,
70
+ expectedRelevantFileCount: expected.length,
71
+ observedExpectedFileCount: null,
72
+ omittedRelevantFileCount: null,
73
+ omittedRelevantFiles: [],
74
+ reason,
75
+ };
76
+ }
@@ -0,0 +1,60 @@
1
+ import { calculateContextBudgetUtilizationPercent, classifyContextFit } from "./contextBudget.js";
2
+ export const UNAVAILABLE_CORRECTNESS = { availability: "unavailable", score: null, pass: null };
3
+ /** The existing scorer's own passed result is used as-is; no score threshold is defined here. */
4
+ export function toCorrectnessEvidence(scored) {
5
+ if (!scored.available || scored.score === null || scored.passed === null) {
6
+ return { ...UNAVAILABLE_CORRECTNESS };
7
+ }
8
+ return { availability: "available", score: scored.score, pass: scored.passed };
9
+ }
10
+ export function deriveSuccessEvidence(args) {
11
+ if (args.contextFitStatus === "unavailable") {
12
+ return { status: "unavailable", success: null, reason: "context-unavailable" };
13
+ }
14
+ if (args.contextFitStatus === "context-too-large") {
15
+ return { status: "available", success: false, reason: "context-too-large" };
16
+ }
17
+ if (args.correctness.availability !== "available" || args.correctness.pass === null) {
18
+ return { status: "unavailable", success: null, reason: "evaluation-unavailable" };
19
+ }
20
+ return args.correctness.pass
21
+ ? { status: "available", success: true, reason: "correctness-pass" }
22
+ : { status: "available", success: false, reason: "correctness-fail" };
23
+ }
24
+ /**
25
+ * One cell per budget. `shared` is the single deterministic evaluation of this treatment's context
26
+ * (null when it was never requested because no budget fits or the context is unavailable).
27
+ */
28
+ export function buildBudgetCell(args) {
29
+ if (args.contextEstimatedTokens === null) {
30
+ return {
31
+ contextBudgetTokens: args.contextBudgetTokens,
32
+ contextFitStatus: "unavailable",
33
+ contextBudgetUtilizationPercent: null,
34
+ evaluationStatus: "unavailable",
35
+ correctness: { ...UNAVAILABLE_CORRECTNESS },
36
+ successEvidence: deriveSuccessEvidence({ contextFitStatus: "unavailable", correctness: UNAVAILABLE_CORRECTNESS }),
37
+ };
38
+ }
39
+ const fit = classifyContextFit(args.contextEstimatedTokens, args.contextBudgetTokens);
40
+ const utilization = calculateContextBudgetUtilizationPercent(args.contextEstimatedTokens, args.contextBudgetTokens);
41
+ if (fit === "context-too-large") {
42
+ return {
43
+ contextBudgetTokens: args.contextBudgetTokens,
44
+ contextFitStatus: fit,
45
+ contextBudgetUtilizationPercent: utilization,
46
+ evaluationStatus: "not-evaluated-context-too-large",
47
+ correctness: { ...UNAVAILABLE_CORRECTNESS },
48
+ successEvidence: deriveSuccessEvidence({ contextFitStatus: fit, correctness: UNAVAILABLE_CORRECTNESS }),
49
+ };
50
+ }
51
+ const correctness = args.shared ?? { ...UNAVAILABLE_CORRECTNESS };
52
+ return {
53
+ contextBudgetTokens: args.contextBudgetTokens,
54
+ contextFitStatus: fit,
55
+ contextBudgetUtilizationPercent: utilization,
56
+ evaluationStatus: correctness.availability === "available" ? "evaluated" : "unavailable",
57
+ correctness: { ...correctness },
58
+ successEvidence: deriveSuccessEvidence({ contextFitStatus: fit, correctness }),
59
+ };
60
+ }
@@ -0,0 +1,56 @@
1
+ import { existsSync, mkdirSync, realpathSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { relativeWithinRoot, resolveWithinRoot } from "../../../core/pathSafety.js";
4
+ import { materializeSyntheticRepository, planSyntheticRepositories } from "../../../evaluation/syntheticRepository/index.js";
5
+ import { resolveScalingProjectProfiles } from "./projectProfile.js";
6
+ /**
7
+ * Stable child of the experiment output root that owns every generated repository. Batch 2 lays each case out
8
+ * beneath it as `<case id>/repository` plus a sibling manifest. It never collides with the execution artifact,
9
+ * reports, `guided/`, `agents/` or `charts/`.
10
+ */
11
+ export const SYNTHETIC_REPOSITORIES_OUTPUT_SUBDIRECTORY = "synthetic-repositories";
12
+ /**
13
+ * Internal input source for context-window-scaling: Batch 1 config -> plans -> Batch 2 materializations ->
14
+ * EvaluationCase[] -> in-memory project profiles. It owns only that sequencing; planning, rendering, manifests,
15
+ * profile derivation and execution all stay with their existing owners, and generated repositories are not
16
+ * serialized into bundled case JSON (readEvaluationCases is deliberately not used).
17
+ *
18
+ * Fail closed: an invalid or empty config, a planner failure, a materialization collision or a verification
19
+ * failure throws the owning typed error before any case exists. All plans are computed before the first
20
+ * repository is written. Repeating the call with the same config and output root reuses the existing
21
+ * materializations (Batch 2 idempotence) and returns the same identities and order.
22
+ *
23
+ * The caller must supply the final experiment output root; the public command resolves it before calling this
24
+ * and passes the same root to the generic runner.
25
+ */
26
+ export function prepareSyntheticContextWindowScalingInputs(args) {
27
+ const plans = planSyntheticRepositories(args.syntheticRepositoryConfig);
28
+ const syntheticOutputRoot = resolveWithinRoot(args.outputRoot, SYNTHETIC_REPOSITORIES_OUTPUT_SUBDIRECTORY);
29
+ assertStaysInsideOutputRoot(args.outputRoot, syntheticOutputRoot);
30
+ const materializations = plans.map((plan) => {
31
+ const materialized = materializeSyntheticRepository(plan, syntheticOutputRoot);
32
+ return {
33
+ caseId: plan.caseId,
34
+ generationIdentity: plan.generationIdentity,
35
+ repositoryContentIdentity: materialized.manifest.repositoryContentIdentity,
36
+ repositoryRoot: materialized.repositoryRoot,
37
+ manifestPath: materialized.manifestPath,
38
+ evaluationCase: materialized.evaluationCase,
39
+ reusedExistingMaterialization: materialized.reusedExistingMaterialization,
40
+ };
41
+ });
42
+ const cases = materializations.map((entry) => entry.evaluationCase);
43
+ return { cases, projectProfiles: resolveScalingProjectProfiles(cases, []), materializations, syntheticOutputRoot };
44
+ }
45
+ /** A pre-existing synthetic subtree that is a link resolving elsewhere must not redirect generation out of the experiment output. */
46
+ function assertStaysInsideOutputRoot(outputRoot, syntheticOutputRoot) {
47
+ mkdirSync(path.resolve(outputRoot), { recursive: true });
48
+ if (!existsSync(syntheticOutputRoot))
49
+ return;
50
+ try {
51
+ relativeWithinRoot(realpathSync(path.resolve(outputRoot)), realpathSync(syntheticOutputRoot));
52
+ }
53
+ catch {
54
+ throw new Error(`Synthetic repository output ${SYNTHETIC_REPOSITORIES_OUTPUT_SUBDIRECTORY} resolves outside the experiment output root.`);
55
+ }
56
+ }
@@ -1,3 +1,4 @@
1
1
  export * from "./contextStrategyComparison/index.js";
2
+ export * from "./contextWindowScaling/index.js";
2
3
  export * from "./incrementalChangeStaleness/index.js";
3
4
  export * from "./warmIndexReuse/index.js";
@@ -41,7 +41,7 @@ export async function evaluateWarmIndexFakeAgents(options) {
41
41
  }
42
42
  return result;
43
43
  }
44
- async function evaluateFakeSide(args) {
44
+ export async function evaluateFakeSide(args) {
45
45
  const promptStrategy = SIDE_STRATEGIES[args.variantId];
46
46
  const base = { variantId: args.variantId, agentId: "fake-agent", promptStrategy };
47
47
  try {
@@ -0,0 +1,96 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE } from "../experiments/plugins/contextWindowScaling/executionArtifact.js";
5
+ import { parseContextWindowScalingExecutionArtifact } from "../experiments/plugins/contextWindowScaling/executionArtifactReader.js";
6
+ import { aggregateContextWindowScaling } from "../experiments/plugins/contextWindowScaling/metrics.js";
7
+ import { STANDARD_CONTEXT_BUDGETS } from "../experiments/plugins/contextWindowScaling/contextBudget.js";
8
+ // ---------------------------------------------------------------------------
9
+ // Context-window-scaling plot data. Maps values already calculated by
10
+ // aggregateContextWindowScaling() (carried on ContextWindowScalingRun.aggregate) to plot
11
+ // points. Nothing is recalculated; a null aggregate value becomes a skipped point, never 0.
12
+ // ---------------------------------------------------------------------------
13
+ export const CONTEXT_WINDOW_SCALING_PLOT_IDS = [
14
+ "context-window-scaling-context-size",
15
+ "context-window-scaling-success-rate-by-budget",
16
+ "context-window-scaling-correctness-by-budget",
17
+ ];
18
+ export const CONTEXT_WINDOW_SCALING_SKIP_REASONS = {
19
+ contextSize: "context-size-unavailable",
20
+ successRate: "success-rate-unavailable",
21
+ correctness: "correctness-unavailable",
22
+ };
23
+ /** Standard budgets read as 8k/16k/32k/64k; any other budget keeps its exact integer form. */
24
+ export function formatContextBudgetLabel(budget) {
25
+ return STANDARD_CONTEXT_BUDGETS.includes(budget) ? `${budget / 1024}k` : String(budget);
26
+ }
27
+ /**
28
+ * Returns the aggregate rebuilt from `<experimentDir>/context-window-scaling-execution.json`, or null
29
+ * when that artifact is absent. A present artifact is validated (schema version and structure) and
30
+ * aggregated by the canonical aggregateContextWindowScaling(); malformed input fails, never falls back.
31
+ */
32
+ export async function readContextWindowScalingPlotSource(experimentDir) {
33
+ const artifactPath = path.join(experimentDir, CONTEXT_WINDOW_SCALING_EXECUTION_ARTIFACT_FILE);
34
+ if (!existsSync(artifactPath))
35
+ return null;
36
+ let parsed;
37
+ try {
38
+ parsed = JSON.parse(await readFile(artifactPath, "utf8"));
39
+ }
40
+ catch (error) {
41
+ throw new Error(`Invalid context-window-scaling execution artifact (${artifactPath}): not valid JSON (${error instanceof Error ? error.message : String(error)}).`);
42
+ }
43
+ const artifact = parseContextWindowScalingExecutionArtifact(parsed);
44
+ return {
45
+ aggregate: aggregateContextWindowScaling({ contextBudgets: artifact.contextBudgets, cases: artifact.cases }),
46
+ generatedAt: artifact.completedAt,
47
+ };
48
+ }
49
+ export function buildContextWindowScalingPlotData(args) {
50
+ const { aggregate } = args;
51
+ const skippedPoints = [];
52
+ const series = (id, title, xLabel, yLabel) => ({
53
+ id,
54
+ title,
55
+ xLabel,
56
+ yLabel,
57
+ kind: "scatter",
58
+ points: [],
59
+ warnings: [],
60
+ });
61
+ const contextSize = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[0], "Raw vs Retrieved Context Size", "Case ordinal (canonical case order)", "Estimated context tokens");
62
+ const successRate = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[1], "Success Rate by Context Budget", "Context budget (estimated tokens)", "Success rate (percent)");
63
+ const correctness = series(CONTEXT_WINDOW_SCALING_PLOT_IDS[2], "Correctness by Context Budget", "Context budget (estimated tokens)", "Mean correctness score");
64
+ const add = (plot, x, value, group, label, reason, metadata) => {
65
+ if (value !== null && Number.isFinite(value)) {
66
+ plot.points.push({ x, y: value, group, label, metadata });
67
+ }
68
+ else {
69
+ skippedPoints.push({ plotId: plot.id, label, reason });
70
+ }
71
+ };
72
+ // Case ordinal follows the aggregate's canonical case order (first appearance of each caseId).
73
+ const caseOrdinals = new Map();
74
+ for (const summary of aggregate.caseTreatmentContextSummaries) {
75
+ if (!caseOrdinals.has(summary.caseId))
76
+ caseOrdinals.set(summary.caseId, caseOrdinals.size + 1);
77
+ }
78
+ for (const summary of aggregate.caseTreatmentContextSummaries) {
79
+ add(contextSize, caseOrdinals.get(summary.caseId), summary.contextStatus === "available" ? summary.estimatedTokens : null, summary.variantId, `${summary.caseId} ${summary.variantId}`, CONTEXT_WINDOW_SCALING_SKIP_REASONS.contextSize, { caseId: summary.caseId });
80
+ }
81
+ for (const summary of aggregate.budgetTreatmentSummaries) {
82
+ const label = `${summary.variantId} ${formatContextBudgetLabel(summary.contextBudgetTokens)}`;
83
+ const metadata = { contextBudgetTokens: summary.contextBudgetTokens };
84
+ add(successRate, summary.contextBudgetTokens, summary.successRatePercent, summary.variantId, label, CONTEXT_WINDOW_SCALING_SKIP_REASONS.successRate, metadata);
85
+ add(correctness, summary.contextBudgetTokens, summary.meanCorrectnessScore, summary.variantId, label, CONTEXT_WINDOW_SCALING_SKIP_REASONS.correctness, metadata);
86
+ }
87
+ const plots = [contextSize, successRate, correctness];
88
+ const warnings = [];
89
+ for (const plot of plots) {
90
+ if (plot.points.length === 0) {
91
+ plot.warnings.push("No comparable data available.");
92
+ warnings.push(`${plot.title}: no comparable data available.`);
93
+ }
94
+ }
95
+ return { generatedAt: args.generatedAt, sourceExperimentDir: args.experimentDir, plots, skippedPoints, warnings };
96
+ }
@@ -1,5 +1,6 @@
1
1
  export * from "./types.js";
2
2
  export * from "./buildExperimentPlotData.js";
3
3
  export * from "./buildWarmIndexPlotData.js";
4
+ export * from "./buildContextWindowScalingPlotData.js";
4
5
  export * from "./renderSvgChart.js";
5
6
  export * from "./writePlotArtifacts.js";
@@ -1,6 +1,7 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { resolveWithinRoot } from "../core/pathSafety.js";
4
+ import { buildContextWindowScalingPlotData, readContextWindowScalingPlotSource } from "./buildContextWindowScalingPlotData.js";
4
5
  import { buildExperimentPlotData } from "./buildExperimentPlotData.js";
5
6
  import { buildWarmIndexPlotData, readWarmIndexPlotSource } from "./buildWarmIndexPlotData.js";
6
7
  import { renderSvgChart } from "./renderSvgChart.js";
@@ -16,10 +17,18 @@ export async function writePlotArtifacts(options) {
16
17
  // A warm-index-reuse plugin output directory is detected by its plugin report; every other
17
18
  // directory keeps the legacy controlled-experiment plot path unchanged.
18
19
  const experimentDir = path.resolve(options.repoRoot ?? process.cwd(), options.experimentDir);
20
+ // A context-window-scaling directory is detected by its V1 execution artifact; evidence for more
21
+ // than one plugin family in the same directory is ambiguous and fails instead of choosing one.
22
+ const scalingSource = await readContextWindowScalingPlotSource(experimentDir);
19
23
  const warmSection = await readWarmIndexPlotSource(experimentDir);
20
- const data = warmSection
21
- ? buildWarmIndexPlotData({ section: warmSection, experimentDir })
22
- : await buildExperimentPlotData({ experimentDir: options.experimentDir, repoRoot: options.repoRoot });
24
+ if (scalingSource && warmSection) {
25
+ throw new Error(`Ambiguous experiment directory for plots (${experimentDir}): it contains both context-window-scaling and warm-index-reuse evidence.`);
26
+ }
27
+ const data = scalingSource
28
+ ? buildContextWindowScalingPlotData({ aggregate: scalingSource.aggregate, experimentDir, generatedAt: scalingSource.generatedAt })
29
+ : warmSection
30
+ ? buildWarmIndexPlotData({ section: warmSection, experimentDir })
31
+ : await buildExperimentPlotData({ experimentDir: options.experimentDir, repoRoot: options.repoRoot });
23
32
  return writePlotArtifactsFromData({ data, outDir: options.outDir });
24
33
  }
25
34
  export async function writePlotArtifactsFromData(options) {