@dailephd/my-dev-kit-lab 0.4.8 → 0.5.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 (36) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +24 -3
  3. package/dist/src/cli/help.js +19 -4
  4. package/dist/src/commands/runExperimentDescribeCommand.js +11 -2
  5. package/dist/src/commands/runExperimentListCommand.js +3 -0
  6. package/dist/src/commands/runExperimentRunCommand.js +27 -7
  7. package/dist/src/evaluation/runMyDevKitRetrieval.js +82 -53
  8. package/dist/src/experiments/defaultRegistry.js +2 -0
  9. package/dist/src/experiments/plugins/index.js +1 -0
  10. package/dist/src/experiments/plugins/warmIndexReuse/config.js +58 -0
  11. package/dist/src/experiments/plugins/warmIndexReuse/execution.js +158 -0
  12. package/dist/src/experiments/plugins/warmIndexReuse/executionArtifact.js +77 -0
  13. package/dist/src/experiments/plugins/warmIndexReuse/fakeAgentEvaluation.js +118 -0
  14. package/dist/src/experiments/plugins/warmIndexReuse/index.js +8 -0
  15. package/dist/src/experiments/plugins/warmIndexReuse/metrics.js +257 -0
  16. package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +212 -0
  17. package/dist/src/experiments/plugins/warmIndexReuse/selection.js +85 -0
  18. package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +43 -0
  19. package/dist/src/plots/buildWarmIndexPlotData.js +112 -0
  20. package/dist/src/plots/index.js +1 -0
  21. package/dist/src/plots/writePlotArtifacts.js +8 -1
  22. package/dist/src/report/experiments/buildPluginExperimentReport.js +18 -2
  23. package/dist/src/report/experiments/buildWarmIndexReuseReport.js +91 -0
  24. package/dist/src/report/experiments/index.js +3 -0
  25. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  26. package/dist/src/report/experiments/renderPluginExperimentReportText.js +73 -0
  27. package/dist/src/report/experiments/renderWarmIndexReuseHtml.js +150 -0
  28. package/dist/src/report/experiments/warmIndexReuseReportModel.js +1 -0
  29. package/dist/src/tutorial/scenarioValidation.js +11 -0
  30. package/dist/src/tutorial/tutorialActions.js +14 -0
  31. package/dist/src/tutorial/tutorialSession.js +3 -0
  32. package/dist/src/tutorial/types.js +1 -0
  33. package/docs/METRICS.md +64 -0
  34. package/examples/tutorial-browser/index.html +5 -0
  35. package/examples/tutorial-browser/scenario.json +1 -0
  36. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to my-dev-kit-lab are documented here.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [0.5.0] - 2026-09-21
8
+
9
+ Warm-index reuse experiment support.
10
+
11
+ - Added the experimental `warm-index-reuse` experiment plugin, registered alongside `context-strategy-comparison` and reachable through `experiment list`, `experiment describe`, and `experiment run`.
12
+ - Built exactly one my-dev-kit index per benchmark-project group and reused it across every task in that group, with a matched `raw-full-file` baseline for each task; cases that disagree on target or source roots fail structurally instead of being indexed.
13
+ - Separated index construction from retrieval: one measured `index` command per project, and search/lookup/slice/source against the prepared index for each task, without re-indexing.
14
+ - Added deterministic fake-agent evaluation of each task side with context evidence, reusing the existing prompt generator, answer parser, run classifier, and correctness scorer.
15
+ - Reported index build duration, raw context-construction and warm retrieval durations, raw and retrieved context size, estimated context tokens, amortized index build duration (build duration / task ordinal), cumulative component durations, cumulative estimated context tokens, fake-agent correctness, fake-agent total tokens, and cumulative fake-agent total tokens, each with explicit `available`/`unavailable`/`not-applicable` availability and strict-prefix cumulative rules.
16
+ - Added a bounded warm-index section to the plugin `report.json`, `report.txt`, and `report.html`, explaining cold-start versus warm-reuse cost, with a neutral interpretation and explicit limitations. The bounded `warm-index-execution.json` records execution evidence without context text.
17
+ - Added four warm-index plots through the existing `plots generate` command: amortized index cost, raw versus retrieved context size, fake-agent correctness, and cumulative fake-agent token usage.
18
+ - Added the warm-index-only `experiment run --kit-command <command>` option (default `npx @dailephd/my-dev-kit@latest`), grouped `experiment run --help` options by plugin, and corrected `plots generate` help to describe supported inputs.
19
+ - Extended the exact packed-package acceptance gate with installed warm-index execution, reports, and plots while preserving installed-package and target immutability.
20
+ - Limitations: agent evidence comes from the deterministic fake agent only; fake-agent correctness and token totals are simulated harness evidence, not real-model results or provider billing telemetry; estimated context tokens remain a separate context-size estimate; cumulative durations are component sums, not wall-clock latency; no token-savings percentage, break-even task, winner, or ranking is calculated; the bundled cases provide one task per benchmark project; real-agent warm-index campaigns, warm-index screenshots, and gallery integration are not included.
21
+
22
+ ## [0.4.9] - 2026-09-20
23
+
24
+ Semantic native-select option selection for declarative browser tutorials.
25
+
26
+ - Added the value-only `select-option` declarative tutorial action, anchored to one existing `TutorialLocatorV1` and identifying a native HTML `<select>` option by its stable HTML `value`.
27
+ - Executed the action through Playwright `Locator.selectOption({ value })` using the existing tutorial timeout model, with no retry.
28
+ - Verified the returned selection exactly: the action passes only when the browser reports one selected value equal to the requested value; empty, mismatched, and multiple returned values fail.
29
+ - Added no keyboard fallback: `ArrowDown`/`Enter` emulation, click fallback, `page.evaluate`, and event dispatch are all absent, and `press` remains real keyboard input.
30
+ - Applied closed validation: `locator` and a non-empty trimmed `value` are required, timeouts use the existing contract, and unknown fields — including `label`, `index`, and `values` — are rejected rather than ignored.
31
+ - Moved the synthetic cursor to the select element before execution without fabricating a click ripple or animating the native popup menu.
32
+ - Extended the generic fixture (`examples/tutorial-browser/`) with a visible native `Operation` select whose ordinary `change` handler updates visible application state, and extended the canonical scenario to ten steps.
33
+ - Proved the action against real Chromium, including test-owned live DOM inspection of the selected value and the resulting visible state.
34
+ - Proved the action from the exact packed npm candidate installed into a clean consumer through the new `TUTORIAL_SELECT_OPTION` gate, preserving installed-package, packaged-example, and target immutability.
35
+ - Validated on Windows, macOS, and Linux across Node 24 and Node latest through the existing CI matrix.
36
+ - Confirmed bounded read-only downstream `my-frontend-observer` compatibility: representative native selects are expressible through the generic value-only contract, with no Observer values added to lab production code and no Observer files changed.
37
+ - Kept `TutorialScenarioV1`, `TutorialTargetContractV1`, `TutorialRunResultV1`, and `TutorialManifestV1` schemas at version `1.0.0`.
38
+
5
39
  ## [0.4.8] - 2026-09-19
