@dailephd/my-dev-kit-lab 0.6.2 → 0.7.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 (138) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +11 -8
  3. package/benchmarks/contracts/context-window-scaling-cases.json +220 -0
  4. package/benchmarks/projects/context-window-scaling-fixed-ts/README.md +14 -0
  5. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/carrier-depot-catalog-01.ts +302 -0
  6. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/course-module-catalog-06.ts +302 -0
  7. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/library-shelf-catalog-07.ts +302 -0
  8. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/sensor-station-catalog-05.ts +302 -0
  9. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/staff-rota-catalog-03.ts +302 -0
  10. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/supplier-contract-catalog-02.ts +302 -0
  11. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/vehicle-route-catalog-04.ts +302 -0
  12. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/archive-box-catalog-12.ts +302 -0
  13. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/clinic-ward-catalog-08.ts +302 -0
  14. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/greenhouse-bay-catalog-13.ts +302 -0
  15. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/harbor-berth-catalog-10.ts +302 -0
  16. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/kitchen-section-catalog-15.ts +302 -0
  17. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/market-stall-catalog-09.ts +302 -0
  18. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/station-platform-catalog-14.ts +302 -0
  19. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/workshop-bench-catalog-11.ts +302 -0
  20. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/archive-box-catalog-28.ts +302 -0
  21. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/carrier-depot-catalog-17.ts +302 -0
  22. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/clinic-ward-catalog-24.ts +302 -0
  23. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/course-module-catalog-22.ts +302 -0
  24. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/greenhouse-bay-catalog-29.ts +302 -0
  25. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/harbor-berth-catalog-26.ts +302 -0
  26. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/kitchen-section-catalog-31.ts +302 -0
  27. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/library-shelf-catalog-23.ts +302 -0
  28. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/market-stall-catalog-25.ts +302 -0
  29. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/sensor-station-catalog-21.ts +302 -0
  30. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/staff-rota-catalog-19.ts +302 -0
  31. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/station-platform-catalog-30.ts +302 -0
  32. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/supplier-contract-catalog-18.ts +302 -0
  33. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/vehicle-route-catalog-20.ts +302 -0
  34. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/warehouse-zone-catalog-16.ts +302 -0
  35. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/workshop-bench-catalog-27.ts +302 -0
  36. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/archive-box-catalog-44.ts +302 -0
  37. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-33.ts +302 -0
  38. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-49.ts +302 -0
  39. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-40.ts +302 -0
  40. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-56.ts +302 -0
  41. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-38.ts +302 -0
  42. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-54.ts +302 -0
  43. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/greenhouse-bay-catalog-45.ts +302 -0
  44. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-42.ts +302 -0
  45. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-58.ts +302 -0
  46. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/kitchen-section-catalog-47.ts +302 -0
  47. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-39.ts +302 -0
  48. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-55.ts +302 -0
  49. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-41.ts +302 -0
  50. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-57.ts +302 -0
  51. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-37.ts +302 -0
  52. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-53.ts +302 -0
  53. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-35.ts +302 -0
  54. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-51.ts +302 -0
  55. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/station-platform-catalog-46.ts +302 -0
  56. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-34.ts +302 -0
  57. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-50.ts +302 -0
  58. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-36.ts +302 -0
  59. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-52.ts +302 -0
  60. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-32.ts +302 -0
  61. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-48.ts +302 -0
  62. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/workshop-bench-catalog-43.ts +302 -0
  63. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/auditRedaction.ts +25 -0
  64. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/inventoryReservation.ts +26 -0
  65. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/invoiceTotals.ts +25 -0
  66. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/shippingQuote.ts +32 -0
  67. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/auditRedaction.test.ts +17 -0
  68. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/inventoryReservation.test.ts +19 -0
  69. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/invoiceTotals.test.ts +14 -0
  70. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/shippingQuote.test.ts +19 -0
  71. package/dist/src/cli/help.js +27 -10
  72. package/dist/src/commands/runExperimentDescribeCommand.js +5 -2
  73. package/dist/src/commands/runExperimentRunCommand.js +64 -8
  74. package/dist/src/evaluation/benchmarkMetadata.js +5 -1
  75. package/dist/src/evaluation/parseAgentAnswer.js +172 -24
  76. package/dist/src/evaluation/runMyDevKitRetrieval.js +120 -7
  77. package/dist/src/experiments/defaultRegistry.js +2 -0
  78. package/dist/src/experiments/plugins/contextWindowScaling/cliBudgets.js +31 -0
  79. package/dist/src/experiments/plugins/contextWindowScaling/config.js +84 -0
  80. package/dist/src/experiments/plugins/contextWindowScaling/contextBudget.js +43 -0
  81. package/dist/src/experiments/plugins/contextWindowScaling/evaluation.js +36 -0
  82. package/dist/src/experiments/plugins/contextWindowScaling/execution.js +187 -0
  83. package/dist/src/experiments/plugins/contextWindowScaling/executionArtifact.js +15 -0
  84. package/dist/src/experiments/plugins/contextWindowScaling/executionArtifactReader.js +186 -0
  85. package/dist/src/experiments/plugins/contextWindowScaling/index.js +14 -0
  86. package/dist/src/experiments/plugins/contextWindowScaling/metadata.js +12 -0
  87. package/dist/src/experiments/plugins/contextWindowScaling/metrics.js +149 -0
  88. package/dist/src/experiments/plugins/contextWindowScaling/plugin.js +185 -0
  89. package/dist/src/experiments/plugins/contextWindowScaling/projectProfile.js +51 -0
  90. package/dist/src/experiments/plugins/contextWindowScaling/relevantFiles.js +76 -0
  91. package/dist/src/experiments/plugins/contextWindowScaling/successEvidence.js +60 -0
  92. package/dist/src/experiments/plugins/contextWindowScaling/types.js +1 -0
  93. package/dist/src/experiments/plugins/incrementalChangeStaleness/comparisonV2.js +49 -0
  94. package/dist/src/experiments/plugins/incrementalChangeStaleness/config.js +5 -4
  95. package/dist/src/experiments/plugins/incrementalChangeStaleness/disposableTarget.js +20 -2
  96. package/dist/src/experiments/plugins/incrementalChangeStaleness/execution.js +42 -16
  97. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifact.js +4 -4
  98. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifactV2.js +289 -0
  99. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionV2.js +195 -0
  100. package/dist/src/experiments/plugins/incrementalChangeStaleness/indexClone.js +76 -0
  101. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycle.js +64 -32
  102. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicy.js +6 -4
  103. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicyV2.js +78 -0
  104. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycleV2.js +298 -0
  105. package/dist/src/experiments/plugins/incrementalChangeStaleness/plugin.js +213 -12
  106. package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSessionV2.js +5 -0
  107. package/dist/src/experiments/plugins/index.js +1 -0
  108. package/dist/src/experiments/plugins/warmIndexReuse/agentEvaluation.js +1 -1
  109. package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +3 -1
  110. package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +9 -2
  111. package/dist/src/mobile/android/advancedSecurity/redaction.js +19 -10
  112. package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +12 -2
  113. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +1 -1
  114. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +7 -7
  115. package/dist/src/plots/buildContextWindowScalingPlotData.js +96 -0
  116. package/dist/src/plots/index.js +1 -0
  117. package/dist/src/plots/writePlotArtifacts.js +12 -3
  118. package/dist/src/report/experiments/buildContextWindowScalingReport.js +84 -0
  119. package/dist/src/report/experiments/buildIncrementalChangeStalenessPluginReport.js +33 -0
  120. package/dist/src/report/experiments/buildIncrementalChangeStalenessReport.js +1 -1
  121. package/dist/src/report/experiments/buildIncrementalChangeStalenessReportV2.js +80 -0
  122. package/dist/src/report/experiments/buildPluginExperimentReport.js +24 -4
  123. package/dist/src/report/experiments/contextWindowScalingReportModel.js +3 -0
  124. package/dist/src/report/experiments/incrementalChangeStalenessReportModelV2.js +3 -0
  125. package/dist/src/report/experiments/incrementalChangeStalenessReportRowsV2.js +146 -0
  126. package/dist/src/report/experiments/index.js +7 -0
  127. package/dist/src/report/experiments/renderContextWindowScalingHtml.js +99 -0
  128. package/dist/src/report/experiments/renderContextWindowScalingText.js +108 -0
  129. package/dist/src/report/experiments/renderIncrementalChangeStalenessHtml.js +4 -0
  130. package/dist/src/report/experiments/renderIncrementalChangeStalenessHtmlV2.js +111 -0
  131. package/dist/src/report/experiments/renderIncrementalChangeStalenessTextV2.js +96 -0
  132. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  133. package/dist/src/report/experiments/renderPluginExperimentReportText.js +14 -0
  134. package/dist/src/securityValidation/staticScans/codeql.js +8 -8
  135. package/docs/METRICS.md +58 -0
  136. package/examples/tutorial-browser/prepare.mjs +13 -13
  137. package/examples/tutorial-browser/server.mjs +13 -13
  138. package/package.json +1 -1
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import { parseAgentCommandTemplate } from "../agents/index.js";
3
3
  import { parseAgentId } from "../agents/agentRegistry.js";
