@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.
- package/CHANGELOG.md +17 -0
- package/README.md +7 -4
- package/benchmarks/contracts/context-window-scaling-cases.json +220 -0
- package/benchmarks/projects/README.md +3 -1
- package/benchmarks/projects/context-window-scaling-fixed-ts/README.md +14 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/carrier-depot-catalog-01.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/course-module-catalog-06.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/library-shelf-catalog-07.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/sensor-station-catalog-05.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/staff-rota-catalog-03.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/supplier-contract-catalog-02.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/vehicle-route-catalog-04.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/archive-box-catalog-12.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/clinic-ward-catalog-08.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/greenhouse-bay-catalog-13.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/harbor-berth-catalog-10.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/kitchen-section-catalog-15.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/market-stall-catalog-09.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/station-platform-catalog-14.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/workshop-bench-catalog-11.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/archive-box-catalog-28.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/carrier-depot-catalog-17.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/clinic-ward-catalog-24.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/course-module-catalog-22.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/greenhouse-bay-catalog-29.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/harbor-berth-catalog-26.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/kitchen-section-catalog-31.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/library-shelf-catalog-23.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/market-stall-catalog-25.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/sensor-station-catalog-21.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/staff-rota-catalog-19.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/station-platform-catalog-30.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/supplier-contract-catalog-18.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/vehicle-route-catalog-20.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/warehouse-zone-catalog-16.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/workshop-bench-catalog-27.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/archive-box-catalog-44.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-33.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-49.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-40.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-56.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-38.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-54.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/greenhouse-bay-catalog-45.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-42.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-58.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/kitchen-section-catalog-47.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-39.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-55.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-41.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-57.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-37.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-53.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-35.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-51.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/station-platform-catalog-46.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-34.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-50.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-36.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-52.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-32.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-48.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/workshop-bench-catalog-43.ts +302 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/auditRedaction.ts +25 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/inventoryReservation.ts +26 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/invoiceTotals.ts +25 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/shippingQuote.ts +32 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/auditRedaction.test.ts +17 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/inventoryReservation.test.ts +19 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/invoiceTotals.test.ts +14 -0
- package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/shippingQuote.test.ts +19 -0
- package/dist/src/cli/help.js +19 -3
- package/dist/src/commands/runExperimentDescribeCommand.js +6 -0
- package/dist/src/commands/runExperimentRunCommand.js +118 -7
- package/dist/src/evaluation/syntheticRepository/config.js +304 -0
- package/dist/src/evaluation/syntheticRepository/evaluationCase.js +43 -0
- package/dist/src/evaluation/syntheticRepository/identity.js +41 -0
- package/dist/src/evaluation/syntheticRepository/index.js +16 -0
- package/dist/src/evaluation/syntheticRepository/inspection.js +92 -0
- package/dist/src/evaluation/syntheticRepository/layout.js +50 -0
- package/dist/src/evaluation/syntheticRepository/manifest.js +281 -0
- package/dist/src/evaluation/syntheticRepository/manifestVerification.js +112 -0
- package/dist/src/evaluation/syntheticRepository/materialize.js +170 -0
- package/dist/src/evaluation/syntheticRepository/planning.js +479 -0
- package/dist/src/evaluation/syntheticRepository/prng.js +47 -0
- package/dist/src/evaluation/syntheticRepository/renderPython.js +68 -0
- package/dist/src/evaluation/syntheticRepository/renderRepository.js +39 -0
- package/dist/src/evaluation/syntheticRepository/renderShared.js +93 -0
- package/dist/src/evaluation/syntheticRepository/renderTypeScript.js +82 -0
- package/dist/src/evaluation/syntheticRepository/topology.js +96 -0
- package/dist/src/evaluation/syntheticRepository/types.js +26 -0
- package/dist/src/experiments/defaultRegistry.js +2 -0
- package/dist/src/experiments/plugins/contextWindowScaling/cliBudgets.js +31 -0
- package/dist/src/experiments/plugins/contextWindowScaling/config.js +84 -0
- package/dist/src/experiments/plugins/contextWindowScaling/contextBudget.js +43 -0
- package/dist/src/experiments/plugins/contextWindowScaling/evaluation.js +36 -0
- package/dist/src/experiments/plugins/contextWindowScaling/execution.js +187 -0
- package/dist/src/experiments/plugins/contextWindowScaling/executionArtifact.js +15 -0
- package/dist/src/experiments/plugins/contextWindowScaling/executionArtifactReader.js +186 -0
- package/dist/src/experiments/plugins/contextWindowScaling/index.js +15 -0
- package/dist/src/experiments/plugins/contextWindowScaling/metadata.js +12 -0
- package/dist/src/experiments/plugins/contextWindowScaling/metrics.js +149 -0
- package/dist/src/experiments/plugins/contextWindowScaling/plugin.js +185 -0
- package/dist/src/experiments/plugins/contextWindowScaling/projectProfile.js +51 -0
- package/dist/src/experiments/plugins/contextWindowScaling/relevantFiles.js +76 -0
- package/dist/src/experiments/plugins/contextWindowScaling/successEvidence.js +60 -0
- package/dist/src/experiments/plugins/contextWindowScaling/syntheticInputs.js +56 -0
- package/dist/src/experiments/plugins/contextWindowScaling/types.js +1 -0
- package/dist/src/experiments/plugins/index.js +1 -0
- package/dist/src/experiments/plugins/warmIndexReuse/agentEvaluation.js +1 -1
- package/dist/src/plots/buildContextWindowScalingPlotData.js +96 -0
- package/dist/src/plots/index.js +1 -0
- package/dist/src/plots/writePlotArtifacts.js +12 -3
- package/dist/src/report/experiments/buildContextWindowScalingReport.js +84 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +22 -2
- package/dist/src/report/experiments/contextWindowScalingReportModel.js +3 -0
- package/dist/src/report/experiments/index.js +4 -0
- package/dist/src/report/experiments/renderContextWindowScalingHtml.js +99 -0
- package/dist/src/report/experiments/renderContextWindowScalingText.js +108 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +8 -0
- package/docs/METRICS.md +29 -1
- package/package.json +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { parseAgentCommandTemplate } from "../agents/index.js";
|
|
3
4
|
import { parseAgentId } from "../agents/agentRegistry.js";
|
|
4
5
|
import { readBenchmarkProjectProfiles, readEvaluationCases } from "../evaluation/index.js";
|
|
5
|
-
import { contextStrategyComparisonPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, incrementalChangeStalenessPlugin, parseWarmIndexCampaignPresetId, resolveExperimentTarget, runExperiment, warmIndexReusePlugin } from "../experiments/index.js";
|
|
6
|
+
import { contextStrategyComparisonPlugin, contextWindowScalingPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, incrementalChangeStalenessPlugin, parseContextBudgetsCliValue, parseWarmIndexCampaignPresetId, resolveExperimentTarget, runExperiment, warmIndexReusePlugin } from "../experiments/index.js";
|
|
7
|
+
import { prepareSyntheticContextWindowScalingInputs } from "../experiments/plugins/contextWindowScaling/index.js";
|
|
6
8
|
import { buildDefaultExperimentOutputRoot } from "../experiments/outputPaths.js";
|
|
7
9
|
import { parsePromptComplexityLevel, parsePromptStrategy } from "../prompts/index.js";
|
|
8
10
|
import { writePluginExperimentReports } from "../report/index.js";
|
|
@@ -32,27 +34,42 @@ import { runWarmIndexCampaignPresentation } from "./runWarmIndexCampaignPresenta
|
|
|
32
34
|
// ---------------------------------------------------------------------------
|
|
33
35
|
const DEFAULT_CASES_RESOURCE = "examples/token-savings-cases.json";
|
|
34
36
|
const DEFAULT_PROJECT_PROFILES_RESOURCE = "benchmarks/contracts/benchmark-project-profiles.json";
|
|
37
|
+
// context-window-scaling owns one frozen bundled case catalog; its fixed project is referenced by the
|
|
38
|
+
// catalog's package-relative targetRoot, so both resolve from the package root, never the cwd.
|
|
39
|
+
const CONTEXT_WINDOW_SCALING_CASES_RESOURCE = "benchmarks/contracts/context-window-scaling-cases.json";
|
|
40
|
+
// Options the context-window-scaling plugin accepts; every other flag is rejected, not ignored.
|
|
41
|
+
const CONTEXT_WINDOW_SCALING_ALLOWED_FLAGS = ["--experiment", "--out", "--case", "--synthetic-config", "--context-budgets", "--kit-command"];
|
|
35
42
|
export async function runExperimentRunCommandFromArgs(argv, options = {}) {
|
|
36
43
|
try {
|
|
37
44
|
const args = parseRunExperimentArgs(argv);
|
|
38
45
|
const context = options.context ?? createLabExecutionContext();
|
|
39
46
|
const toolRoot = context.packageRoot;
|
|
40
47
|
const registry = createDefaultExperimentPluginRegistry();
|
|
41
|
-
const inputs = await loadPluginInputs(args, toolRoot, context);
|
|
42
48
|
const runId = generateExperimentRunId(args.experimentId);
|
|
43
|
-
|
|
49
|
+
let outputRoot = resolveOutputRoot(args, {
|
|
44
50
|
toolRoot,
|
|
45
51
|
invocationCwd: context.invocationCwd,
|
|
46
52
|
installedDefaultOutputRoot: options.installedDefaultOutputRoot,
|
|
47
53
|
experimentId: args.experimentId,
|
|
48
54
|
runId
|
|
49
55
|
});
|
|
56
|
+
if (args.syntheticConfigPath !== undefined && outputRoot === undefined) {
|
|
57
|
+
// Synthetic generation needs the concrete root before the runner runs; use the exact default the
|
|
58
|
+
// runner itself would compute for this self-targeted invocation.
|
|
59
|
+
outputRoot = buildDefaultExperimentOutputRoot({
|
|
60
|
+
toolRoot,
|
|
61
|
+
pluginId: args.experimentId,
|
|
62
|
+
target: resolveExperimentTarget(args.targetPath, toolRoot),
|
|
63
|
+
runId
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const inputs = await loadPluginInputs(args, toolRoot, context, outputRoot);
|
|
50
67
|
const result = await runExperiment({
|
|
51
68
|
pluginId: args.experimentId,
|
|
52
69
|
registry,
|
|
53
70
|
targetPath: args.targetPath,
|
|
54
71
|
outputRoot,
|
|
55
|
-
config: args
|
|
72
|
+
config: pluginConfigFor(args),
|
|
56
73
|
inputs,
|
|
57
74
|
toolRoot,
|
|
58
75
|
runId
|
|
@@ -142,9 +159,14 @@ export function parseRunExperimentArgs(argv) {
|
|
|
142
159
|
let includeRealAgents;
|
|
143
160
|
let kitCommand;
|
|
144
161
|
let campaignPreset;
|
|
162
|
+
let contextBudgets;
|
|
163
|
+
let syntheticConfigPath;
|
|
164
|
+
const seenFlags = [];
|
|
145
165
|
const commandTemplates = {};
|
|
146
166
|
for (let index = 0; index < argv.length; index += 1) {
|
|
147
167
|
const arg = argv[index];
|
|
168
|
+
if (arg !== undefined && arg.startsWith("--"))
|
|
169
|
+
seenFlags.push(arg);
|
|
148
170
|
if (arg === "--experiment") {
|
|
149
171
|
experimentId = readRequiredValue(argv, ++index, "--experiment");
|
|
150
172
|
}
|
|
@@ -160,6 +182,12 @@ export function parseRunExperimentArgs(argv) {
|
|
|
160
182
|
else if (arg === "--project-profiles") {
|
|
161
183
|
projectProfilesPath = readRequiredValue(argv, ++index, "--project-profiles");
|
|
162
184
|
}
|
|
185
|
+
else if (arg === "--synthetic-config") {
|
|
186
|
+
if (syntheticConfigPath !== undefined) {
|
|
187
|
+
throw new Error("--synthetic-config may be supplied only once.");
|
|
188
|
+
}
|
|
189
|
+
syntheticConfigPath = readRequiredValue(argv, ++index, "--synthetic-config");
|
|
190
|
+
}
|
|
163
191
|
else if (arg === "--case") {
|
|
164
192
|
caseIds.push(...splitList(readRequiredValue(argv, ++index, "--case")));
|
|
165
193
|
}
|
|
@@ -202,6 +230,9 @@ export function parseRunExperimentArgs(argv) {
|
|
|
202
230
|
else if (arg === "--kit-command") {
|
|
203
231
|
kitCommand = readRequiredValue(argv, ++index, "--kit-command");
|
|
204
232
|
}
|
|
233
|
+
else if (arg === "--context-budgets") {
|
|
234
|
+
contextBudgets = parseContextBudgetsCliValue(readRequiredValue(argv, ++index, "--context-budgets"));
|
|
235
|
+
}
|
|
205
236
|
else if (arg === "--campaign") {
|
|
206
237
|
campaignPreset = parseWarmIndexCampaignPresetId(readRequiredValue(argv, ++index, "--campaign"));
|
|
207
238
|
}
|
|
@@ -216,7 +247,29 @@ export function parseRunExperimentArgs(argv) {
|
|
|
216
247
|
if (!experimentId) {
|
|
217
248
|
throw new Error("Usage: --experiment <id> [--target <path>] [--out <directory>]");
|
|
218
249
|
}
|
|
219
|
-
const KIT_COMMAND_PLUGIN_IDS = [
|
|
250
|
+
const KIT_COMMAND_PLUGIN_IDS = [
|
|
251
|
+
warmIndexReusePlugin.metadata.id,
|
|
252
|
+
incrementalChangeStalenessPlugin.metadata.id,
|
|
253
|
+
contextWindowScalingPlugin.metadata.id
|
|
254
|
+
];
|
|
255
|
+
if (contextBudgets !== undefined && experimentId !== contextWindowScalingPlugin.metadata.id) {
|
|
256
|
+
throw new Error(`--context-budgets is only supported for --experiment ${contextWindowScalingPlugin.metadata.id}.`);
|
|
257
|
+
}
|
|
258
|
+
if (syntheticConfigPath !== undefined && experimentId !== contextWindowScalingPlugin.metadata.id) {
|
|
259
|
+
throw new Error(`--synthetic-config is only supported for --experiment ${contextWindowScalingPlugin.metadata.id}.`);
|
|
260
|
+
}
|
|
261
|
+
if (experimentId === contextWindowScalingPlugin.metadata.id) {
|
|
262
|
+
const unsupported = [...new Set(seenFlags.filter((flag) => !CONTEXT_WINDOW_SCALING_ALLOWED_FLAGS.includes(flag)))];
|
|
263
|
+
if (unsupported.length > 0) {
|
|
264
|
+
throw new Error(`${unsupported.join(", ")} ${unsupported.length === 1 ? "is" : "are"} not supported for --experiment ${experimentId}; supported options: ${CONTEXT_WINDOW_SCALING_ALLOWED_FLAGS.join(", ")}.`);
|
|
265
|
+
}
|
|
266
|
+
if (seenFlags.includes("--case") && caseIds.length === 0) {
|
|
267
|
+
throw new Error("--case must list at least one case id.");
|
|
268
|
+
}
|
|
269
|
+
if (syntheticConfigPath !== undefined && seenFlags.includes("--case")) {
|
|
270
|
+
throw new Error("--case and --synthetic-config are mutually exclusive; the synthetic config owns the generated case set.");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
220
273
|
if (kitCommand !== undefined && !KIT_COMMAND_PLUGIN_IDS.includes(experimentId)) {
|
|
221
274
|
throw new Error(`--kit-command is only supported for --experiment ${KIT_COMMAND_PLUGIN_IDS.join(" or ")}.`);
|
|
222
275
|
}
|
|
@@ -238,6 +291,7 @@ export function parseRunExperimentArgs(argv) {
|
|
|
238
291
|
experimentId,
|
|
239
292
|
targetPath,
|
|
240
293
|
outDir,
|
|
294
|
+
syntheticConfigPath,
|
|
241
295
|
config: withoutUndefined({
|
|
242
296
|
casesPath,
|
|
243
297
|
projectProfilesPath,
|
|
@@ -253,7 +307,8 @@ export function parseRunExperimentArgs(argv) {
|
|
|
253
307
|
includeRealAgents,
|
|
254
308
|
commandTemplates: Object.keys(commandTemplates).length > 0 ? commandTemplates : undefined,
|
|
255
309
|
kitCommand,
|
|
256
|
-
campaignPreset
|
|
310
|
+
campaignPreset,
|
|
311
|
+
contextBudgets
|
|
257
312
|
})
|
|
258
313
|
};
|
|
259
314
|
}
|
|
@@ -279,7 +334,7 @@ function generateExperimentRunId(pluginId) {
|
|
|
279
334
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
280
335
|
return `${pluginId}-${timestamp}`;
|
|
281
336
|
}
|
|
282
|
-
async function loadPluginInputs(args, toolRoot, context) {
|
|
337
|
+
async function loadPluginInputs(args, toolRoot, context, outputRoot) {
|
|
283
338
|
if (args.experimentId === contextStrategyComparisonPlugin.metadata.id) {
|
|
284
339
|
const validation = contextStrategyComparisonPlugin.validateConfig(args.config);
|
|
285
340
|
if (!validation.valid || !validation.config) {
|
|
@@ -297,8 +352,64 @@ async function loadPluginInputs(args, toolRoot, context) {
|
|
|
297
352
|
}
|
|
298
353
|
return loadCasesAndProjectProfiles(args, toolRoot, context);
|
|
299
354
|
}
|
|
355
|
+
if (args.experimentId === contextWindowScalingPlugin.metadata.id) {
|
|
356
|
+
return loadContextWindowScalingInputs(args, toolRoot, context, outputRoot);
|
|
357
|
+
}
|
|
300
358
|
return undefined;
|
|
301
359
|
}
|
|
360
|
+
// --case is a selection over the frozen bundled catalog (catalog order, not argument order);
|
|
361
|
+
// the rest of the CLI config is the plugin's own ContextWindowScalingConfig.
|
|
362
|
+
function pluginConfigFor(args) {
|
|
363
|
+
if (args.experimentId !== contextWindowScalingPlugin.metadata.id)
|
|
364
|
+
return args.config;
|
|
365
|
+
const { caseIds: _caseIds, ...config } = args.config;
|
|
366
|
+
return config;
|
|
367
|
+
}
|
|
368
|
+
async function loadContextWindowScalingInputs(args, toolRoot, context, outputRoot) {
|
|
369
|
+
const validation = contextWindowScalingPlugin.validateConfig(pluginConfigFor(args));
|
|
370
|
+
if (!validation.valid || !validation.config) {
|
|
371
|
+
throw new Error(`Invalid context window scaling config: ${validation.errors.join("; ")}`);
|
|
372
|
+
}
|
|
373
|
+
if (args.syntheticConfigPath !== undefined) {
|
|
374
|
+
if (outputRoot === undefined) {
|
|
375
|
+
throw new Error("Internal error: synthetic generation requires a resolved experiment output root.");
|
|
376
|
+
}
|
|
377
|
+
const configPath = path.resolve(context.invocationCwd, args.syntheticConfigPath);
|
|
378
|
+
const syntheticRepositoryConfig = await readSyntheticConfigJson(configPath, args.syntheticConfigPath);
|
|
379
|
+
const prepared = prepareSyntheticContextWindowScalingInputs({ syntheticRepositoryConfig, outputRoot });
|
|
380
|
+
return { cases: prepared.cases, projectProfiles: prepared.projectProfiles, env: process.env };
|
|
381
|
+
}
|
|
382
|
+
const catalog = await readEvaluationCases(resolvePackageResource(context, CONTEXT_WINDOW_SCALING_CASES_RESOURCE), toolRoot);
|
|
383
|
+
const requested = args.config.caseIds;
|
|
384
|
+
if (requested === undefined)
|
|
385
|
+
return { cases: catalog, env: process.env };
|
|
386
|
+
const duplicates = [...new Set(requested.filter((id, position) => requested.indexOf(id) !== position))];
|
|
387
|
+
if (duplicates.length > 0) {
|
|
388
|
+
throw new Error(`--case contains duplicate case id(s): ${duplicates.join(", ")}.`);
|
|
389
|
+
}
|
|
390
|
+
const known = new Set(catalog.map((evaluationCase) => evaluationCase.id));
|
|
391
|
+
const unknown = requested.filter((id) => !known.has(id));
|
|
392
|
+
if (unknown.length > 0) {
|
|
393
|
+
throw new Error(`--case contains unknown case id(s): ${unknown.join(", ")}. Expected one of ${catalog.map((evaluationCase) => evaluationCase.id).join(", ")}.`);
|
|
394
|
+
}
|
|
395
|
+
return { cases: catalog.filter((evaluationCase) => requested.includes(evaluationCase.id)), env: process.env };
|
|
396
|
+
}
|
|
397
|
+
// The command only reads and parses the caller-supplied file; schema validation belongs to the synthetic planner.
|
|
398
|
+
async function readSyntheticConfigJson(resolvedPath, displayPath) {
|
|
399
|
+
let text;
|
|
400
|
+
try {
|
|
401
|
+
text = await readFile(resolvedPath, "utf8");
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
throw new Error(`Cannot read --synthetic-config ${displayPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
return JSON.parse(text);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
throw new Error(`--synthetic-config ${displayPath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
302
413
|
// Shared by both registered plugins: explicit paths resolve against toolRoot, defaults come from
|
|
303
414
|
// bundled package resources so they do not depend on the invocation cwd.
|
|
304
415
|
async function loadCasesAndProjectProfiles(args, toolRoot, context) {
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { TASK_LOCALITIES } from "../types.js";
|
|
2
|
+
import { SYNTHETIC_REPOSITORY_CONFIG_SCHEMA_VERSION, SYNTHETIC_REPOSITORY_LANGUAGES, SyntheticRepositoryConfigError, } from "./types.js";
|
|
3
|
+
export const SYNTHETIC_REPOSITORY_LIMITS = {
|
|
4
|
+
maxCases: 64,
|
|
5
|
+
caseIdMaxLength: 64,
|
|
6
|
+
seedMaxLength: 256,
|
|
7
|
+
sourceFileCount: { min: 1, max: 10000 },
|
|
8
|
+
moduleDepth: { min: 1, max: 64 },
|
|
9
|
+
internalImportCount: { min: 0, max: 100000 },
|
|
10
|
+
symbolCount: { min: 1, max: 100000 },
|
|
11
|
+
testFileCount: { min: 0, max: 10000 },
|
|
12
|
+
repeatedPatternCount: { min: 0, max: 100000 },
|
|
13
|
+
broadChangeMinSourceFiles: 4,
|
|
14
|
+
crossModuleMinSourceFiles: 2,
|
|
15
|
+
};
|
|
16
|
+
/** Fixed field order of one case; also the canonical serialization order. */
|
|
17
|
+
export const SYNTHETIC_REPOSITORY_CASE_FIELDS = [
|
|
18
|
+
"id",
|
|
19
|
+
"language",
|
|
20
|
+
"seed",
|
|
21
|
+
"sourceFileCount",
|
|
22
|
+
"moduleDepth",
|
|
23
|
+
"internalImportCount",
|
|
24
|
+
"symbolCount",
|
|
25
|
+
"testFileCount",
|
|
26
|
+
"taskLocality",
|
|
27
|
+
"repeatedPatternCount",
|
|
28
|
+
];
|
|
29
|
+
const COUNT_FIELDS = [
|
|
30
|
+
"sourceFileCount",
|
|
31
|
+
"moduleDepth",
|
|
32
|
+
"internalImportCount",
|
|
33
|
+
"symbolCount",
|
|
34
|
+
"testFileCount",
|
|
35
|
+
"repeatedPatternCount",
|
|
36
|
+
];
|
|
37
|
+
const CASE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
38
|
+
const WINDOWS_DEVICE_NAME_PATTERN = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
|
|
39
|
+
/** Locale-independent ordering by UTF-16 code unit. */
|
|
40
|
+
export function compareCodeUnits(left, right) {
|
|
41
|
+
if (left < right)
|
|
42
|
+
return -1;
|
|
43
|
+
if (left > right)
|
|
44
|
+
return 1;
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Maximum number of unique directed acyclic import edges among `sourceFileCount` modules whose longest
|
|
49
|
+
* dependency chain has exactly `moduleDepth` modules: the edge count of the balanced complete
|
|
50
|
+
* multipartite arrangement, (N^2 - sum of squared layer sizes) / 2.
|
|
51
|
+
*/
|
|
52
|
+
export function maxImportEdges(sourceFileCount, moduleDepth) {
|
|
53
|
+
const base = Math.floor(sourceFileCount / moduleDepth);
|
|
54
|
+
const larger = sourceFileCount % moduleDepth;
|
|
55
|
+
const sumOfSquares = larger * (base + 1) * (base + 1) + (moduleDepth - larger) * base * base;
|
|
56
|
+
return (sourceFileCount * sourceFileCount - sumOfSquares) / 2;
|
|
57
|
+
}
|
|
58
|
+
/** Returns the case in canonical field order. Assumes the spec is valid. */
|
|
59
|
+
export function orderCaseSpec(spec) {
|
|
60
|
+
return {
|
|
61
|
+
id: spec.id,
|
|
62
|
+
language: spec.language,
|
|
63
|
+
seed: spec.seed,
|
|
64
|
+
sourceFileCount: spec.sourceFileCount,
|
|
65
|
+
moduleDepth: spec.moduleDepth,
|
|
66
|
+
internalImportCount: spec.internalImportCount,
|
|
67
|
+
symbolCount: spec.symbolCount,
|
|
68
|
+
testFileCount: spec.testFileCount,
|
|
69
|
+
taskLocality: spec.taskLocality,
|
|
70
|
+
repeatedPatternCount: spec.repeatedPatternCount,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function isPlainObject(value) {
|
|
74
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
75
|
+
return false;
|
|
76
|
+
const prototype = Object.getPrototypeOf(value);
|
|
77
|
+
return prototype === Object.prototype || prototype === null;
|
|
78
|
+
}
|
|
79
|
+
function hasOwn(value, key) {
|
|
80
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
81
|
+
}
|
|
82
|
+
function describeValue(value) {
|
|
83
|
+
if (typeof value === "string") {
|
|
84
|
+
return JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}...` : value);
|
|
85
|
+
}
|
|
86
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
87
|
+
return String(value);
|
|
88
|
+
if (value === null)
|
|
89
|
+
return "null";
|
|
90
|
+
if (Array.isArray(value))
|
|
91
|
+
return "array";
|
|
92
|
+
return typeof value;
|
|
93
|
+
}
|
|
94
|
+
function hasLoneSurrogate(value) {
|
|
95
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
96
|
+
const unit = value.charCodeAt(index);
|
|
97
|
+
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
98
|
+
const next = value.charCodeAt(index + 1);
|
|
99
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
100
|
+
index += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (unit >= 0xdc00 && unit <= 0xdfff)
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
function validateCaseId(value, label, errors) {
|
|
111
|
+
if (typeof value !== "string") {
|
|
112
|
+
errors.push(`${label}.id: must be a string (received ${describeValue(value)}).`);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const before = errors.length;
|
|
116
|
+
if (value.length < 1 || value.length > SYNTHETIC_REPOSITORY_LIMITS.caseIdMaxLength) {
|
|
117
|
+
errors.push(`${label}.id: length must be between 1 and ${SYNTHETIC_REPOSITORY_LIMITS.caseIdMaxLength}.`);
|
|
118
|
+
}
|
|
119
|
+
else if (!CASE_ID_PATTERN.test(value)) {
|
|
120
|
+
errors.push(`${label}.id: must begin with an ASCII letter or digit and contain only ASCII letters, digits, ".", "_" and "-" (received ${describeValue(value)}).`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
if (value.endsWith(".")) {
|
|
124
|
+
errors.push(`${label}.id: must not end with ".".`);
|
|
125
|
+
}
|
|
126
|
+
if (WINDOWS_DEVICE_NAME_PATTERN.test(value.split(".")[0])) {
|
|
127
|
+
errors.push(`${label}.id: must not use a reserved device name (received ${describeValue(value)}).`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return errors.length === before;
|
|
131
|
+
}
|
|
132
|
+
function validateSeed(value, label, errors) {
|
|
133
|
+
if (typeof value !== "string") {
|
|
134
|
+
errors.push(`${label}.seed: must be a string (received ${describeValue(value)}).`);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const before = errors.length;
|
|
138
|
+
if (!/\S/.test(value)) {
|
|
139
|
+
errors.push(`${label}.seed: must contain at least one non-whitespace character.`);
|
|
140
|
+
}
|
|
141
|
+
if (value.length > SYNTHETIC_REPOSITORY_LIMITS.seedMaxLength) {
|
|
142
|
+
errors.push(`${label}.seed: must be at most ${SYNTHETIC_REPOSITORY_LIMITS.seedMaxLength} UTF-16 code units.`);
|
|
143
|
+
}
|
|
144
|
+
if (hasLoneSurrogate(value)) {
|
|
145
|
+
errors.push(`${label}.seed: must be well-formed Unicode (lone surrogate found).`);
|
|
146
|
+
}
|
|
147
|
+
return errors.length === before;
|
|
148
|
+
}
|
|
149
|
+
function validateCount(value, field, label, errors) {
|
|
150
|
+
const { min, max } = SYNTHETIC_REPOSITORY_LIMITS[field];
|
|
151
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) {
|
|
152
|
+
errors.push(`${label}.${field}: must be an integer between ${min} and ${max} (received ${describeValue(value)}).`);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
function validateCase(value, label, errors) {
|
|
158
|
+
if (!isPlainObject(value)) {
|
|
159
|
+
errors.push(`${label}: must be an object (received ${describeValue(value)}).`);
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
const before = errors.length;
|
|
163
|
+
const allowed = new Set(SYNTHETIC_REPOSITORY_CASE_FIELDS);
|
|
164
|
+
for (const key of Object.keys(value).sort(compareCodeUnits)) {
|
|
165
|
+
if (!allowed.has(key))
|
|
166
|
+
errors.push(`${label}: unknown field ${JSON.stringify(key)}.`);
|
|
167
|
+
}
|
|
168
|
+
for (const field of SYNTHETIC_REPOSITORY_CASE_FIELDS) {
|
|
169
|
+
if (!hasOwn(value, field))
|
|
170
|
+
errors.push(`${label}: missing required field ${field}.`);
|
|
171
|
+
}
|
|
172
|
+
const idOk = hasOwn(value, "id") && validateCaseId(value.id, label, errors);
|
|
173
|
+
const languageOk = hasOwn(value, "language") &&
|
|
174
|
+
SYNTHETIC_REPOSITORY_LANGUAGES.includes(value.language);
|
|
175
|
+
if (hasOwn(value, "language") && !languageOk) {
|
|
176
|
+
errors.push(`${label}.language: must be one of ${SYNTHETIC_REPOSITORY_LANGUAGES.join(", ")} (received ${describeValue(value.language)}).`);
|
|
177
|
+
}
|
|
178
|
+
const seedOk = hasOwn(value, "seed") && validateSeed(value.seed, label, errors);
|
|
179
|
+
const countOk = new Map();
|
|
180
|
+
for (const field of COUNT_FIELDS) {
|
|
181
|
+
countOk.set(field, hasOwn(value, field) && validateCount(value[field], field, label, errors));
|
|
182
|
+
}
|
|
183
|
+
const localityOk = hasOwn(value, "taskLocality") && TASK_LOCALITIES.includes(value.taskLocality);
|
|
184
|
+
if (hasOwn(value, "taskLocality") && !localityOk) {
|
|
185
|
+
errors.push(`${label}.taskLocality: must be one of ${TASK_LOCALITIES.join(", ")} (received ${describeValue(value.taskLocality)}).`);
|
|
186
|
+
}
|
|
187
|
+
const sourceFileCount = value.sourceFileCount;
|
|
188
|
+
const moduleDepth = value.moduleDepth;
|
|
189
|
+
const internalImportCount = value.internalImportCount;
|
|
190
|
+
const symbolCount = value.symbolCount;
|
|
191
|
+
const taskLocality = value.taskLocality;
|
|
192
|
+
const sourceOk = countOk.get("sourceFileCount") === true;
|
|
193
|
+
const depthOk = countOk.get("moduleDepth") === true;
|
|
194
|
+
const importsOk = countOk.get("internalImportCount") === true;
|
|
195
|
+
const symbolsOk = countOk.get("symbolCount") === true;
|
|
196
|
+
if (sourceOk && depthOk) {
|
|
197
|
+
if (moduleDepth > sourceFileCount) {
|
|
198
|
+
errors.push(`${label}.moduleDepth: must not exceed sourceFileCount (${moduleDepth} > ${sourceFileCount}).`);
|
|
199
|
+
}
|
|
200
|
+
else if (importsOk) {
|
|
201
|
+
if (moduleDepth > 1 && internalImportCount < moduleDepth - 1) {
|
|
202
|
+
errors.push(`${label}.internalImportCount: must be at least moduleDepth - 1 (${moduleDepth - 1}) to realize moduleDepth ${moduleDepth}.`);
|
|
203
|
+
}
|
|
204
|
+
const capacity = maxImportEdges(sourceFileCount, moduleDepth);
|
|
205
|
+
if (internalImportCount > capacity) {
|
|
206
|
+
errors.push(`${label}.internalImportCount: exceeds the ${capacity} unique acyclic edges possible for sourceFileCount ${sourceFileCount} with exact moduleDepth ${moduleDepth}.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (sourceOk && symbolsOk && symbolCount < sourceFileCount) {
|
|
211
|
+
errors.push(`${label}.symbolCount: must be at least sourceFileCount (${symbolCount} < ${sourceFileCount}).`);
|
|
212
|
+
}
|
|
213
|
+
if (sourceOk && localityOk) {
|
|
214
|
+
if (taskLocality === "cross-module") {
|
|
215
|
+
if (sourceFileCount < SYNTHETIC_REPOSITORY_LIMITS.crossModuleMinSourceFiles) {
|
|
216
|
+
errors.push(`${label}.taskLocality: cross-module requires sourceFileCount >= 2.`);
|
|
217
|
+
}
|
|
218
|
+
if (importsOk && internalImportCount < 1) {
|
|
219
|
+
errors.push(`${label}.taskLocality: cross-module requires internalImportCount >= 1.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (taskLocality === "broad-change" && sourceFileCount < SYNTHETIC_REPOSITORY_LIMITS.broadChangeMinSourceFiles) {
|
|
223
|
+
errors.push(`${label}.taskLocality: broad-change requires sourceFileCount >= 4.`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (errors.length !== before || !idOk || !languageOk || !seedOk || !localityOk)
|
|
227
|
+
return undefined;
|
|
228
|
+
return orderCaseSpec(value);
|
|
229
|
+
}
|
|
230
|
+
/** Validates one case spec and returns it in canonical field order. Never throws, never repairs. */
|
|
231
|
+
export function validateSyntheticRepositoryCaseSpec(input) {
|
|
232
|
+
const errors = [];
|
|
233
|
+
const spec = validateCase(input, "case", errors);
|
|
234
|
+
if (errors.length > 0 || spec === undefined)
|
|
235
|
+
return { ok: false, errors };
|
|
236
|
+
return { ok: true, errors: [], spec };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Strictly validates an unknown value as SyntheticRepositoryConfigV1. On success the returned config is
|
|
240
|
+
* canonical: cases sorted ascending by id (code-unit order), fixed field order, values untouched.
|
|
241
|
+
*/
|
|
242
|
+
export function validateSyntheticRepositoryConfig(input) {
|
|
243
|
+
const errors = [];
|
|
244
|
+
if (!isPlainObject(input)) {
|
|
245
|
+
return { ok: false, errors: [`config: must be an object (received ${describeValue(input)}).`] };
|
|
246
|
+
}
|
|
247
|
+
for (const key of Object.keys(input).sort(compareCodeUnits)) {
|
|
248
|
+
if (key !== "schemaVersion" && key !== "cases")
|
|
249
|
+
errors.push(`config: unknown field ${JSON.stringify(key)}.`);
|
|
250
|
+
}
|
|
251
|
+
if (!hasOwn(input, "schemaVersion")) {
|
|
252
|
+
errors.push("config: missing required field schemaVersion.");
|
|
253
|
+
}
|
|
254
|
+
else if (input.schemaVersion !== SYNTHETIC_REPOSITORY_CONFIG_SCHEMA_VERSION) {
|
|
255
|
+
errors.push(`config.schemaVersion: must be ${JSON.stringify(SYNTHETIC_REPOSITORY_CONFIG_SCHEMA_VERSION)} (received ${describeValue(input.schemaVersion)}).`);
|
|
256
|
+
}
|
|
257
|
+
if (!hasOwn(input, "cases")) {
|
|
258
|
+
errors.push("config: missing required field cases.");
|
|
259
|
+
return { ok: false, errors };
|
|
260
|
+
}
|
|
261
|
+
const cases = input.cases;
|
|
262
|
+
if (!Array.isArray(cases)) {
|
|
263
|
+
errors.push(`config.cases: must be an array (received ${describeValue(cases)}).`);
|
|
264
|
+
return { ok: false, errors };
|
|
265
|
+
}
|
|
266
|
+
if (cases.length < 1 || cases.length > SYNTHETIC_REPOSITORY_LIMITS.maxCases) {
|
|
267
|
+
errors.push(`config.cases: must contain between 1 and ${SYNTHETIC_REPOSITORY_LIMITS.maxCases} cases (received ${cases.length}).`);
|
|
268
|
+
return { ok: false, errors };
|
|
269
|
+
}
|
|
270
|
+
const valid = [];
|
|
271
|
+
const firstIndexById = new Map();
|
|
272
|
+
cases.forEach((candidate, index) => {
|
|
273
|
+
const label = `cases[${index}]`;
|
|
274
|
+
const spec = validateCase(candidate, label, errors);
|
|
275
|
+
if (spec)
|
|
276
|
+
valid.push(spec);
|
|
277
|
+
const id = isPlainObject(candidate) ? candidate.id : undefined;
|
|
278
|
+
if (typeof id === "string") {
|
|
279
|
+
const key = id.toLowerCase();
|
|
280
|
+
const first = firstIndexById.get(key);
|
|
281
|
+
if (first === undefined) {
|
|
282
|
+
firstIndexById.set(key, index);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
errors.push(`${label}.id: duplicates cases[${first}].id (ids are compared without regard to letter case).`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
if (errors.length > 0)
|
|
290
|
+
return { ok: false, errors };
|
|
291
|
+
const sorted = [...valid].sort((left, right) => compareCodeUnits(left.id, right.id));
|
|
292
|
+
return {
|
|
293
|
+
ok: true,
|
|
294
|
+
errors: [],
|
|
295
|
+
config: { schemaVersion: SYNTHETIC_REPOSITORY_CONFIG_SCHEMA_VERSION, cases: sorted },
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/** Validates then returns the canonical configuration; throws SyntheticRepositoryConfigError when invalid. */
|
|
299
|
+
export function normalizeSyntheticRepositoryConfig(input) {
|
|
300
|
+
const result = validateSyntheticRepositoryConfig(input);
|
|
301
|
+
if (!result.ok)
|
|
302
|
+
throw new SyntheticRepositoryConfigError(result.errors);
|
|
303
|
+
return result.config;
|
|
304
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describeSyntheticTask } from "./layout.js";
|
|
2
|
+
/** The plan's answer key expressed in the existing benchmark answer-key vocabulary (no second correctness contract). */
|
|
3
|
+
export function buildSyntheticAnswerKey(plan) {
|
|
4
|
+
const { factTextById } = describeSyntheticTask(plan);
|
|
5
|
+
return {
|
|
6
|
+
expectedFiles: [...plan.answerKey.expectedFiles],
|
|
7
|
+
expectedSymbols: [...plan.answerKey.expectedSymbols],
|
|
8
|
+
expectedFacts: plan.answerKey.facts.map((fact) => ({
|
|
9
|
+
id: fact.factId,
|
|
10
|
+
text: factTextById.get(fact.factId),
|
|
11
|
+
weight: fact.weight,
|
|
12
|
+
required: fact.required,
|
|
13
|
+
})),
|
|
14
|
+
expectedContextTargets: plan.answerKey.expectedContextTargets.map((target) => ({
|
|
15
|
+
file: target.file,
|
|
16
|
+
symbols: [...target.symbols],
|
|
17
|
+
required: target.required,
|
|
18
|
+
})),
|
|
19
|
+
minimumCorrectFacts: plan.answerKey.minimumCorrectFacts,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Builds the existing EvaluationCase directly in memory (never through readEvaluationCases, which owns
|
|
24
|
+
* repository-contained JSON cases). `targetRoot` is the stable logical root from the manifest; only
|
|
25
|
+
* `absoluteTargetRoot` carries the physical generated repository directory.
|
|
26
|
+
*/
|
|
27
|
+
export function buildSyntheticEvaluationCase(plan, manifest, repositoryRoot) {
|
|
28
|
+
const text = describeSyntheticTask(plan);
|
|
29
|
+
return {
|
|
30
|
+
id: plan.task.taskId,
|
|
31
|
+
title: text.title,
|
|
32
|
+
benchmarkProject: plan.logicalProjectId,
|
|
33
|
+
targetRoot: manifest.logicalTargetRoot,
|
|
34
|
+
sourceRoots: [...manifest.sourceRoots],
|
|
35
|
+
query: text.query,
|
|
36
|
+
expectedFiles: [...plan.answerKey.expectedFiles],
|
|
37
|
+
expectedSymbols: [...plan.answerKey.expectedSymbols],
|
|
38
|
+
rawIncludeGlobs: [...manifest.rawIncludeGlobs],
|
|
39
|
+
answerKey: buildSyntheticAnswerKey(plan),
|
|
40
|
+
taskLocality: plan.task.locality,
|
|
41
|
+
absoluteTargetRoot: repositoryRoot,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { normalizeSyntheticRepositoryConfig, orderCaseSpec, validateSyntheticRepositoryCaseSpec } from "./config.js";
|
|
3
|
+
import { SYNTHETIC_REPOSITORY_CONFIG_SCHEMA_ID, SYNTHETIC_REPOSITORY_CONFIG_SCHEMA_VERSION, SYNTHETIC_REPOSITORY_IDENTITY_ALGORITHM_VERSION, SyntheticRepositoryConfigError, } from "./types.js";
|
|
4
|
+
/** Canonical JSON text of one valid case: fixed key order, exact values. */
|
|
5
|
+
export function canonicalSyntheticRepositoryCaseText(spec) {
|
|
6
|
+
return JSON.stringify(orderCaseSpec(spec));
|
|
7
|
+
}
|
|
8
|
+
/** Canonical JSON text of a normalized configuration (cases already sorted by id). */
|
|
9
|
+
export function canonicalSyntheticRepositoryConfigText(config) {
|
|
10
|
+
return JSON.stringify({
|
|
11
|
+
schemaVersion: config.schemaVersion,
|
|
12
|
+
cases: config.cases.map(orderCaseSpec),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* SHA-256 over the UTF-8 bytes of five newline-joined lines: schema id, schema version, identity
|
|
17
|
+
* algorithm version, a domain tag ("case" or "config") and the canonical JSON payload. JSON escapes every
|
|
18
|
+
* newline, so the payload cannot forge an earlier line.
|
|
19
|
+
*/
|
|
20
|
+
function hashDomain(domain, payload) {
|
|
21
|
+
const text = [
|
|
22
|
+
SYNTHETIC_REPOSITORY_CONFIG_SCHEMA_ID,
|
|
23
|
+
SYNTHETIC_REPOSITORY_CONFIG_SCHEMA_VERSION,
|
|
24
|
+
SYNTHETIC_REPOSITORY_IDENTITY_ALGORITHM_VERSION,
|
|
25
|
+
domain,
|
|
26
|
+
payload,
|
|
27
|
+
].join("\n");
|
|
28
|
+
return createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
/** Per-case generation identity (lowercase 64-character SHA-256 hex). Throws on an invalid case. */
|
|
31
|
+
export function computeSyntheticRepositoryCaseIdentity(input) {
|
|
32
|
+
const result = validateSyntheticRepositoryCaseSpec(input);
|
|
33
|
+
if (!result.ok)
|
|
34
|
+
throw new SyntheticRepositoryConfigError(result.errors);
|
|
35
|
+
return hashDomain("case", canonicalSyntheticRepositoryCaseText(result.spec));
|
|
36
|
+
}
|
|
37
|
+
/** Whole-configuration identity over the normalized form; input case order never matters. */
|
|
38
|
+
export function computeSyntheticRepositoryConfigIdentity(input) {
|
|
39
|
+
const config = normalizeSyntheticRepositoryConfig(input);
|
|
40
|
+
return hashDomain("config", canonicalSyntheticRepositoryConfigText(config));
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export { SYNTHETIC_REPOSITORY_CASE_FIELDS, SYNTHETIC_REPOSITORY_LIMITS, compareCodeUnits, maxImportEdges, normalizeSyntheticRepositoryConfig, validateSyntheticRepositoryCaseSpec, validateSyntheticRepositoryConfig, } from "./config.js";
|
|
3
|
+
export { canonicalSyntheticRepositoryCaseText, canonicalSyntheticRepositoryConfigText, computeSyntheticRepositoryCaseIdentity, computeSyntheticRepositoryConfigIdentity, } from "./identity.js";
|
|
4
|
+
export { createSyntheticRepositoryPrng, syntheticRepositoryPrngInitialState } from "./prng.js";
|
|
5
|
+
export { planTopology } from "./topology.js";
|
|
6
|
+
export { planSyntheticRepositories, planSyntheticRepositoryCase, verifySyntheticRepositoryPlan } from "./planning.js";
|
|
7
|
+
export { SyntheticRepositoryRenderError } from "./renderShared.js";
|
|
8
|
+
export { renderTypeScriptRepository } from "./renderTypeScript.js";
|
|
9
|
+
export { pythonModuleName, renderPythonRepository } from "./renderPython.js";
|
|
10
|
+
export { renderSyntheticRepository } from "./renderRepository.js";
|
|
11
|
+
export { inspectRepositoryFiles } from "./inspection.js";
|
|
12
|
+
export { deriveSyntheticRepositoryLayout, describeSyntheticTask } from "./layout.js";
|
|
13
|
+
export { SYNTHETIC_REPOSITORY_MANIFEST_FILE_NAME, SYNTHETIC_REPOSITORY_MANIFEST_SCHEMA_ID, SYNTHETIC_REPOSITORY_MANIFEST_SCHEMA_VERSION, SYNTHETIC_REPOSITORY_REPOSITORY_DIRECTORY, buildSyntheticRepositoryManifest, computeRepositoryContentIdentity, serializeSyntheticRepositoryManifest, validateSyntheticRepositoryManifest, } from "./manifest.js";
|
|
14
|
+
export { verifySyntheticRepositoryMaterialization } from "./manifestVerification.js";
|
|
15
|
+
export { buildSyntheticAnswerKey, buildSyntheticEvaluationCase } from "./evaluationCase.js";
|
|
16
|
+
export { SyntheticRepositoryMaterializationError, materializeSyntheticRepository } from "./materialize.js";
|