6
40
 
7
41
  Locator-anchored pointer gestures for declarative browser tutorials.
package/README.md CHANGED
@@ -4,18 +4,23 @@ my-dev-kit-lab is the experiment, audit, and evidence companion for [my-dev-kit]
4
4
 
5
5
  my-dev-kit provides local repository indexing and graph-guided retrieval. my-dev-kit-lab supplies the controlled benchmarks, agent adapters, metrics, security checks, and reports needed to evaluate when that retrieval is useful. Results are evidence for a specific target and configuration; they do not guarantee token savings or security.
6
6
 
7
- The latest release is v0.4.8 (locator-anchored pointer gestures for declarative browser tutorials, adding `pointer-click` and `pointer-drag` using normalized locator-relative fraction positions while existing `drag` remains element-to-element). The v0.4.5 context-integrity evaluation remains intentionally frozen against the published `@dailephd/my-dev-kit@1.10.4` and `@dailephd/my-dev-kit-orchestrator@1.2.3` contracts; those versions are historical validation baselines, not a statement that they are the ecosystem's current releases. See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md).
7
+ Whole-ecosystem workflows are centralized in [my-dev-kit/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md](https://github.com/dailephd/my-dev-kit/blob/main/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md). Its [command-surface compatibility map](https://github.com/dailephd/my-dev-kit/blob/main/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md#915-command-surface-compatibility-map) documents which Lab outputs can safely seed my-dev-kit investigation, Observer reference work, or Orchestrator evidence, and which look-alike artifacts are not direct inputs.
8
+
9
+ The latest release is v0.5.0 (warm-index reuse experiment support). The previous release, v0.4.9, added semantic native-select option selection for declarative browser tutorials through the value-only `select-option` action backed by Playwright `Locator.selectOption({ value })`. The v0.4.5 context-integrity evaluation remains intentionally frozen against the published `@dailephd/my-dev-kit@1.10.4` and `@dailephd/my-dev-kit-orchestrator@1.2.3` contracts; those versions are historical validation baselines, not a statement that they are the ecosystem's current releases. See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md).
10
+
11
+ The current release includes v0.5.0 warm-index reuse. The package and installed CLI are version 0.5.0; v0.4.9 remains the previous release. See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for the validated release state.
8
12
 
9
13
  v0.4.6 adds a supported `my-dev-kit-lab` installed CLI router (`--help`, `--version`, `security validate`, `audit`, the `experiment` family, `report render`, `plots generate`, `gallery build`, `demo final`, and the historical direct final-demo invocation form), a writable lab workspace model kept separate from the installed package and the inspected target, and a permanent packed-tarball installation/execution acceptance gate (`npm run verify:packed-package`). See [docs/ROADMAP.md](docs/ROADMAP.md) and [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for status detail and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the runtime path model. The "Installed CLI" section below documents the shipped command surface; the source-checkout `npm run` workflow in Quickstart remains available for contributors.
10
14
 
11
15
  ## Current capabilities
12
16
 
13
17
  - **Run context-strategy experiments:** compare `raw-full-file` with `my-dev-kit-guided` using deterministic fixtures or locally configured Codex and Claude CLIs.
18
+ - **Measure warm-index reuse (v0.5.0):** the `warm-index-reuse` experiment plugin builds one my-dev-kit index per benchmark project, reuses it across that project's tasks, and compares every task with a matched `raw-full-file` baseline. Reports separate one-time index-build cost from per-task retrieval cost, show amortized index cost and cumulative measurements, and include deterministic fake-agent correctness and token evidence; `plots generate` renders four warm-index SVG charts. Estimated context tokens (a character-based context-size estimate) and fake-agent total tokens (simulated harness telemetry) are reported separately, and neither is provider billing telemetry. See [docs/WORKFLOWS.md](docs/WORKFLOWS.md#warm-index-reuse-experiment) and [docs/METRICS.md](docs/METRICS.md#warm-index-reuse-metrics).
14
19
  - **Audit repository health:** run conservative code-rot detectors for TypeScript/JavaScript, Python, Java, and Kotlin, or adapt security findings into the common audit report.
15
20
  - **Validate CLI/package security:** inspect dependencies, package contents, path and subprocess boundaries, malformed inputs, optional static scanners, and bounded fuzz targets.
16
21
  - **Validate Android projects:** run nineteen static checks by default, with Gradle operations, external tools, and network access available only through explicit opt-in flags.
17
22
  - **Review evidence:** generate JSON and HTML reports, SVG plots, optional screenshots, visualization demos, and a static gallery.
18
- - **Generate declarative browser tutorials:** validate and execute bounded local browser scenarios with WebM recording, step screenshots, SRT/VTT subtitles, Markdown, and a tutorial manifest. Locator-anchored pointer gestures (`pointer-click` and `pointer-drag`) operate using normalized fraction coordinates inside a single located surface, while existing `drag` remains element-to-element. Tutorial execution requires a compatible locally installed Chromium; see [docs/COMMANDS.md](docs/COMMANDS.md) and [docs/TUTORIAL.md](docs/TUTORIAL.md) for details.
23
+ - **Generate declarative browser tutorials:** validate and execute bounded local browser scenarios with WebM recording, step screenshots, SRT/VTT subtitles, Markdown, and a tutorial manifest. Locator-anchored pointer gestures (`pointer-click` and `pointer-drag`) operate using normalized fraction coordinates inside a single located surface, while existing `drag` remains element-to-element and `press` remains real keyboard input. `select-option` performs semantic native HTML `<select>` option selection by stable HTML value, executed through Playwright `Locator.selectOption({ value })` rather than keyboard navigation. Tutorial execution requires a compatible locally installed Chromium; see [docs/COMMANDS.md](docs/COMMANDS.md) and [docs/TUTORIAL.md](docs/TUTORIAL.md) for details.
19
24
  - **Evaluate stage-context strategies:** compare the two legacy strategies against six additional bounded stage-context strategies — `architecture-context-only`, `architecture-plus-implementation-refresh`, `architecture-plus-implementation-and-test-refresh`, `full-workflow-library`, `bounded-workflow-instruction-packet`, and `combined-bounded-stage-context` — selected through programmatic configuration, not CLI flags. Each strategy's evidence is reported through bounded `report.json`, `report.html`, and `report.txt` output with an explicit `available`/`unavailable`/`not-applicable` metric-availability model and no composite score, grade, ranking, or winning strategy.
20
25
  - **Evaluate the producer-readiness bridge (v0.4.4):** optionally extend `combined-bounded-stage-context` with the frozen my-dev-kit-orchestrator supplemental implementation/test-context packet and retrieval-report documents plus an observed readiness result, all supplied programmatically (there is no CLI flag), to measure owner, allocation, truncation-cause, supplemental/raw agreement, readiness-agreement, and criticality-overlay evidence without reimplementing upstream owner-selection, allocation, producer-parity, or readiness policy.
21
26
  - **Evaluate context integrity (v0.4.5):** compare condition-aware producer evidence from my-dev-kit v1.10.4 (role condition coverage, allocation/spillover, required-evidence-loss) against my-dev-kit-orchestrator v1.2.3 run-integrity evidence (run-integrity gate, judge integrity, final-report eligibility, artifact lifecycle state), reporting agreement or contradiction between them rather than re-deriving a verdict. Evaluation runs against a frozen, hash-verified regression fixture pair — a byte-exact real historical failed run and a hand-distilled corrected-replay counterpart representing the same validated contracts — and is programmatic/test-driven only. `npm run report:context-integrity-smoke` renders both fixtures' reports for manual inspection; it takes no arguments and is a developer convenience, not a configurable evaluation CLI.
@@ -151,6 +156,19 @@ npm run experiment:run -- `
151
156
  --no-screenshot
152
157
  ```
153
158
 
159
+ Run the warm-index reuse plugin deterministically with the fake my-dev-kit fixture (omit `--kit-command` to use the default `npx @dailephd/my-dev-kit@latest`):
160
+
161
+ ```bash
162
+ npm run experiment:run -- \
163
+ --experiment warm-index-reuse \
164
+ --cases tests/fixtures/warm-index-reuse/multi-task-cases.json \
165
+ --kit-command "node tests/fixtures/fake-my-dev-kit-cli.js" \
166
+ --out lab-output/warm-index-reuse
167
+ npm run generate-experiment-plots -- --experiment lab-output/warm-index-reuse --out lab-output/warm-index-plots
168
+ ```
169
+
170
+ The bundled `examples/token-savings-cases.json` currently has one task per benchmark project, so multi-task reuse needs a cases file with several tasks per project, such as the test fixture above; an expanded bundled warm-index benchmark suite is planned.
171
+
154
172
  When `--target` is omitted, the experiment runs in self mode against my-dev-kit-lab. When `--target <path>` is provided, the lab remains the tool root and the target project is inspected separately. Generated experiment outputs stay under lab-controlled output directories by default, not inside the target project.
155
173
 
156
174
  ---
@@ -168,6 +186,7 @@ When `--target` is omitted, the experiment runs in self mode against my-dev-kit-
168
186
  | Plugin experiment report JSON | `lab-output/experiments/<plugin>/<target>/<run>/report.json` |
169
187
  | Plugin experiment report HTML | `lab-output/experiments/<plugin>/<target>/<run>/report.html` |
170
188
  | Plugin experiment report text | `lab-output/experiments/<plugin>/<target>/<run>/report.txt` |
189
+ | Warm-index execution evidence | `<experiment out>/warm-index-execution.json` (plus `indexes/`, `commands/`, and `agents/` beneath the same output root) |
171
190
  | Plot data | `lab-output/<plots>/plot-data.json` |
172
191
  | SVG charts | `lab-output/<plots>/charts/*.svg` |
173
192
  | Gallery manifest | `lab-output/<gallery>/gallery-manifest.json` |
@@ -201,7 +220,8 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
201
220
  - Claude does not expose token totals; token savings comparisons are unavailable for Claude runs
202
221
  - Codex may expose token totals but can produce timeouts or invalid-output runs
203
222
  - Small projects may make raw-full-file cheaper than my-dev-kit-guided; larger localized tasks are where my-dev-kit is expected to become more useful
204
- - The generic experiment-plugin framework currently ships one plugin, `context-strategy-comparison`; future plugins such as warm-index reuse, incremental-change, and context-window scaling are not implemented yet
223
+ - The generic experiment-plugin framework has two plugins: `context-strategy-comparison` and the current-release v0.5.0 `warm-index-reuse`. Later plugins such as incremental-change and context-window scaling are future roadmap work
224
+ - `warm-index-reuse` uses the deterministic fake agent only; selectable Codex/Claude warm-index campaigns remain planned for v0.5.2, the expanded benchmark suite remains planned for v0.5.1, and warm-index screenshots/gallery integration are not implemented. Its component-duration sums are not wall-clock latency, and it calculates no token-savings percentage, break-even task, winner, or ranking
205
225
  - The current release does not guarantee token savings; it produces auditable evidence for specific cases, targets, agents, and strategies
206
226
  - Provider telemetry dashboards, semantic LLM judging, and cloud API billing integration are not yet implemented
207
227
  - The six new stage-context strategies have no CLI flags yet, are configured programmatically, and do not yet include plots, screenshots, or gallery integration
@@ -210,6 +230,7 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
210
230
  - The v0.4.5 context-integrity evaluation is released. It has no CLI flags, no plots/screenshot/gallery integration, and produces no composite score, grade, ranking, or winner. Its corrected-replay fixture is a hand-distilled representation of the validated my-dev-kit v1.10.4 and my-dev-kit-orchestrator v1.2.3 contracts, not a live capture of a complete ten-stage workflow. The orchestrator agreement evidence does not read a literal upstream `promptMode` field; `stageMayRenderNormalPrompt`, derived from structured blocked-stage evidence, is the bounded substitute used instead.
211
231
  - v0.4.7 added declarative browser tutorial validation and execution with bounded actions/assertions, persistent Chromium sessions, WebM, screenshots, SRT/VTT, Markdown, and `tutorial-manifest.json`. Chromium is a separate local runtime prerequisite; the package does not download browser binaries automatically. Gallery consumption remains future scope.
212
232
  - v0.4.8 added `pointer-click` and `pointer-drag` for normalized fraction positions inside a single located element. Existing `drag` remains element-to-element between source and target locators.
233
+ - v0.4.9 `select-option` identifies an option by HTML value only: label selection, index selection, multi-select arrays, and automatic selector fallback are not supported, and there is no keyboard (`ArrowDown`/`Enter`) fallback. Native popup/menu traversal is not animated. Chromium remains a separately installed local runtime prerequisite; neither package installation nor tutorial execution downloads it automatically.
213
234
 
214
235
  ---
215
236
 
@@ -142,7 +142,7 @@ export function renderExperimentRunHelp() {
142
142
  "Required:",
143
143
  " --experiment <id> Registered experiment plugin id (see \"my-dev-kit-lab experiment list\")",
144
144
  "",
145
- "Options:",
145
+ "Common options (all plugins):",
146
146
  " --target <path> Local target project path (default: self)",
147
147
  " --out <dir> Output directory for experiment artifacts",
148
148
  " (default: beneath the workspace when installed)",
@@ -152,6 +152,15 @@ export function renderExperimentRunHelp() {
152
152
  " (default: the bundled package resource)",
153
153
  " --case <ids> Comma-separated case ids to run",
154
154
  " --benchmark-project <ids> Comma-separated benchmark project ids to run",
155
+ "",
156
+ "warm-index-reuse only:",
157
+ " --kit-command <command> my-dev-kit command used to build one index per benchmark",
158
+ " project and retrieve per task",
159
+ " (default: npx @dailephd/my-dev-kit@latest)",
160
+ " Agent evidence uses the deterministic fake agent only;",
161
+ " agent options below are not accepted by this plugin.",
162
+ "",
163
+ "context-strategy-comparison only:",
155
164
  " --agents <list> Comma-separated agent ids (default: fake-agent)",
156
165
  " --strategies <list> Comma-separated strategies: raw-full-file,my-dev-kit-guided",
157
166
  " --complexities <list> Comma-separated complexity levels: short,medium,long,multi-step",
@@ -238,19 +247,25 @@ export function renderPlotsHelp() {
238
247
  " my-dev-kit-lab plots --help",
239
248
  "",
240
249
  "Commands:",
241
- " generate Generate experiment plot artifacts from controlled-experiment artifacts.",
250
+ " generate Generate experiment plot artifacts from controlled-experiment or supported",
251
+ " plugin experiment artifacts (warm-index-reuse).",
242
252
  " Run \"my-dev-kit-lab plots generate --help\" for details."
243
253
  ].join("\n");
244
254
  }
245
255
  export function renderPlotsGenerateHelp() {
246
256
  return [
247
- "my-dev-kit-lab plots generate - generate chart/plot artifacts from controlled-experiment artifacts",
257
+ "my-dev-kit-lab plots generate - generate chart/plot artifacts from experiment artifacts",
248
258
  "",
249
259
  "Usage:",
250
260
  " my-dev-kit-lab plots generate --experiment <dir> --out <dir>",
251
261
  "",
262
+ "Supported inputs:",
263
+ " - legacy controlled-experiment output directories",
264
+ " - warm-index-reuse plugin output directories (containing its report.json)",
265
+ " Other plugin outputs are not plotted by this command.",
266
+ "",
252
267
  "Required:",
253
- " --experiment <dir> Path to a controlled-experiment output directory",
268
+ " --experiment <dir> Path to a supported experiment output directory",
254
269
  " --out <dir> Output directory for plot artifacts"
255
270
  ].join("\n");
256
271
  }
@@ -106,6 +106,9 @@ function readConfigFields(plugin, required) {
106
106
  }));
107
107
  }