4
4
  import { readBenchmarkProjectProfiles, readEvaluationCases } from "../evaluation/index.js";
5
- import { contextStrategyComparisonPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, incrementalChangeStalenessPlugin, parseWarmIndexCampaignPresetId, resolveExperimentTarget, runExperiment, warmIndexReusePlugin } from "../experiments/index.js";
5
+ import { contextStrategyComparisonPlugin, contextWindowScalingPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, incrementalChangeStalenessPlugin, parseContextBudgetsCliValue, parseWarmIndexCampaignPresetId, resolveExperimentTarget, runExperiment, warmIndexReusePlugin } from "../experiments/index.js";
6
6
  import { buildDefaultExperimentOutputRoot } from "../experiments/outputPaths.js";
7
7
  import { parsePromptComplexityLevel, parsePromptStrategy } from "../prompts/index.js";
8
8
  import { writePluginExperimentReports } from "../report/index.js";
@@ -32,6 +32,11 @@ import { runWarmIndexCampaignPresentation } from "./runWarmIndexCampaignPresenta
32
32
  // ---------------------------------------------------------------------------
33
33
  const DEFAULT_CASES_RESOURCE = "examples/token-savings-cases.json";
34
34
  const DEFAULT_PROJECT_PROFILES_RESOURCE = "benchmarks/contracts/benchmark-project-profiles.json";