108
108
  function readSupportedVariants(plugin) {
109
+ if (plugin.supportedVariants) {
110
+ return [...plugin.supportedVariants];
111
+ }
109
112
  const strategies = readArrayField(plugin.defaultConfig, "strategies");
110
113
  return strategies.filter((value) => typeof value === "string");
111
114
  }
@@ -139,9 +142,15 @@ function describeExpectedReports(plugin) {
139
142
  return `Writes supported outputs: ${plugin.metadata.supportedOutputs.join(", ")}.`;
140
143
  }
141
144
  function describeExamples(plugin) {
145
+ // Only advertise agent-matrix options for plugins that declare them.
146
+ const agentOptions = hasConfigField(plugin, "agents") ? " --agents fake-agent --complexities short" : "";
147
+ const screenshotOption = hasConfigField(plugin, "agents") ? " --no-screenshot" : "";
142
148
  return [
143
149
  `my-dev-kit-lab experiment describe --experiment ${plugin.metadata.id}`,
144
- `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --agents fake-agent --complexities short`,
145
- `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target "Z:\\Users\\newuser\\Projects\\my-dev-kit-v1" --agents fake-agent --complexities short --no-screenshot`
150
+ `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id}${agentOptions}`,
151
+ `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target "Z:\\Users\\newuser\\Projects\\my-dev-kit-v1"${agentOptions}${screenshotOption}`
146
152
  ];
147
153
  }