35
+ // context-window-scaling owns one frozen bundled case catalog; its fixed project is referenced by the
36
+ // catalog's package-relative targetRoot, so both resolve from the package root, never the cwd.
37
+ const CONTEXT_WINDOW_SCALING_CASES_RESOURCE = "benchmarks/contracts/context-window-scaling-cases.json";
38
+ // Options the context-window-scaling plugin accepts; every other flag is rejected, not ignored.
39
+ const CONTEXT_WINDOW_SCALING_ALLOWED_FLAGS = ["--experiment", "--out", "--case", "--context-budgets", "--kit-command"];
35
40
  export async function runExperimentRunCommandFromArgs(argv, options = {}) {
36
41
  try {
37
42
  const args = parseRunExperimentArgs(argv);
@@ -52,7 +57,7 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
52
57
  registry,
53
58
  targetPath: args.targetPath,
54
59
  outputRoot,
55
- config: args.config,
60
+ config: pluginConfigFor(args),
56
61
  inputs,
57
62
  toolRoot,
58
63
  runId
@@ -76,7 +81,6 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
76
81
  // Automatic presentation (report already produced above) applies only to warm-index-reuse
77
82
  // campaign runs; legacy fake-agent warm-index runs and context-strategy-comparison stay
78
83
  // report-only, unchanged.
79
- let screenshotFailed = false;
80
84
  if (args.experimentId === warmIndexReusePlugin.metadata.id && args.config.campaignPreset) {
81
85
  const executionArtifactPath = readMetadataString(result.metadata?.executionArtifactPath);
82
86
  const campaignAgentId = readMetadataString(result.metadata?.campaignAgentId);
@@ -106,7 +110,6 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
106
110
  else {
107
111
  outputLines.push("Screenshot: failed");
108
112
  outputLines.push(presentation.screenshot.error ?? "Screenshot capture failed.");
109
- screenshotFailed = true;
110
113
  }
111
114
  outputLines.push(`Gallery manifest: ${presentation.gallery.manifestPath}`);
112
115
  outputLines.push(`Gallery index: ${presentation.gallery.indexPath}`);
@@ -114,8 +117,6 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
114
117
  console.log(outputLines.join("\n"));
115
118
  if (result.status === "failed")
116
119
  return 1;
117
- if (screenshotFailed)
118
- return 1;
119
120
  return 0;
120
121
  }
121
122
  catch (error) {
@@ -146,9 +147,13 @@ export function parseRunExperimentArgs(argv) {
146
147
  let includeRealAgents;
147
148
  let kitCommand;
148
149
  let campaignPreset;
150
+ let contextBudgets;
151
+ const seenFlags = [];
149
152
  const commandTemplates = {};
150
153
  for (let index = 0; index < argv.length; index += 1) {
151
154
  const arg = argv[index];
155
+ if (arg !== undefined && arg.startsWith("--"))
156
+ seenFlags.push(arg);
152
157
  if (arg === "--experiment") {
153
158
  experimentId = readRequiredValue(argv, ++index, "--experiment");
154
159
  }
@@ -206,6 +211,9 @@ export function parseRunExperimentArgs(argv) {
206
211
  else if (arg === "--kit-command") {
207
212
  kitCommand = readRequiredValue(argv, ++index, "--kit-command");
208
213
  }
214
+ else if (arg === "--context-budgets") {
215
+ contextBudgets = parseContextBudgetsCliValue(readRequiredValue(argv, ++index, "--context-budgets"));
216
+ }
209
217
  else if (arg === "--campaign") {
210
218
  campaignPreset = parseWarmIndexCampaignPresetId(readRequiredValue(argv, ++index, "--campaign"));
211
219
  }
@@ -220,7 +228,23 @@ export function parseRunExperimentArgs(argv) {
220
228
  if (!experimentId) {
221
229
  throw new Error("Usage: --experiment <id> [--target <path>] [--out <directory>]");
222
230
  }
223
- const KIT_COMMAND_PLUGIN_IDS = [warmIndexReusePlugin.metadata.id, incrementalChangeStalenessPlugin.metadata.id];
231
+ const KIT_COMMAND_PLUGIN_IDS = [
232
+ warmIndexReusePlugin.metadata.id,
233
+ incrementalChangeStalenessPlugin.metadata.id,
234
+ contextWindowScalingPlugin.metadata.id
235
+ ];
236
+ if (contextBudgets !== undefined && experimentId !== contextWindowScalingPlugin.metadata.id) {
237
+ throw new Error(`--context-budgets is only supported for --experiment ${contextWindowScalingPlugin.metadata.id}.`);
238
+ }
239
+ if (experimentId === contextWindowScalingPlugin.metadata.id) {
240
+ const unsupported = [...new Set(seenFlags.filter((flag) => !CONTEXT_WINDOW_SCALING_ALLOWED_FLAGS.includes(flag)))];
241
+ if (unsupported.length > 0) {
242
+ throw new Error(`${unsupported.join(", ")} ${unsupported.length === 1 ? "is" : "are"} not supported for --experiment ${experimentId}; supported options: ${CONTEXT_WINDOW_SCALING_ALLOWED_FLAGS.join(", ")}.`);
243
+ }
244
+ if (seenFlags.includes("--case") && caseIds.length === 0) {
245
+ throw new Error("--case must list at least one case id.");
246
+ }
247
+ }
224
248
  if (kitCommand !== undefined && !KIT_COMMAND_PLUGIN_IDS.includes(experimentId)) {
225
249
  throw new Error(`--kit-command is only supported for --experiment ${KIT_COMMAND_PLUGIN_IDS.join(" or ")}.`);
226
250
  }
@@ -257,7 +281,8 @@ export function parseRunExperimentArgs(argv) {
257
281
  includeRealAgents,
258
282
  commandTemplates: Object.keys(commandTemplates).length > 0 ? commandTemplates : undefined,
259
283
  kitCommand,
260
- campaignPreset
284
+ campaignPreset,
285
+ contextBudgets
261
286
  })
262
287
  };
263
288
  }
@@ -301,8 +326,39 @@ async function loadPluginInputs(args, toolRoot, context) {
301
326
  }
302
327
  return loadCasesAndProjectProfiles(args, toolRoot, context);
303
328
  }
329
+ if (args.experimentId === contextWindowScalingPlugin.metadata.id) {
330
+ return loadContextWindowScalingInputs(args, toolRoot, context);
331
+ }
304
332
  return undefined;
305
333
  }
334
+ // --case is a selection over the frozen bundled catalog (catalog order, not argument order);
335
+ // the rest of the CLI config is the plugin's own ContextWindowScalingConfig.
336
+ function pluginConfigFor(args) {
337
+ if (args.experimentId !== contextWindowScalingPlugin.metadata.id)
338
+ return args.config;
339
+ const { caseIds: _caseIds, ...config } = args.config;
340
+ return config;
341
+ }
342
+ async function loadContextWindowScalingInputs(args, toolRoot, context) {
343
+ const validation = contextWindowScalingPlugin.validateConfig(pluginConfigFor(args));
344
+ if (!validation.valid || !validation.config) {
345
+ throw new Error(`Invalid context window scaling config: ${validation.errors.join("; ")}`);
346
+ }
347
+ const catalog = await readEvaluationCases(resolvePackageResource(context, CONTEXT_WINDOW_SCALING_CASES_RESOURCE), toolRoot);
348
+ const requested = args.config.caseIds;
349
+ if (requested === undefined)
350
+ return { cases: catalog, env: process.env };
351
+ const duplicates = [...new Set(requested.filter((id, position) => requested.indexOf(id) !== position))];
352
+ if (duplicates.length > 0) {
353
+ throw new Error(`--case contains duplicate case id(s): ${duplicates.join(", ")}.`);
354
+ }
355
+ const known = new Set(catalog.map((evaluationCase) => evaluationCase.id));
356
+ const unknown = requested.filter((id) => !known.has(id));
357
+ if (unknown.length > 0) {
358
+ throw new Error(`--case contains unknown case id(s): ${unknown.join(", ")}. Expected one of ${catalog.map((evaluationCase) => evaluationCase.id).join(", ")}.`);
359
+ }
360
+ return { cases: catalog.filter((evaluationCase) => requested.includes(evaluationCase.id)), env: process.env };
361
+ }
306
362
  // Shared by both registered plugins: explicit paths resolve against toolRoot, defaults come from
307
363
  // bundled package resources so they do not depend on the invocation cwd.
308
364
  async function loadCasesAndProjectProfiles(args, toolRoot, context) {
@@ -280,7 +280,11 @@ function validateWarmIndexSourceRoots(sourceRoots, profile, label) {
280
280
  return errors;
281
281
  }
282
282
  function normalizeRelativeRoot(value) {
283
- return path.posix.normalize(value.replace(/\\/g, "/")).replace(/\/+$/, "");
283
+ const normalized = path.posix.normalize(value.replaceAll("\\", "/"));
284
+ let end = normalized.length;
285
+ while (end > 0 && normalized[end - 1] === "/")
286
+ end -= 1;
287
+ return normalized.slice(0, end);
284
288
  }
285
289
  function sameOrderedValues(left, right) {
286
290
  return left.length === right.length && left.every((value, index) => value === right[index]);
@@ -77,11 +77,24 @@ function collectJsonCandidates(text) {
77
77
  if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
78
78
  candidates.push(trimmed);
79
79
  }
80
- for (const match of text.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)) {
81
- const body = match[1]?.trim();
80
+ let cursor = 0;
81
+ while (cursor < text.length) {
82
+ const opening = text.indexOf("```", cursor);
83
+ if (opening < 0)
84
+ break;
85
+ let bodyStart = opening + 3;
86
+ if (text.slice(bodyStart, bodyStart + 4).toLowerCase() === "json")
87
+ bodyStart += 4;
88
+ while (bodyStart < text.length && isWhitespace(text[bodyStart]))
89
+ bodyStart += 1;
90
+ const closing = text.indexOf("```", bodyStart);
91
+ if (closing < 0)
92
+ break;
93
+ const body = text.slice(bodyStart, closing).trim();
82
94
  if (body?.startsWith("{")) {
83
95
  candidates.push(body);
84
96
  }
97
+ cursor = closing + 3;
85
98
  }
86
99
  return candidates;
87
100
  }
@@ -89,39 +102,103 @@ function collectFieldValues(text) {
89
102
  const fields = new Map();
90
103
  let currentKey;
91
104
  for (const line of text.split(/\r?\n/)) {
92
- const fieldMatch = line.match(/^\s*(?:[-*]\s*)?(?:[*_`]{0,2})([A-Za-z][A-Za-z0-9 _-]{1,40})(?:[*_`]{0,2})\s*:\s*(.*)$/);
93
- if (fieldMatch) {
94
- currentKey = normalizeKey(fieldMatch[1]);
105
+ const field = parseStructuredFieldLine(line);
106
+ if (field) {
107
+ currentKey = normalizeKey(field.label);
95
108
  const current = fields.get(currentKey) ?? [];
96
- current.push(stripMarkupOnlyValue(fieldMatch[2] ?? ""));
109
+ current.push(stripMarkupOnlyValue(field.value));
97
110
  fields.set(currentKey, current);
98
111
  continue;
99
112
  }
100
- if (currentKey && (/^\s+[-*]?\s*\S/.test(line) || /^\s*[-*]\s+\S/.test(line))) {
101
- fields.get(currentKey)?.push(line.trim().replace(/^[-*]\s*/, ""));
113
+ if (currentKey) {
114
+ const continuation = parseFieldContinuation(line);
115
+ if (continuation !== undefined)
116
+ fields.get(currentKey)?.push(continuation);
102
117
  }
103
118
  }
104
119
  return fields;
105
120
  }
121
+ function isWhitespace(character) {
122
+ return character.trim().length === 0;
123
+ }
124
+ function parseStructuredFieldLine(line) {
125
+ let start = 0;
126
+ while (start < line.length && isWhitespace(line[start]))
127
+ start += 1;
128
+ if (line[start] === "-" || line[start] === "*") {
129
+ start += 1;
130
+ while (start < line.length && isWhitespace(line[start]))
131
+ start += 1;
132
+ }
133
+ let leadingMarkup = 0;
134
+ while (start < line.length && leadingMarkup < 2 && "*_`".includes(line[start])) {
135
+ start += 1;
136
+ leadingMarkup += 1;
137
+ }
138
+ const colon = line.indexOf(":", start);
139
+ if (colon < 0)
140
+ return undefined;
141
+ let labelEnd = colon;
142
+ let trailingMarkup = 0;
143
+ while (labelEnd > start && trailingMarkup < 2 && "*_`".includes(line[labelEnd - 1])) {
144
+ labelEnd -= 1;
145
+ trailingMarkup += 1;
146
+ }
147
+ const label = line.slice(start, labelEnd);
148
+ if (label.length < 2 || label.length > 41 || !isAsciiLetter(label[0]))
149
+ return undefined;
150
+ for (let index = 1; index < label.length; index += 1) {
151
+ const character = label[index];
152
+ if (!isAsciiLetter(character) && !(character >= "0" && character <= "9") && character !== " " && character !== "_" && character !== "-")
153
+ return undefined;
154
+ }
155
+ let valueStart = colon + 1;
156
+ while (valueStart < line.length && isWhitespace(line[valueStart]))
157
+ valueStart += 1;
158
+ return { label, value: line.slice(valueStart) };
159
+ }
160
+ function isAsciiLetter(character) {
161
+ return (character >= "a" && character <= "z") || (character >= "A" && character <= "Z");
162
+ }
163
+ function parseFieldContinuation(line) {
164
+ let index = 0;
165
+ while (index < line.length && isWhitespace(line[index]))
166
+ index += 1;
167
+ const indented = index > 0;
168
+ if (index === line.length)
169
+ return undefined;
170
+ if (line[index] === "-" || line[index] === "*") {
171
+ index += 1;
172
+ const whitespaceStart = index;
173
+ while (index < line.length && isWhitespace(line[index]))
174
+ index += 1;
175
+ if ((!indented && index === whitespaceStart) || index === line.length)
176
+ return undefined;
177
+ return line.slice(index).trim();
178
+ }
179
+ if (!indented)
180
+ return undefined;
181
+ return line.slice(index).trim();
182
+ }
106
183
  function parseMarkdownSection(text, headings) {
107
184
  const lines = text.split(/\r?\n/);
108
185
  const values = [];
109
186
  let inSection = false;
110
187
  for (const line of lines) {
111
- const heading = line.match(/^\s*#{1,6}\s*(.+?)\s*$/);
112
- if (heading) {
113
- inSection = headings.some((candidate) => normalizeKey(candidate) === normalizeKey(heading[1]));
188
+ const heading = parseMarkdownHeading(line);
189
+ if (heading !== undefined) {
190
+ inSection = headings.some((candidate) => normalizeKey(candidate) === normalizeKey(heading));
114
191
  continue;
115
192
  }
116
193
  if (!inSection) {
117
194
  continue;
118
195
  }
119
- if (/^\s*#{1,6}\s+/.test(line)) {
196
+ if (isMarkdownHeading(line)) {
120
197
  break;
121
198
  }
122
- const bullet = line.match(/^\s*[-*]\s+(.+)$/);
123
- if (bullet) {
124
- values.push(bullet[1].trim());
199
+ const bullet = parseMarkdownBullet(line);
200
+ if (bullet !== undefined) {
201
+ values.push(bullet);
125
202
  }
126
203
  else if (line.includes(",")) {
127
204
  values.push(...splitListValues([line]));
@@ -129,6 +206,41 @@ function parseMarkdownSection(text, headings) {
129
206
  }
130
207
  return unique(values);
131
208
  }
209
+ function parseMarkdownHeading(line) {
210
+ let index = 0;
211
+ while (index < line.length && isWhitespace(line[index]))
212
+ index += 1;
213
+ const hashStart = index;
214
+ while (index < line.length && line[index] === "#" && index - hashStart < 6)
215
+ index += 1;
216
+ if (index === hashStart || (index < line.length && line[index] === "#"))
217
+ return undefined;
218
+ const heading = line.slice(index).trim();
219
+ return heading.length > 0 ? heading : undefined;
220
+ }
221
+ function isMarkdownHeading(line) {
222
+ let index = 0;
223
+ while (index < line.length && isWhitespace(line[index]))
224
+ index += 1;
225
+ const start = index;
226
+ while (index < line.length && line[index] === "#" && index - start < 6)
227
+ index += 1;
228
+ return index > start && index < line.length && isWhitespace(line[index]);
229
+ }
230
+ function parseMarkdownBullet(line) {
231
+ let index = 0;
232
+ while (index < line.length && isWhitespace(line[index]))
233
+ index += 1;
234
+ if (line[index] !== "-" && line[index] !== "*")
235
+ return undefined;
236
+ index += 1;
237
+ if (index >= line.length || !isWhitespace(line[index]))
238
+ return undefined;
239
+ while (index < line.length && isWhitespace(line[index]))
240
+ index += 1;
241
+ const content = line.slice(index).trim();
242
+ return content.length > 0 ? content : undefined;
243
+ }
132
244
  function enrichFacts(parsed, text, answerKey) {
133
245
  parsed.expectedFactsFound = normalizeFactMatches(parsed.expectedFactsFound, text, answerKey);
134
246
  if (parsed.answerText.length === 0) {
@@ -168,15 +280,51 @@ function splitListValues(values) {
168
280
  .filter(Boolean));
169
281
  }
170
282
  function cleanListItem(value) {
171
- const codeSpan = value.match(/`([^`]+)`/);
172
- const cleaned = (codeSpan?.[1] ?? value)
173
- .trim()
174
- .replace(/^[-*]\s*/, "")
175
- .replace(/^["'`]+|["'`.]+$/g, "")
176
- .replace(/\s+[--]\s+.*$/g, "")
177
- .replace(/\s+[-–—]\s+.*$/g, "")
178
- .trim();
179
- return cleaned;
283
+ let source = value;
284
+ let searchFrom = 0;
285
+ while (searchFrom < value.length) {
286
+ const open = value.indexOf("`", searchFrom);
287
+ if (open < 0)
288
+ break;
289
+ const close = value.indexOf("`", open + 1);
290
+ if (close < 0)
291
+ break;
292
+ if (close > open + 1) {
293
+ source = value.slice(open + 1, close);
294
+ break;
295
+ }
296
+ searchFrom = close + 1;
297
+ }
298
+ let start = 0;
299
+ let end = source.length;
300
+ while (start < end && isWhitespace(source[start]))
301
+ start += 1;
302
+ while (end > start && isWhitespace(source[end - 1]))
303
+ end -= 1;
304
+ if (source[start] === "-" || source[start] === "*") {
305
+ start += 1;
306
+ while (start < end && isWhitespace(source[start]))
307
+ start += 1;
308
+ }
309
+ while (start < end && (source[start] === '"' || source[start] === "'" || source[start] === "`"))
310
+ start += 1;
311
+ while (end > start && (source[end - 1] === '"' || source[end - 1] === "'" || source[end - 1] === "`" || source[end - 1] === "."))
312
+ end -= 1;
313
+ let whitespaceStart = -1;
314
+ for (let index = start; index < end; index += 1) {
315
+ const character = source[index];
316
+ if (isWhitespace(character)) {
317
+ if (whitespaceStart < 0)
318
+ whitespaceStart = index;
319
+ continue;
320
+ }
321
+ if ((character === "-" || character === "–" || character === "—") && whitespaceStart >= start && index + 1 < end && isWhitespace(source[index + 1])) {
322
+ end = whitespaceStart;
323
+ break;
324
+ }
325
+ whitespaceStart = -1;
326
+ }
327
+ return source.slice(start, end).trim();
180
328
  }
181
329
  function stripMarkupOnlyValue(value) {
182
330
  return /^[*_`\s]+$/.test(value) ? "" : value;
@@ -55,13 +55,118 @@ function skippedRetrieval(caseId, warnings, commands, durationMs) {
55
55
  durationMs
56
56
  };
57
57
  }
58
+ const APPLIED_SCOPES = ["none", "changed-files", "affected-neighborhood", "full"];
59
+ const SELECTION_STATUSES = ["not-needed", "applied", "fallback-full"];
60
+ const NEIGHBORHOOD_COUNT_KEYS = ["seedFileCount", "seedSymbolCount", "affectedNodeCount", "affectedEdgeCount"];
61
+ function isNonNegativeInteger(value) {
62
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
63
+ }
64
+ /**
65
+ * Validates the per-invocation `incrementalRefresh` object from `my-dev-kit index --incremental --json`
66
+ * stdout against the scope Lab requested. Returns validated evidence or an explicit error; never coerces.
67
+ * The fallback reason is intentionally an open string: upstream reason codes may evolve additively.
68
+ */
69
+ export function parseIncrementalRefreshEvidence(stdout, requestedScope) {
70
+ const payload = parseJsonIfPossible(stdout);
71
+ if (payload === undefined)
72
+ return { ok: false, error: "index stdout is not valid JSON" };
73
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
74
+ return { ok: false, error: "index JSON result is not an object" };
75
+ }
76
+ const raw = payload.incrementalRefresh;
77
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
78
+ return { ok: false, error: "index JSON result has no incrementalRefresh object" };
79
+ }
80
+ const r = raw;
81
+ if (r.requestedScope !== requestedScope) {
82
+ return {
83
+ ok: false,
84
+ error: `incrementalRefresh.requestedScope ${JSON.stringify(r.requestedScope)} does not match requested scope ${requestedScope}`
85
+ };
86
+ }
87
+ if (!APPLIED_SCOPES.includes(r.appliedScope)) {
88
+ return { ok: false, error: `incrementalRefresh.appliedScope ${JSON.stringify(r.appliedScope)} is not a known value` };
89
+ }
90
+ if (!SELECTION_STATUSES.includes(r.selectionStatus)) {
91
+ return { ok: false, error: `incrementalRefresh.selectionStatus ${JSON.stringify(r.selectionStatus)} is not a known value` };
92
+ }
93
+ if (r.fallbackReason !== null && typeof r.fallbackReason !== "string") {
94
+ return { ok: false, error: "incrementalRefresh.fallbackReason must be a string or null" };
95
+ }
96
+ for (const key of NEIGHBORHOOD_COUNT_KEYS) {
97
+ if (r[key] !== null && !isNonNegativeInteger(r[key])) {
98
+ return { ok: false, error: `incrementalRefresh.${key} must be a non-negative integer or null` };
99
+ }
100
+ }
101
+ for (const key of ["forcedNeighborReanalysisFileCount", "freshExtractionFileCount", "reusedFileCount"]) {
102
+ if (!isNonNegativeInteger(r[key])) {
103
+ return { ok: false, error: `incrementalRefresh.${key} must be a non-negative integer` };
104
+ }
105
+ }
106
+ if (!Array.isArray(r.forcedNeighborSample) || r.forcedNeighborSample.some((item) => typeof item !== "string")) {
107
+ return { ok: false, error: "incrementalRefresh.forcedNeighborSample must be an array of strings" };
108
+ }
109
+ const appliedScope = r.appliedScope;
110
+ const selectionStatus = r.selectionStatus;
111
+ const fallbackReason = r.fallbackReason;
112
+ if (selectionStatus === "applied") {
113
+ if (appliedScope !== requestedScope) {
114
+ return { ok: false, error: "incrementalRefresh applied status requires appliedScope equal to requestedScope" };
115
+ }
116
+ if (fallbackReason !== null)
117
+ return { ok: false, error: "incrementalRefresh applied status requires a null fallbackReason" };
118
+ }
119
+ else if (selectionStatus === "fallback-full") {
120
+ if (appliedScope !== "full")
121
+ return { ok: false, error: "incrementalRefresh fallback-full status requires appliedScope full" };
122
+ if (fallbackReason === null || fallbackReason.length === 0) {
123
+ return { ok: false, error: "incrementalRefresh fallback-full status requires a nonempty fallbackReason" };
124
+ }
125
+ }
126
+ else {
127
+ if (appliedScope !== "none")
128
+ return { ok: false, error: "incrementalRefresh not-needed status requires appliedScope none" };
129
+ if (fallbackReason !== null)
130
+ return { ok: false, error: "incrementalRefresh not-needed status requires a null fallbackReason" };
131
+ }
132
+ if (requestedScope === "affected-neighborhood" && selectionStatus === "applied") {
133
+ for (const key of NEIGHBORHOOD_COUNT_KEYS) {
134
+ if (r[key] === null) {
135
+ return { ok: false, error: `incrementalRefresh.${key} must be numeric for an applied affected-neighborhood refresh` };
136
+ }
137
+ }
138
+ }
139
+ return {
140
+ ok: true,
141
+ evidence: {
142
+ requestedScope,
143
+ appliedScope,
144
+ selectionStatus,
145
+ fallbackReason,
146
+ seedFileCount: r.seedFileCount,
147
+ seedSymbolCount: r.seedSymbolCount,
148
+ affectedNodeCount: r.affectedNodeCount,
149
+ affectedEdgeCount: r.affectedEdgeCount,
150
+ forcedNeighborReanalysisFileCount: r.forcedNeighborReanalysisFileCount,
151
+ forcedNeighborSample: [...r.forcedNeighborSample],
152
+ freshExtractionFileCount: r.freshExtractionFileCount,
153
+ reusedFileCount: r.reusedFileCount
154
+ }
155
+ };
156
+ }
58
157
  /**
59
158
  * Runs exactly one my-dev-kit `index` command for the target and writes the index only to
60
159
  * `indexDir`. Performs no search/lookup/slice/source work. With `requireKit` a failed index
61
160
  * throws; otherwise the failure is returned as `ok: false` with a warning.
161
+ *
162
+ * `mode` defaults to a full index. An incremental mode adds `--incremental --refresh-scope <scope>`
163
+ * and requires valid per-invocation `incrementalRefresh` JSON evidence on stdout; a successful
164
+ * process without that evidence is not a usable incremental build. Upstream full-fallback and
165
+ * no-change outcomes are valid evidence and are returned as-is.
62
166
  */
63
167
  export async function buildMyDevKitIndex(options) {
64
168
  const warnings = [];
169
+ const mode = options.mode ?? { kind: "full" };
65
170
  const command = await runMeasuredCommand({
66
171
  commandId: "index",
67
172
  commandString: options.kitCommand,
@@ -74,22 +179,30 @@ export async function buildMyDevKitIndex(options) {
74
179
  ...options.target.sourceRoots.flatMap((sourceRoot) => ["--src", sourceRoot]),
75
180
  "--out",
76
181
  options.indexDir,
182
+ ...(mode.kind === "incremental" ? ["--incremental", "--refresh-scope", mode.refreshScope] : []),
77
183
  "--json"
78
184
  ]
79
185
  });
186
+ const result = { indexDir: options.indexDir, durationMs: command.durationMs, command, mode };
80
187
  if (!command.ok) {
81
188
  if (options.requireKit) {
82
189
  throw new Error(command.error || `my-dev-kit index failed with exit code ${command.exitCode}`);
83
190
  }
84
191
  warnings.push("my-dev-kit index command was unavailable or failed.");
192
+ return { ...result, ok: false, warnings, incrementalRefresh: null };
85
193
  }
86
- return {
87
- ok: command.ok,
88
- indexDir: options.indexDir,
89
- durationMs: command.durationMs,
90
- warnings,
91
- command
92
- };
194
+ if (mode.kind === "full") {
195
+ return { ...result, ok: true, warnings, incrementalRefresh: null };
196
+ }
197
+ const parsed = parseIncrementalRefreshEvidence(command.stdout, mode.refreshScope);
198
+ if (!parsed.ok) {
199
+ const message = `my-dev-kit incremental index (${mode.refreshScope}) returned no valid incrementalRefresh evidence: ${parsed.error}`;
200
+ if (options.requireKit)
201
+ throw new Error(message);
202
+ warnings.push(message);
203
+ return { ...result, ok: false, warnings, incrementalRefresh: null };
204
+ }
205
+ return { ...result, ok: true, warnings, incrementalRefresh: parsed.evidence };
93
206
  }
94
207
  /**
95
208
  * Runs `<kit-command> --version` once through the shared measured-command runner and returns
@@ -1,5 +1,6 @@
1
1
  import { ExperimentPluginRegistry } from "./registry.js";
2
2
  import { contextStrategyComparisonPlugin } from "./plugins/contextStrategyComparison/index.js";
3
+ import { contextWindowScalingPlugin } from "./plugins/contextWindowScaling/index.js";
3
4
  import { incrementalChangeStalenessPlugin } from "./plugins/incrementalChangeStaleness/index.js";
4
5
  import { warmIndexReusePlugin } from "./plugins/warmIndexReuse/index.js";
5
6
  export function createDefaultExperimentPluginRegistry() {
@@ -7,5 +8,6 @@ export function createDefaultExperimentPluginRegistry() {
7
8
  registry.register(contextStrategyComparisonPlugin);
8
9
  registry.register(warmIndexReusePlugin);
9
10
  registry.register(incrementalChangeStalenessPlugin);
11
+ registry.register(contextWindowScalingPlugin);
10
12
  return registry;
11
13
  }
@@ -0,0 +1,31 @@
1
+ import { validateContextBudgets } from "./config.js";
2
+ import { CONTEXT_BUDGET_16K, CONTEXT_BUDGET_32K, CONTEXT_BUDGET_64K, CONTEXT_BUDGET_8K } from "./contextBudget.js";
3
+ /** Closed, lowercase CLI vocabulary. "8k" is 8192 (binary), never 8000. */
4
+ const SYMBOLIC_CONTEXT_BUDGETS = Object.freeze({
5
+ "8k": CONTEXT_BUDGET_8K,
6
+ "16k": CONTEXT_BUDGET_16K,
7
+ "32k": CONTEXT_BUDGET_32K,
8
+ "64k": CONTEXT_BUDGET_64K,
9
+ });
10
+ const CANONICAL_POSITIVE_INTEGER = /^[1-9][0-9]*$/;
11
+ /**
12
+ * Converts the comma-separated --context-budgets value to numbers (symbolic or canonical base-10
13
+ * integers only), then delegates all domain rules (positive safe integer, no duplicates, ascending
14
+ * order) to validateContextBudgets. Throws with the CLI flag name on any invalid input.
15
+ */
16
+ export function parseContextBudgetsCliValue(value) {
17
+ const tokens = value.split(",").map((token) => token.trim());
18
+ const numbers = tokens.map((token) => {
19
+ const symbolic = Object.hasOwn(SYMBOLIC_CONTEXT_BUDGETS, token) ? SYMBOLIC_CONTEXT_BUDGETS[token] : undefined;
20
+ if (symbolic !== undefined)
21
+ return symbolic;
22
+ if (CANONICAL_POSITIVE_INTEGER.test(token))
23
+ return Number(token);
24
+ throw new Error(`--context-budgets value ${JSON.stringify(token.length > 20 ? `${token.slice(0, 20)}...` : token)} is invalid; use 8k, 16k, 32k, 64k, or a positive base-10 integer.`);
25
+ });
26
+ const validated = validateContextBudgets(numbers);
27
+ if (validated.errors.length > 0) {
28
+ throw new Error(`--context-budgets is invalid: ${validated.errors.join(" ")}`);
29
+ }
30
+ return validated.budgets;
31
+ }