154
+ function hasConfigField(plugin, name) {
155
+ return (plugin.configDefinition?.fields ?? []).some((field) => field.name === name);
156
+ }
@@ -29,6 +29,9 @@ function toListedExperiment(metadata, plugin) {
29
29
  };
30
30
  }
31
31
  function readSupportedVariants(plugin) {
32
+ if (plugin.supportedVariants) {
33
+ return [...plugin.supportedVariants];
34
+ }
32
35
  const strategies = readArrayField(plugin.defaultConfig, "strategies");
33
36
  return strategies.filter((value) => typeof value === "string");
34
37
  }
@@ -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, resolveExperimentTarget, runExperiment } from "../experiments/index.js";
5
+ import { contextStrategyComparisonPlugin, createDefaultExperimentPluginRegistry, 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";
@@ -100,6 +100,7 @@ export function parseRunExperimentArgs(argv) {
100
100
  let continueOnFailure;
101
101
  let requireAgents;
102
102
  let includeRealAgents;
103
+ let kitCommand;
103
104
  const commandTemplates = {};
104
105
  for (let index = 0; index < argv.length; index += 1) {
105
106
  const arg = argv[index];
@@ -157,6 +158,9 @@ export function parseRunExperimentArgs(argv) {
157
158
  else if (arg === "--command-template-claude") {
158
159
  commandTemplates.claude = parseAgentCommandTemplate(readRequiredValue(argv, ++index, "--command-template-claude"));
159
160
  }
161
+ else if (arg === "--kit-command") {
162
+ kitCommand = readRequiredValue(argv, ++index, "--kit-command");
163
+ }
160
164
  else if (arg === "--no-screenshot") {
161
165
  // The plugin-aware report path does not capture screenshots yet; accept this
162
166
  // flag so smoke commands can share the legacy demo option set.
@@ -168,6 +172,9 @@ export function parseRunExperimentArgs(argv) {
168
172
  if (!experimentId) {
169
173
  throw new Error("Usage: --experiment <id> [--target <path>] [--out <directory>]");
170
174
  }
175
+ if (kitCommand !== undefined && experimentId !== warmIndexReusePlugin.metadata.id) {
176
+ throw new Error(`--kit-command is only supported for --experiment ${warmIndexReusePlugin.metadata.id}.`);
177
+ }
171
178
  return {
172
179
  experimentId,
173
180
  targetPath,
@@ -185,7 +192,8 @@ export function parseRunExperimentArgs(argv) {
185
192
  continueOnFailure,
186
193
  requireAgents,
187
194
  includeRealAgents,
188
- commandTemplates: Object.keys(commandTemplates).length > 0 ? commandTemplates : undefined
195
+ commandTemplates: Object.keys(commandTemplates).length > 0 ? commandTemplates : undefined,
196
+ kitCommand
189
197
  })
190
198
  };
191
199
  }
@@ -212,13 +220,25 @@ function generateExperimentRunId(pluginId) {
212
220
  return `${pluginId}-${timestamp}`;
213
221
  }
214
222
  async function loadPluginInputs(args, toolRoot, context) {
215
- if (args.experimentId !== contextStrategyComparisonPlugin.metadata.id) {
216
- return undefined;
223
+ if (args.experimentId === contextStrategyComparisonPlugin.metadata.id) {
224
+ const validation = contextStrategyComparisonPlugin.validateConfig(args.config);
225
+ if (!validation.valid || !validation.config) {
226
+ throw new Error(`Invalid context strategy comparison config: ${validation.errors.join("; ")}`);
227
+ }
228
+ return loadCasesAndProjectProfiles(args, toolRoot, context);
217
229
  }
218
- const validation = contextStrategyComparisonPlugin.validateConfig(args.config);
219
- if (!validation.valid || !validation.config) {
220
- throw new Error(`Invalid context strategy comparison config: ${validation.errors.join("; ")}`);
230
+ if (args.experimentId === warmIndexReusePlugin.metadata.id) {
231
+ const validation = warmIndexReusePlugin.validateConfig(args.config);
232
+ if (!validation.valid || !validation.config) {
233
+ throw new Error(`Invalid warm index reuse config: ${validation.errors.join("; ")}`);
234
+ }
235
+ return loadCasesAndProjectProfiles(args, toolRoot, context);
221
236
  }
237
+ return undefined;
238
+ }
239
+ // Shared by both registered plugins: explicit paths resolve against toolRoot, defaults come from
240
+ // bundled package resources so they do not depend on the invocation cwd.
241
+ async function loadCasesAndProjectProfiles(args, toolRoot, context) {
222
242
  const projectProfilesPath = args.config.projectProfilesPath
223
243
  ? path.resolve(toolRoot, args.config.projectProfilesPath)
224
244
  : resolvePackageResource(context, DEFAULT_PROJECT_PROFILES_RESOURCE);
@@ -39,52 +39,71 @@ function pickCandidateFields(candidate) {
39
39
  symbol: readString("symbol", "name", "label")
40
40
  };
41
41
  }
42
- export async function runMyDevKitRetrieval(options) {
43
- const started = Date.now();
42
+ function skippedRetrieval(caseId, warnings, commands, durationMs) {
43
+ return {
44
+ caseId,
45
+ skipped: true,
46
+ warnings,
47
+ totalChars: 0,
48
+ totalEstimatedTokens: 0,
49
+ tokenCountMethod,
50
+ contextText: "",
51
+ filesRead: [],
52
+ commands,
53
+ durationMs
54
+ };
55
+ }
56
+ /**
57
+ * Runs exactly one my-dev-kit `index` command for the target and writes the index only to
58
+ * `indexDir`. Performs no search/lookup/slice/source work. With `requireKit` a failed index
59
+ * throws; otherwise the failure is returned as `ok: false` with a warning.
60
+ */
61
+ export async function buildMyDevKitIndex(options) {
44
62
  const warnings = [];
45
- const commandsDir = path.join(options.outputDir, "commands", options.evaluationCase.id);
46
- const indexesDir = path.join(options.outputDir, "indexes", options.evaluationCase.id);
47
- const commands = [];
48
- const indexCommand = await runMeasuredCommand({
63
+ const command = await runMeasuredCommand({
49
64
  commandId: "index",
50
65
  commandString: options.kitCommand,
51
66
  cwd: process.cwd(),
52
- outDir: commandsDir,
67
+ outDir: options.commandsDir,
53
68
  extraArgs: [
54
69
  "index",
55
70
  "--root",
56
- options.evaluationCase.absoluteTargetRoot,
57
- ...options.evaluationCase.sourceRoots.flatMap((sourceRoot) => ["--src", sourceRoot]),
71
+ options.target.absoluteTargetRoot,
72
+ ...options.target.sourceRoots.flatMap((sourceRoot) => ["--src", sourceRoot]),
58
73
  "--out",
59
- indexesDir,
74
+ options.indexDir,
60
75
  "--json"
61
76
  ]
62
77
  });
63
- commands.push(indexCommand);
64
- if (!indexCommand.ok) {
78
+ if (!command.ok) {
65
79
  if (options.requireKit) {
66
- throw new Error(indexCommand.error || `my-dev-kit index failed with exit code ${indexCommand.exitCode}`);
80
+ throw new Error(command.error || `my-dev-kit index failed with exit code ${command.exitCode}`);
67
81
  }
68
82
  warnings.push("my-dev-kit index command was unavailable or failed.");
69
- return {
70
- caseId: options.evaluationCase.id,
71
- skipped: true,
72
- warnings,
73
- totalChars: 0,
74
- totalEstimatedTokens: 0,
75
- tokenCountMethod,
76
- contextText: "",
77
- filesRead: [],
78
- commands,
79
- durationMs: Date.now() - started
80
- };
81
83
  }
84
+ return {
85
+ ok: command.ok,
86
+ indexDir: options.indexDir,
87
+ durationMs: command.durationMs,
88
+ warnings,
89
+ command
90
+ };
91
+ }
92
+ /**
93
+ * Runs search -> lookup -> slice -> source against an already prepared index. Never invokes
94
+ * `index`; `durationMs` covers retrieval only.
95
+ */
96
+ export async function runMyDevKitRetrievalFromIndex(options) {
97
+ const started = Date.now();
98
+ const warnings = [];
99
+ const commands = [];
100
+ const { commandsDir, indexDir } = options;
82
101
  const searchCommand = await runMeasuredCommand({
83
102
  commandId: "search",
84
103
  commandString: options.kitCommand,
85
104
  cwd: process.cwd(),
86
105
  outDir: commandsDir,
87
- extraArgs: ["search", "--index", indexesDir, "--query", options.evaluationCase.query, "--json"]
106
+ extraArgs: ["search", "--index", indexDir, "--query", options.evaluationCase.query, "--json"]
88
107
  });
89
108
  commands.push(searchCommand);
90
109
  if (!searchCommand.ok) {
@@ -92,36 +111,14 @@ export async function runMyDevKitRetrieval(options) {
92
111
  throw new Error(searchCommand.error || `my-dev-kit search failed with exit code ${searchCommand.exitCode}`);
93
112
  }
94
113
  warnings.push("my-dev-kit search command failed.");
95
- return {
96
- caseId: options.evaluationCase.id,
97
- skipped: true,
98
- warnings,
99
- totalChars: 0,
100
- totalEstimatedTokens: 0,
101
- tokenCountMethod,
102
- contextText: "",
103
- filesRead: [],
104
- commands,
105
- durationMs: Date.now() - started
106
- };
114
+ return skippedRetrieval(options.evaluationCase.id, warnings, commands, Date.now() - started);
107
115
  }
108
116
  const searchPayload = parseJsonIfPossible(searchCommand.stdout);
109
117
  const candidates = readSearchResults(searchPayload);
110
118
  const selected = candidates[0];
111
119
  if (!selected) {
112
120
  warnings.push("No my-dev-kit search candidate was found.");
113
- return {
114
- caseId: options.evaluationCase.id,
115
- skipped: true,
116
- warnings,
117
- totalChars: 0,
118
- totalEstimatedTokens: 0,
119
- tokenCountMethod,
120
- contextText: "",
121
- filesRead: [],
122
- commands,
123
- durationMs: Date.now() - started
124
- };
121
+ return skippedRetrieval(options.evaluationCase.id, warnings, commands, Date.now() - started);
125
122
  }
126
123
  const candidate = pickCandidateFields(selected);
127
124
  const selectedNodeId = candidate.nodeId;
@@ -136,7 +133,7 @@ export async function runMyDevKitRetrieval(options) {
136
133
  commandString: options.kitCommand,
137
134
  cwd: process.cwd(),
138
135
  outDir: commandsDir,
139
- extraArgs: ["lookup", "--index", indexesDir, "--node", selectedNodeId, "--json"]
136
+ extraArgs: ["lookup", "--index", indexDir, "--node", selectedNodeId, "--json"]
140
137
  });
141
138
  commands.push(lookupCommand);
142
139
  if (lookupCommand.ok) {
@@ -150,7 +147,7 @@ export async function runMyDevKitRetrieval(options) {
150
147
  commandString: options.kitCommand,
151
148
  cwd: process.cwd(),
152
149
  outDir: commandsDir,
153
- extraArgs: ["slice", "--index", indexesDir, "--node", selectedNodeId, "--json"]
150
+ extraArgs: ["slice", "--index", indexDir, "--node", selectedNodeId, "--json"]
154
151
  });
155
152
  commands.push(sliceCommand);
156
153
  if (sliceCommand.ok) {
@@ -164,7 +161,7 @@ export async function runMyDevKitRetrieval(options) {
164
161
  commandString: options.kitCommand,
165
162
  cwd: process.cwd(),
166
163
  outDir: commandsDir,
167
- extraArgs: ["source", "--index", indexesDir, "--node", selectedNodeId, "--max-lines", "160", "--format", "numbered"]
164
+ extraArgs: ["source", "--index", indexDir, "--node", selectedNodeId, "--max-lines", "160", "--format", "numbered"]
168
165
  });
169
166
  commands.push(sourceCommand);
170
167
  if (sourceCommand.ok) {
@@ -195,3 +192,35 @@ export async function runMyDevKitRetrieval(options) {
195
192
  durationMs: Date.now() - started
196
193
  };
197
194
  }
195
+ /**
196
+ * Legacy per-case lifecycle: builds a case-specific index and retrieves from it. `commands`
197
+ * starts with the index command and `durationMs` covers the whole lifecycle including indexing.
198
+ */
199
+ export async function runMyDevKitRetrieval(options) {
200
+ const started = Date.now();
201
+ const commandsDir = path.join(options.outputDir, "commands", options.evaluationCase.id);
202
+ const indexDir = path.join(options.outputDir, "indexes", options.evaluationCase.id);
203
+ const index = await buildMyDevKitIndex({
204
+ target: options.evaluationCase,
205
+ kitCommand: options.kitCommand,
206
+ indexDir,
207
+ commandsDir,
208
+ requireKit: options.requireKit
209
+ });
210
+ if (!index.ok) {
211
+ return skippedRetrieval(options.evaluationCase.id, [...index.warnings], [index.command], Date.now() - started);
212
+ }
213
+ const retrieval = await runMyDevKitRetrievalFromIndex({
214
+ evaluationCase: options.evaluationCase,
215
+ kitCommand: options.kitCommand,
216
+ indexDir,
217
+ commandsDir,
218
+ requireKit: options.requireKit
219
+ });
220
+ return {
221
+ ...retrieval,
222
+ warnings: [...index.warnings, ...retrieval.warnings],
223
+ commands: [index.command, ...retrieval.commands],
224
+ durationMs: Date.now() - started
225
+ };
226
+ }
@@ -1,7 +1,9 @@
1
1
  import { ExperimentPluginRegistry } from "./registry.js";
2
2
  import { contextStrategyComparisonPlugin } from "./plugins/contextStrategyComparison/index.js";
3
+ import { warmIndexReusePlugin } from "./plugins/warmIndexReuse/index.js";
3
4
  export function createDefaultExperimentPluginRegistry() {
4
5
  const registry = new ExperimentPluginRegistry();
5
6
  registry.register(contextStrategyComparisonPlugin);
7
+ registry.register(warmIndexReusePlugin);
6
8
  return registry;
7
9
  }
@@ -1 +1,2 @@
1
1
  export * from "./contextStrategyComparison/index.js";
2
+ export * from "./warmIndexReuse/index.js";
@@ -0,0 +1,58 @@
1
+ import { invalidExperimentConfig, isPlainObject, mergeConfig, validExperimentConfig } from "../../config.js";
2
+ export const defaultWarmIndexReuseConfig = {
3
+ casesPath: "examples/token-savings-cases.json",
4
+ projectProfilesPath: "benchmarks/contracts/benchmark-project-profiles.json",
5
+ outDir: "lab-output/warm-index-reuse",
6
+ kitCommand: "npx @dailephd/my-dev-kit@latest",
7
+ };
8
+ const SUPPORTED_FIELDS = [
9
+ "casesPath",
10
+ "projectProfilesPath",
11
+ "outDir",
12
+ "kitCommand",
13
+ "caseIds",
14
+ "benchmarkProjects",
15
+ ];
16
+ export const warmIndexReuseConfigDefinition = {
17
+ fields: [
18
+ { name: "casesPath", type: "string", required: true, description: "Evaluation cases file." },
19
+ { name: "outDir", type: "string", required: true, description: "Experiment output root." },
20
+ { name: "projectProfilesPath", type: "string", description: "Benchmark project profiles file." },
21
+ { name: "kitCommand", type: "string", description: "my-dev-kit command used for index and retrieval." },
22
+ { name: "caseIds", type: "array", description: "Evaluation case IDs to include." },
23
+ { name: "benchmarkProjects", type: "array", description: "Benchmark project IDs to include." },
24
+ ],
25
+ };
26
+ /**
27
+ * Closed validation: fields outside the warm-index contract (for example agent-matrix settings)
28
+ * are rejected instead of being silently ignored.
29
+ */
30
+ export function validateWarmIndexReuseConfig(config) {
31
+ if (config !== undefined && !isPlainObject(config)) {
32
+ return invalidExperimentConfig(["warm index reuse config must be an object."]);
33
+ }
34
+ const errors = [];
35
+ const unsupported = Object.keys(config ?? {}).filter((key) => !SUPPORTED_FIELDS.includes(key));
36
+ if (unsupported.length > 0) {
37
+ errors.push(`Unsupported warm-index-reuse config field(s): ${unsupported.sort().join(", ")}.`);
38
+ }
39
+ const normalized = mergeConfig(defaultWarmIndexReuseConfig, config);
40
+ for (const field of ["casesPath", "projectProfilesPath", "outDir", "kitCommand"]) {
41
+ const value = normalized[field];
42
+ if (typeof value !== "string" || !value.trim()) {
43
+ errors.push(`${field} must be a non-empty string.`);
44
+ }
45
+ }
46
+ for (const field of ["caseIds", "benchmarkProjects"]) {
47
+ const value = normalized[field];
48
+ if (value === undefined)
49
+ continue;
50
+ if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && item.trim())) {
51
+ errors.push(`${field} must be a non-empty array of non-empty strings when provided.`);
52
+ }
53
+ }
54
+ if (errors.length > 0) {
55
+ return invalidExperimentConfig(errors);
56
+ }
57
+ return validExperimentConfig(normalized);
58
+ }