@dailephd/my-dev-kit-lab 0.5.1 → 0.6.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 (35) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/README.md +23 -8
  3. package/dist/src/agents/adapters/claudeAdapter.js +47 -4
  4. package/dist/src/agents/adapters/codexAdapter.js +64 -5
  5. package/dist/src/cli/help.js +22 -1
  6. package/dist/src/commands/runExperimentDescribeCommand.js +6 -1
  7. package/dist/src/commands/runExperimentRunCommand.js +87 -5
  8. package/dist/src/commands/runWarmIndexCampaignPresentation.js +64 -0
  9. package/dist/src/core/runMeasuredCommand.js +49 -10
  10. package/dist/src/evaluation/indexFreshness.js +199 -0
  11. package/dist/src/evaluation/indexSnapshot.js +393 -0
  12. package/dist/src/evaluation/runMyDevKitRetrieval.js +34 -0
  13. package/dist/src/experiments/plugins/warmIndexReuse/agentEvaluation.js +233 -0
  14. package/dist/src/experiments/plugins/warmIndexReuse/campaignPresets.js +83 -0
  15. package/dist/src/experiments/plugins/warmIndexReuse/config.js +51 -0
  16. package/dist/src/experiments/plugins/warmIndexReuse/execution.js +15 -0
  17. package/dist/src/experiments/plugins/warmIndexReuse/executionArtifact.js +2 -0
  18. package/dist/src/experiments/plugins/warmIndexReuse/fakeAgentEvaluation.js +6 -118
  19. package/dist/src/experiments/plugins/warmIndexReuse/index.js +3 -1
  20. package/dist/src/experiments/plugins/warmIndexReuse/metrics.js +35 -11
  21. package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +50 -14
  22. package/dist/src/experiments/plugins/warmIndexReuse/realAgentPrompt.js +52 -0
  23. package/dist/src/experiments/plugins/warmIndexReuse/selection.js +35 -0
  24. package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +18 -3
  25. package/dist/src/gallery/index.js +1 -0
  26. package/dist/src/gallery/writeGalleryManifest.js +5 -1
  27. package/dist/src/gallery/writeWarmIndexCampaignGallery.js +116 -0
  28. package/dist/src/plots/buildWarmIndexPlotData.js +21 -2
  29. package/dist/src/report/experiments/buildPluginExperimentReport.js +47 -8
  30. package/dist/src/report/experiments/buildWarmIndexReuseReport.js +190 -13
  31. package/dist/src/report/experiments/renderPluginExperimentReportText.js +90 -12
  32. package/dist/src/report/experiments/renderWarmIndexReuseHtml.js +114 -14
  33. package/dist/src/report/experiments/warmIndexReuseReportModel.js +3 -0
  34. package/docs/METRICS.md +39 -2
  35. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ All notable changes to my-dev-kit-lab are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.0] - 2026-09-24
8
+
9
+ Index freshness and changed-file detection.
10
+
11
+ - Added `IndexSnapshotV1` (schema `my-dev-kit-lab-index-snapshot-v1`): after a warm-index run's one index build, Lab interprets the my-dev-kit manifest and symbol index and records baseline index evidence — manifest metadata, the exact indexed-file set the upstream index contract lists (never a directory walk), per-file SHA-256, size, and modified-time metadata, the index command, and an inventory of generated index artifacts. Snapshots are `complete`, `partial`, or `unavailable` with explicit reasons; source contents are never persisted.
12
+ - Added a configured my-dev-kit `--version` probe, run once per prepared index session outside the measured index build. An unsupported or failing probe is non-fatal and recorded as explicitly unavailable.
13
+ - Added `IndexFreshnessAssessmentV1` (schema `my-dev-kit-lab-index-freshness-v1`): a read-only comparison of the files the snapshot lists against their current state. SHA-256 is the comparison identity; modified time is diagnostic only. Modified and missing indexed files are confirmed changes; unreadable or unresolvable files stay explicit unresolved evidence; files absent from the snapshot are not inferred to be newly indexable. Exactly four statuses: `fresh`, `stale`, `partially-stale`, `unknown`.
14
+ - Freshness is assessed for every task immediately before its warm retrieval and is observational only: it never triggers or recommends reindexing, suppresses retrieval, or changes execution, provider, agent, correctness, or token-evidence status. One index is still built per benchmark project.
15
+ - Extended `warm-index-execution.json` additively (schema `my-dev-kit-lab-warm-index-execution-v1` unchanged) with project-level `indexSnapshot` and task-level `indexFreshness`.
16
+ - Extended the warm-index report additively (schema `my-dev-kit-lab-warm-index-report-v1` unchanged) with an index freshness summary (assessed, unassessed, fresh, stale, partially-stale, and unknown task counts) and per-task freshness evidence in `report.json`, `report.txt`, and `report.html`. Report evidence is bounded to 20 changed files and 10 unresolved entries per task, omits content hashes, and escapes dynamic strings; a task with no assessment is shown as not assessed, distinct from `unknown`. Report limitations and the run interpretation state what freshness does and does not establish.
17
+ - No new CLI command or flag, `ExperimentMetric`, plot, gallery item, or experiment plugin was added: the warm-index metrics, exactly four plots, and the three-item campaign gallery are unchanged, and no reindex recommendation exists.
18
+ - Extended the packed-package acceptance gate (`npm run verify:packed-package`) to prove the exact tarball ships the snapshot and freshness runtime and that an installed warm-index run produces snapshot, tool-version, freshness, and report freshness evidence while the target, installed package, and packaged examples stay unchanged.
19
+ - Validation of the implementation candidate: exact-SHA ordinary CI passed on Ubuntu, macOS, and Windows with Node 24 and latest, and the full local suite passed with the packed-package gate.
20
+
21
+ ## [0.5.2] - 2026-09-23
22
+
23
+ Warm-index real-agent campaigns.
24
+
25
+ - Added three frozen `warm-index-reuse` campaign presets — `codex-full`, `claude-full`, and `codex-timeout-isolation` — each owning exactly one provider (Codex or Claude), the bundled production corpus, and project profiles. `codex-timeout-isolation` narrows to three large/mixed cases (`warm-large-health-label`, `warm-large-ts-leaderboard`, `warm-large-broad-analytics-comparison`) for provider timeout/partial-outcome exercises without the full 12-case corpus. Default per-agent timeout is 240000 ms, overridable with `--timeout-ms`.
26
+ - Added the `experiment run --experiment warm-index-reuse` campaign surface: `--campaign <preset>`, `--include-real-agents` (required), `--case`, `--benchmark-project`, `--timeout-ms`, `--kit-command`, `--out`. Campaign mode rejects `--target`, explicit `--cases`/`--project-profiles`, and the agent-matrix flags owned by `context-strategy-comparison`. Legacy non-campaign warm-index runs are unchanged.
27
+ - Real-agent evaluation consumes the exact `rawBaseline.contextText`/`warmRetrieval.contextText` the experiment already measured; providers never independently run my-dev-kit retrieval. The real-agent prompt explicitly forbids repository inspection, filesystem search, my-dev-kit execution, shell commands, external information, and file modification, and delivers the prompt over stdin. Each provider call runs from a neutral temporary working directory, never the repository, benchmark target, installed package, or agent artifact directory.
28
+ - Added Codex JSONL and Claude JSON response parsing to the existing agent adapters, sharing the existing prompt/parser/classifier/scorer pipeline with the deterministic fake agent.
29
+ - Extended provider outcome classification with `agent-unavailable`, `agent-limit-reached`, and `timeout`, alongside the existing `completed`, `failed`, `invalid-output`, and `skipped`. Warm-index infrastructure status (index build, raw/warm retrieval) and agent/provider outcome status are reported and reasoned about separately: infrastructure can report `completed` while campaign evidence is `partial` or `unavailable`.
30
+ - Extended the warm-index report (schema unchanged: `my-dev-kit-lab-warm-index-report-v1`) with agent identity (`agent.id`, `agent.mode`) and an additive campaign summary (preset, provider, timeout, selected/scheduled/executed/not-run-for-missing-context side counts, closed provider outcome counts, `agentEvidenceStatus`, `tokenEvidenceStatus`). Generic agent metric IDs (`agent-correctness-score`, `agent-total-tokens`, `cumulative-agent-total-tokens`) and their formulas are unchanged; correctness remains deterministic answer-key scoring and missing token evidence remains explicitly unavailable, never zero, never substituted with an estimated context-token count. The existing four warm-index plots are unchanged in formula and gained provider-aware titles/labels; a v1 report from before this change still plots correctly as legacy fake-agent evidence.
31
+ - A real-agent campaign run now automatically produces, beneath its output root: the existing report (`report.json`/`.txt`/`.html`), the same four warm-index SVG charts under `plots/`, a best-effort `report.png` (skipped without a Playwright/browser runtime — non-fatal — and reported as an unsuccessful command only on an unexpected capture failure, which still preserves the report/plots/gallery already written), and a narrow three-item campaign gallery (`gallery/gallery-manifest.json`, `gallery-index.html`: `warm-index-campaign-report`, `warm-index-campaign-plots`, `warm-index-execution`) with relative, bounded paths. Legacy non-campaign warm-index runs remain report-only. The `warm-index-reuse` plugin itself owns no presentation orchestration; the command layer sequences report → plots → screenshot → gallery.
32
+ - Hardened `runMeasuredCommand`'s command-timeout path: a bounded fallback now resolves the measured-command promise even if a killed process's descendant outlives an intermediate shim, and its streams are explicitly detached so the calling process can exit; a provider evaluation's neutral-working-directory cleanup is now best-effort so an OS-level cleanup failure never masks already-classified agent evidence.
33
+ - Extended the packed-package installed-execution acceptance gate (`npm run verify:packed-package`) with deterministic, cross-platform, local-only Codex/Claude provider fixtures proving, through the installed package: completed-with-tokens, token-unavailable, failed, invalid-output, agent-unavailable, agent-limit-reached, and timeout provider outcomes; the `codex-timeout-isolation` preset's continuation past a failed case; captured and skipped screenshot paths; the three-item campaign gallery; provider-argv privacy (no prompt/context in argv, frozen stdin transport flags); and target/installed-package immutability. No real Codex or Claude is ever invoked by this gate.
34
+
7
35
  ## [0.5.1] - 2026-09-22
8
36
 
9
37
  Expanded warm-index benchmark suite.
@@ -15,7 +43,6 @@ Expanded warm-index benchmark suite.
15
43
  - Regression tests prove the existing one-index-per-project warm-index runtime at six tasks per project: two index builds for the 12 tasks, task ordinals 1–6, amortized and cumulative metrics, per-project reset, strict-prefix availability, and isolation of single-task and single-project failures.
16
44
  - The existing warm-index report (schema `my-dev-kit-lab-warm-index-report-v1`) and the existing four plots cover the expanded suite: 2 projects, 12 tasks, and ordinals 1–6 per project.
17
45
  - The packed-package acceptance gate now checks that the corpus is in the tarball, readable from a clean install, and selectable through the installed CLI's existing `--cases` option.
18
- - No CLI command or flag, warm-index metric formula, report schema, or plot was added. The default cases file (`examples/token-savings-cases.json`) and the default my-dev-kit command are unchanged. Real-agent warm-index campaigns remain planned for v0.5.2.
19
46
 
20
47
  ## [0.5.0] - 2026-09-21
21
48
 
package/README.md CHANGED
@@ -6,16 +6,16 @@ my-dev-kit provides local repository indexing and graph-guided retrieval. my-dev
6
6
 
7
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
8
 
9
- The latest release is v0.5.1 (expanded warm-index benchmark suite). The previous release, v0.5.0, added warm-index reuse experiment support. 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.1 expanded warm-index benchmarks. The package and installed CLI are version 0.5.1; v0.5.0 remains the previous release. v0.5.2 real-agent warm-index campaigns are planned next. See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for the validated release state.
9
+ The latest release is v0.6.0 (index freshness and changed-file detection), and the package and installed CLI are version 0.6.0. The previous release is v0.5.2 (warm-index real-agent campaigns), and v0.6.1 (affected-neighborhood experiments) is next planned. 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) for current state.
12
10
 
13
11
  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.
14
12
 
15
13
  ## Current capabilities
16
14
 
17
15
  - **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.1):** 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. The v0.5.1 release adds a dedicated 12-task benchmark corpus (`benchmarks/contracts/warm-index-benchmark-cases.json`: six medium and six large/mixed tasks tagged `localized`, `cross-module`, or `broad-change`) for comparing warm-index behavior as task count grows. 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).
16
+ - **Measure warm-index reuse (v0.5.0–v0.5.1, released):** 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. The v0.5.1 release adds a dedicated 12-task benchmark corpus (`benchmarks/contracts/warm-index-benchmark-cases.json`: six medium and six large/mixed tasks tagged `localized`, `cross-module`, or `broad-change`) for comparing warm-index behavior as task count grows. 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.
17
+ - **Run selectable real-agent warm-index campaigns (v0.5.2, released):** `--campaign codex-full`, `claude-full`, or `codex-timeout-isolation` (with `--include-real-agents`) runs exactly one provider — Codex or Claude — against the exact raw and warm contexts the experiment already measured, over the frozen production corpus each preset owns. Provider outcomes (`completed`, `failed`, `timeout`, `invalid-output`, `agent-unavailable`, `agent-limit-reached`, `skipped`) are classified and reported separately from warm-index infrastructure status, so a campaign can complete its indexing/retrieval work even when provider evidence is partial or unavailable. A campaign run automatically produces the report, the same four warm-index SVG charts, a best-effort report screenshot (skipped without a browser runtime, never fatal), and a three-item gallery (report, plots, bounded execution evidence). Legacy runs without `--campaign` keep the existing deterministic fake-agent, report-only behavior unchanged. See [docs/WORKFLOWS.md](docs/WORKFLOWS.md#warm-index-reuse-experiment) and [docs/METRICS.md](docs/METRICS.md#warm-index-reuse-metrics).
18
+ - **Record index freshness evidence (v0.6.0, current):** a warm-index run captures an index snapshot after its one index build — the exact indexed file set from the my-dev-kit index contract, each with SHA-256 content identity, size, and modified-time metadata, plus the my-dev-kit version and index-command evidence — and assesses freshness immediately before each task's warm retrieval as `fresh`, `stale`, `partially-stale`, or `unknown`. The evidence appears in `warm-index-execution.json` and in the existing `report.json`/`report.txt`/`report.html` warm-index reports. It is observational only: it covers only files the snapshot lists (not the whole repository), never changes retrieval, execution or provider status, and adds no CLI flag, metric, plot, gallery item, or reindex recommendation. See [docs/METRICS.md](docs/METRICS.md#index-freshness-evidence-v060) and [docs/WORKFLOWS.md](docs/WORKFLOWS.md#warm-index-reuse-experiment).
19
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.
20
20
  - **Validate CLI/package security:** inspect dependencies, package contents, path and subprocess boundaries, malformed inputs, optional static scanners, and bounded fuzz targets.
21
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.
@@ -169,6 +169,19 @@ npm run generate-experiment-plots -- --experiment lab-output/warm-index-reuse --
169
169
 
170
170
  The default cases file, `examples/token-savings-cases.json`, is a small compatibility corpus with one task per benchmark project and remains the default when `--cases` is omitted. For multi-task reuse, select the dedicated corpus explicitly as above: it runs six tasks for each of the two benchmark projects against one index per project. Add `--benchmark-project task-workflow-medium-ts` or `--case <id>` to narrow the selection.
171
171
 
172
+ Run a selectable real-agent warm-index campaign (v0.5.2; requires a locally available `codex` or `claude` CLI and `--include-real-agents`). A campaign preset owns its provider and corpus, so `--target`, explicit `--cases`/`--project-profiles`, and the agent-matrix flags below are rejected in campaign mode:
173
+
174
+ ```bash
175
+ npm run experiment:run -- \
176
+ --experiment warm-index-reuse \
177
+ --campaign codex-full \
178
+ --include-real-agents \
179
+ --case warm-medium-complete-idempotent \
180
+ --out lab-output/warm-index-campaign
181
+ ```
182
+
183
+ This automatically writes `report.json`/`.txt`/`.html`, the four warm-index charts under `plots/`, a best-effort `report.png`, and a three-item `gallery/` (report, plots, bounded execution evidence) beneath `--out`. `--timeout-ms` overrides the preset's default per-agent timeout (240000 ms). `codex-timeout-isolation` narrows the corpus to three larger cases for exercising provider timeout/partial-outcome handling without running the full 12-case corpus.
184
+
172
185
  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.
173
186
 
174
187
  ---
@@ -186,7 +199,8 @@ When `--target` is omitted, the experiment runs in self mode against my-dev-kit-
186
199
  | Plugin experiment report JSON | `lab-output/experiments/<plugin>/<target>/<run>/report.json` |
187
200
  | Plugin experiment report HTML | `lab-output/experiments/<plugin>/<target>/<run>/report.html` |
188
201
  | 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) |
202
+ | Warm-index execution evidence | `<experiment out>/warm-index-execution.json` (plus `indexes/`, `commands/`, and `agents/` beneath the same output root); the v0.6.0 artifact carries project-level `indexSnapshot` and per-task `indexFreshness` evidence |
203
+ | Warm-index campaign presentation (v0.5.2) | `<experiment out>/plots/` (four charts), `<experiment out>/report.png` (best-effort), `<experiment out>/gallery/gallery-manifest.json` and `gallery-index.html` — automatic for `--campaign` runs only |
190
204
  | Plot data | `lab-output/<plots>/plot-data.json` |
191
205
  | SVG charts | `lab-output/<plots>/charts/*.svg` |
192
206
  | Gallery manifest | `lab-output/<gallery>/gallery-manifest.json` |
@@ -217,11 +231,11 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
217
231
  ## Current limitations
218
232
 
219
233
  - Token savings shown in fake-agent runs are based on estimated character counts, not provider billing telemetry
220
- - Claude does not expose token totals; token savings comparisons are unavailable for Claude runs
221
- - Codex may expose token totals but can produce timeouts or invalid-output runs
234
+ - Claude and Codex token totals are used only when the CLI/run exposes them; missing provider token evidence stays explicitly unavailable (never zero, and never substituted with an estimated context-token count)
235
+ - Codex and Claude campaign runs can produce timeouts, invalid-output, agent-unavailable, or agent-limit-reached outcomes; these are structured provider states, not warm-index infrastructure failures
222
236
  - 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
223
237
  - The generic experiment-plugin framework has two plugins: `context-strategy-comparison` and the v0.5.0-introduced `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, and warm-index screenshots/gallery integration are not implemented. The v0.5.1 expanded corpus is deterministic fake-agent benchmark evidence, not real-agent evidence. Its component-duration sums are not wall-clock latency, and it calculates no token-savings percentage, break-even task, winner, or ranking
238
+ - `warm-index-reuse` without `--campaign` still uses the deterministic fake agent only. With `--campaign`, exactly one real provider (Codex or Claude) runs per campaign preset — there is no multi-agent matrix, retry, provider switching, or scheduler. The v0.5.1 expanded corpus is deterministic fake-agent benchmark evidence when run without `--campaign`. No warm-index mode calculates a token-savings percentage, break-even task, winner, or ranking, and component-duration sums are not wall-clock latency
225
239
  - The current release does not guarantee token savings; it produces auditable evidence for specific cases, targets, agents, and strategies
226
240
  - Provider telemetry dashboards, semantic LLM judging, and cloud API billing integration are not yet implemented
227
241
  - The six new stage-context strategies have no CLI flags yet, are configured programmatically, and do not yet include plots, screenshots, or gallery integration
@@ -231,6 +245,7 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
231
245
  - 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.
232
246
  - 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
247
  - 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.
248
+ - v0.5.2 (released) selectable real-agent warm-index campaigns run exactly one provider per preset (`codex-full`, `claude-full`, `codex-timeout-isolation`); there is no retry, provider switching, or scheduler. Correctness remains deterministic answer-key scoring, not semantic LLM judging. No composite score, winner, ranking, or break-even task is calculated for campaign or legacy warm-index runs.
234
249
 
235
250
  ---
236
251
 
@@ -2,6 +2,20 @@ import { runCliAgent } from "./codexAdapter.js";
2
2
  import { applyPromptToCommandTemplate } from "../runAgentPrompt.js";
3
3
  import { parseAgentTokenUsage } from "../parseAgentTokenUsage.js";
4
4
  import { runMeasuredCommand } from "../../core/runMeasuredCommand.js";
5
+ // v0.5.2 Batch 2 -- frozen stdin invocation for the real-agent evaluation transport. Every flag
6
+ // here is planner-owned; --bare is intentionally not used (it changes auth behavior and still
7
+ // exposes Bash/read/edit tools unless separately constrained).
8
+ const CLAUDE_STDIN_ARGS = [
9
+ "--restricted",
10
+ "-p",
11
+ "--output-format",
12
+ "json",
13
+ "--no-session-persistence",
14
+ "--tools",
15
+ "",
16
+ "--disallowedTools",
17
+ "mcp__*"
18
+ ];
5
19
  export const claudeAdapter = {
6
20
  id: "claude",
7
21
  displayName: "Claude",
@@ -21,17 +35,46 @@ export const claudeAdapter = {
21
35
  return check.ok;
22
36
  },
23
37
  buildCommand(request) {
38
+ if (request.promptTransport === "stdin" && request.commandTemplate) {
39
+ throw new Error("claude agent: stdin prompt transport cannot be combined with a command template.");
40
+ }
24
41
  if (request.commandTemplate) {
25
42
  return applyPromptToCommandTemplate(request.commandTemplate, request.promptText);
26
43
  }
44
+ if (request.promptTransport === "stdin") {
45
+ return { command: "claude", args: [...CLAUDE_STDIN_ARGS], stdinText: request.promptText };
46
+ }
27
47
  return { command: "claude", args: ["-p", request.promptText] };
28
48
  },
29
49
  async runPrompt(request) {
30
50
  return runCliAgent(request, this);
31
51
  },
32
52
  parseTokenUsage: parseAgentTokenUsage,
33
- parseFinalAnswer(text) {
34
- const trimmed = text.trim();
35
- return { finalAnswerText: trimmed, finalAnswerParseStatus: trimmed ? "parsed" : "empty" };
36
- }
53
+ parseFinalAnswer: parseClaudeFinalAnswer
37
54
  };
55
+ /**
56
+ * Parses a single JSON result envelope (`{ "result": "..." }`) when stdout is valid JSON, never
57
+ * the raw envelope itself. Falls back to the legacy plain-text behavior when stdout is not valid
58
+ * JSON, so existing argv-mode text output keeps working unchanged.
59
+ */
60
+ export function parseClaudeFinalAnswer(text) {
61
+ const trimmedInput = text.trim();
62
+ if (trimmedInput) {
63
+ try {
64
+ const parsed = JSON.parse(trimmedInput);
65
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
66
+ const payload = parsed;
67
+ if (typeof payload.result === "string") {
68
+ const resultText = payload.result.trim();
69
+ return { finalAnswerText: resultText, finalAnswerParseStatus: resultText ? "parsed" : "empty" };
70
+ }
71
+ // Valid JSON object without a string result: empty, never the raw envelope.
72
+ return { finalAnswerText: "", finalAnswerParseStatus: "empty" };
73
+ }
74
+ }
75
+ catch {
76
+ // Not valid JSON; fall through to the legacy plain-text behavior below.
77
+ }
78
+ }
79
+ return { finalAnswerText: trimmedInput, finalAnswerParseStatus: trimmedInput ? "parsed" : "empty" };
80
+ }
@@ -1,6 +1,17 @@
1
1
  import { runMeasuredCommand } from "../../core/runMeasuredCommand.js";
2
2
  import { applyPromptToCommandTemplate } from "../runAgentPrompt.js";
3
3
  import { parseAgentTokenUsage } from "../parseAgentTokenUsage.js";
4
+ // v0.5.2 Batch 2 -- frozen stdin invocation for the real-agent evaluation transport. Every flag
5
+ // here is planner-owned; do not add sandbox/model/approval-policy flags in this batch.
6
+ const CODEX_STDIN_ARGS = [
7
+ "exec",
8
+ "--json",
9
+ "--ephemeral",
10
+ "--skip-git-repo-check",
11
+ "--ignore-user-config",
12
+ "--ignore-rules",
13
+ "-"
14
+ ];
4
15
  export const codexAdapter = {
5
16
  id: "codex",
6
17
  displayName: "Codex",
@@ -20,20 +31,67 @@ export const codexAdapter = {
20
31
  return check.ok;
21
32
  },
22
33
  buildCommand(request) {
34
+ if (request.promptTransport === "stdin" && request.commandTemplate) {
35
+ throw new Error("codex agent: stdin prompt transport cannot be combined with a command template.");
36
+ }
23
37
  if (request.commandTemplate) {
24
38
  return applyPromptToCommandTemplate(request.commandTemplate, request.promptText);
25
39
  }
40
+ if (request.promptTransport === "stdin") {
41
+ return { command: "codex", args: [...CODEX_STDIN_ARGS], stdinText: request.promptText };
42
+ }
26
43
  return { command: "codex", args: ["exec", "--json", request.promptText] };
27
44
  },
28
45
  async runPrompt(request) {
29
46
  return runCliAgent(request, this);
30
47
  },
31
48
  parseTokenUsage: parseAgentTokenUsage,
32
- parseFinalAnswer(text) {
33
- const trimmed = text.trim();
34
- return { finalAnswerText: trimmed, finalAnswerParseStatus: trimmed ? "parsed" : "empty" };
35
- }
49
+ parseFinalAnswer: parseCodexFinalAnswer
36
50
  };
51
+ /**
52
+ * Recognizes a Codex JSONL event stream (at least one line parses to an object with a string
53
+ * `type`) and, when recognized, uses the LAST `item.completed` `agent_message` text as the final
54
+ * answer -- never the raw JSONL. Falls back to the legacy plain-text behavior when no event-shaped
55
+ * line is found, so non-JSONL Codex output (and existing tests) keep working unchanged.
56
+ */
57
+ export function parseCodexFinalAnswer(text) {
58
+ let sawEventLine = false;
59
+ let lastAgentMessageText = null;
60
+ for (const line of text.split(/\r?\n/)) {
61
+ const trimmedLine = line.trim();
62
+ if (!trimmedLine)
63
+ continue;
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(trimmedLine);
67
+ }
68
+ catch {
69
+ // Agent output can mix JSONL with incidental non-JSON noise; ignore malformed lines.
70
+ continue;
71
+ }
72
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
73
+ continue;
74
+ const event = parsed;
75
+ if (typeof event.type !== "string")
76
+ continue;
77
+ sawEventLine = true;
78
+ if (event.type === "item.completed") {
79
+ const item = event.item;
80
+ if (item && typeof item === "object" && item.type === "agent_message") {
81
+ const itemText = item.text;
82
+ if (typeof itemText === "string") {
83
+ lastAgentMessageText = itemText;
84
+ }
85
+ }
86
+ }
87
+ }
88
+ if (sawEventLine) {
89
+ const finalAnswerText = lastAgentMessageText ?? "";
90
+ return { finalAnswerText, finalAnswerParseStatus: finalAnswerText ? "parsed" : "empty" };
91
+ }
92
+ const trimmed = text.trim();
93
+ return { finalAnswerText: trimmed, finalAnswerParseStatus: trimmed ? "parsed" : "empty" };
94
+ }
37
95
  export async function runCliAgent(request, adapter) {
38
96
  const started = Date.now();
39
97
  const command = adapter.buildCommand(request);
@@ -74,7 +132,8 @@ export async function runCliAgent(request, adapter) {
74
132
  cwd: request.commandTemplate?.cwd ?? request.cwd,
75
133
  outDir: request.outDir,
76
134
  env: request.env,
77
- timeoutMs: request.timeoutMs
135
+ timeoutMs: request.timeoutMs,
136
+ stdinText: command.stdinText
78
137
  });
79
138
  const ended = Date.now();
80
139
  const combinedOutput = `${measured.stdout}\n${measured.stderr}`;
@@ -157,8 +157,29 @@ export function renderExperimentRunHelp() {
157
157
  " --kit-command <command> my-dev-kit command used to build one index per benchmark",
158
158
  " project and retrieve per task",
159
159
  " (default: npx @dailephd/my-dev-kit@latest)",
160
- " Agent evidence uses the deterministic fake agent only;",
160
+ " Legacy/default mode uses the deterministic fake agent only;",
161
161
  " agent options below are not accepted by this plugin.",
162
+ " --campaign <preset> Runs the selected real-agent campaign preset: codex-full,",
163
+ " claude-full, codex-timeout-isolation. Selects Codex or",
164
+ " Claude as the preset's single provider and requires",
165
+ " --include-real-agents. The preset owns the bundled",
166
+ " production corpus and project profiles; explicit",
167
+ " --cases/--project-profiles and --target are rejected in",
168
+ " campaign mode. --case and --benchmark-project narrow the",
169
+ " preset's case set. --kit-command and --timeout-ms are",
170
+ " accepted. The report distinguishes warm-index",
171
+ " infrastructure status from agent/provider outcome status;",
172
+ " provider token telemetry may be unavailable and is never",
173
+ " replaced by a context-size estimate. A campaign run",
174
+ " automatically produces the report JSON/text/HTML, the",
175
+ " four warm-index SVG plots, a best-effort report PNG",
176
+ " screenshot, and a gallery manifest/index. A missing",
177
+ " Playwright/browser runtime makes screenshot capture skip",
178
+ " without invalidating the campaign; an unexpected",
179
+ " screenshot failure makes the command unsuccessful after",
180
+ " preserving the report, plots, and gallery. Legacy",
181
+ " non-campaign warm-index runs retain their existing",
182
+ " report-only behavior.",
162
183
  "",
163
184
  "context-strategy-comparison only:",
164
185
  " --agents <list> Comma-separated agent ids (default: fake-agent)",
@@ -145,11 +145,16 @@ function describeExamples(plugin) {
145
145
  // Only advertise agent-matrix options for plugins that declare them.
146
146
  const agentOptions = hasConfigField(plugin, "agents") ? " --agents fake-agent --complexities short" : "";
147
147
  const screenshotOption = hasConfigField(plugin, "agents") ? " --no-screenshot" : "";
148
- return [
148
+ const examples = [
149
149
  `my-dev-kit-lab experiment describe --experiment ${plugin.metadata.id}`,
150
150
  `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id}${agentOptions}`,
151
151
  `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target "Z:\\Users\\newuser\\Projects\\my-dev-kit-v1"${agentOptions}${screenshotOption}`
152
152
  ];
153
+ // Only advertise campaign examples for plugins that declare the campaignPreset config field.
154
+ if (hasConfigField(plugin, "campaignPreset")) {
155
+ examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --campaign codex-full --include-real-agents --out <dir>`, `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --campaign claude-full --include-real-agents --case warm-medium-complete-idempotent --out <dir>`);
156
+ }
157
+ return examples;
153
158
  }
154
159
  function hasConfigField(plugin, name) {
155
160
  return (plugin.configDefinition?.fields ?? []).some((field) => field.name === name);
@@ -2,11 +2,12 @@ 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, warmIndexReusePlugin } from "../experiments/index.js";
5
+ import { contextStrategyComparisonPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, 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";
9
9
  import { createLabExecutionContext, resolvePackageResource } from "../runtime/index.js";
10
+ import { runWarmIndexCampaignPresentation } from "./runWarmIndexCampaignPresentation.js";
10
11
  // ---------------------------------------------------------------------------
11
12
  // v0.4.6 Batch 4 -- reusable experiment-run command owner.
12
13
  //
@@ -61,7 +62,7 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
61
62
  plugin: registry.describe(result.pluginId),
62
63
  outputRoot: String(result.metadata?.outputRoot ?? "")
63
64
  });
64
- console.log([
65
+ const outputLines = [
65
66
  `Experiment: ${result.pluginId}`,
66
67
  `Run ID: ${result.runId}`,
67
68
  `Status: ${result.status}`,
@@ -71,8 +72,51 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
71
72
  `Output: ${String(result.metadata?.outputRoot ?? "")}`,
72
73
  `Report JSON: ${reports.outputPaths.jsonPath}`,
73
74
  `Report HTML: ${reports.outputPaths.htmlPath}`
74
- ].join("\n"));
75
- return result.status === "failed" ? 1 : 0;
75
+ ];
76
+ // Automatic presentation (report already produced above) applies only to warm-index-reuse
77
+ // campaign runs; legacy fake-agent warm-index runs and context-strategy-comparison stay
78
+ // report-only, unchanged.
79
+ let screenshotFailed = false;
80
+ if (args.experimentId === warmIndexReusePlugin.metadata.id && args.config.campaignPreset) {
81
+ const executionArtifactPath = readMetadataString(result.metadata?.executionArtifactPath);
82
+ const campaignAgentId = readMetadataString(result.metadata?.campaignAgentId);
83
+ if (!executionArtifactPath) {
84
+ throw new Error("Warm-index campaign presentation requires an execution artifact path on the completed run.");
85
+ }
86
+ if (campaignAgentId !== "codex" && campaignAgentId !== "claude") {
87
+ throw new Error(`Warm-index campaign presentation requires a codex or claude campaign agent id on the completed run; received ${String(campaignAgentId)}.`);
88
+ }
89
+ const presentation = await runWarmIndexCampaignPresentation({
90
+ outputRoot: String(result.metadata?.outputRoot ?? ""),
91
+ reportPaths: reports.outputPaths,
92
+ executionArtifactPath,
93
+ campaignPreset: args.config.campaignPreset,
94
+ agentId: campaignAgentId,
95
+ captureScreenshot: options.presentation?.captureScreenshot
96
+ });
97
+ outputLines.push(`Plots: ${presentation.plots.artifactPaths.summaryPath}`);
98
+ if (presentation.screenshot.status === "captured") {
99
+ outputLines.push(`Screenshot: ${presentation.screenshot.pngPath}`);
100
+ }
101
+ else if (presentation.screenshot.status === "skipped") {
102
+ outputLines.push("Screenshot: skipped");
103
+ if (presentation.screenshot.warning)
104
+ outputLines.push(presentation.screenshot.warning);
105
+ }
106
+ else {
107
+ outputLines.push("Screenshot: failed");
108
+ outputLines.push(presentation.screenshot.error ?? "Screenshot capture failed.");
109
+ screenshotFailed = true;
110
+ }
111
+ outputLines.push(`Gallery manifest: ${presentation.gallery.manifestPath}`);
112
+ outputLines.push(`Gallery index: ${presentation.gallery.indexPath}`);
113
+ }
114
+ console.log(outputLines.join("\n"));
115
+ if (result.status === "failed")
116
+ return 1;
117
+ if (screenshotFailed)
118
+ return 1;
119
+ return 0;
76
120
  }
77
121
  catch (error) {
78
122
  if (process.env.DEBUG) {
@@ -101,6 +145,7 @@ export function parseRunExperimentArgs(argv) {
101
145
  let requireAgents;
102
146
  let includeRealAgents;
103
147
  let kitCommand;
148
+ let campaignPreset;
104
149
  const commandTemplates = {};
105
150
  for (let index = 0; index < argv.length; index += 1) {
106
151
  const arg = argv[index];
@@ -161,6 +206,9 @@ export function parseRunExperimentArgs(argv) {
161
206
  else if (arg === "--kit-command") {
162
207
  kitCommand = readRequiredValue(argv, ++index, "--kit-command");
163
208
  }
209
+ else if (arg === "--campaign") {
210
+ campaignPreset = parseWarmIndexCampaignPresetId(readRequiredValue(argv, ++index, "--campaign"));
211
+ }
164
212
  else if (arg === "--no-screenshot") {
165
213
  // The plugin-aware report path does not capture screenshots yet; accept this
166
214
  // flag so smoke commands can share the legacy demo option set.
@@ -175,6 +223,20 @@ export function parseRunExperimentArgs(argv) {
175
223
  if (kitCommand !== undefined && experimentId !== warmIndexReusePlugin.metadata.id) {
176
224
  throw new Error(`--kit-command is only supported for --experiment ${warmIndexReusePlugin.metadata.id}.`);
177
225
  }
226
+ if (campaignPreset !== undefined) {
227
+ if (experimentId !== warmIndexReusePlugin.metadata.id) {
228
+ throw new Error(`--campaign is only supported for --experiment ${warmIndexReusePlugin.metadata.id}.`);
229
+ }
230
+ if (targetPath !== undefined) {
231
+ throw new Error("--campaign cannot be combined with --target; campaigns run only against bundled synthetic benchmark projects.");
232
+ }
233
+ if (casesPath !== undefined) {
234
+ throw new Error("--campaign cannot be combined with --cases; the selected preset owns the production corpus.");
235
+ }
236
+ if (projectProfilesPath !== undefined) {
237
+ throw new Error("--campaign cannot be combined with --project-profiles; the selected preset owns the project profiles.");
238
+ }
239
+ }
178
240
  return {
179
241
  experimentId,
180
242
  targetPath,
@@ -193,7 +255,8 @@ export function parseRunExperimentArgs(argv) {
193
255
  requireAgents,
194
256
  includeRealAgents,
195
257
  commandTemplates: Object.keys(commandTemplates).length > 0 ? commandTemplates : undefined,
196
- kitCommand
258
+ kitCommand,
259
+ campaignPreset
197
260
  })
198
261
  };
199
262
  }
@@ -232,6 +295,9 @@ async function loadPluginInputs(args, toolRoot, context) {
232
295
  if (!validation.valid || !validation.config) {
233
296
  throw new Error(`Invalid warm index reuse config: ${validation.errors.join("; ")}`);
234
297
  }
298
+ if (args.config.campaignPreset) {
299
+ return loadCampaignCasesAndProjectProfiles(args.config.campaignPreset, toolRoot, context);
300
+ }
235
301
  return loadCasesAndProjectProfiles(args, toolRoot, context);
236
302
  }
237
303
  return undefined;
@@ -252,6 +318,19 @@ async function loadCasesAndProjectProfiles(args, toolRoot, context) {
252
318
  });
253
319
  return { cases, projectProfiles, env: process.env };
254
320
  }
321
+ // Campaign resource resolution (v0.5.2 Batch 1): the preset owns both bundled resource paths;
322
+ // explicit --cases/--project-profiles are already rejected for campaign mode by argument parsing.
323
+ async function loadCampaignCasesAndProjectProfiles(campaignPresetId, toolRoot, context) {
324
+ const preset = getWarmIndexCampaignPreset(campaignPresetId);
325
+ const projectProfilesPath = resolvePackageResource(context, preset.projectProfilesResourcePath);
326
+ const casesPath = resolvePackageResource(context, preset.casesResourcePath);
327
+ const projectProfiles = await readBenchmarkProjectProfiles(projectProfilesPath, toolRoot);
328
+ const cases = await readEvaluationCases(casesPath, toolRoot, {
329
+ projectProfiles,
330
+ requireProjectProfileRef: true
331
+ });
332
+ return { cases, projectProfiles, env: process.env };
333
+ }
255
334
  function splitList(value) {
256
335
  return value
257
336
  .split(",")
@@ -272,6 +351,9 @@ function parsePositiveInteger(label, value) {
272
351
  }
273
352
  return parsed;
274
353
  }
354
+ function readMetadataString(value) {
355
+ return typeof value === "string" && value.trim() ? value : undefined;
356
+ }
275
357
  function withoutUndefined(value) {
276
358
  return Object.fromEntries(Object.entries(value).filter(([, entryValue]) => entryValue !== undefined));
277
359
  }
@@ -0,0 +1,64 @@
1
+ import path from "node:path";
2
+ import { writeWarmIndexCampaignGallery } from "../gallery/writeWarmIndexCampaignGallery.js";
3
+ import { captureReportScreenshot } from "../screenshot/captureReportScreenshot.js";
4
+ import { writePlotArtifacts } from "../plots/writePlotArtifacts.js";
5
+ // ---------------------------------------------------------------------------
6
+ // v0.5.2 Batch 5 -- command-level warm-index campaign presentation owner.
7
+ //
8
+ // This is the single orchestrator for: existing report -> existing four warm-
9
+ // index plots -> best-effort report screenshot -> narrow warm-index campaign
10
+ // gallery. It reuses writePlotArtifacts, captureReportScreenshot, and
11
+ // writeWarmIndexCampaignGallery unchanged; it builds no new plot, screenshot,
12
+ // or gallery mechanism of its own. The warm-index-reuse plugin remains free of
13
+ // this orchestration -- it is invoked only by the command layer, and only for
14
+ // campaign runs.
15
+ // ---------------------------------------------------------------------------
16
+ const EXPECTED_WARM_INDEX_CHART_IDS = [
17
+ "warm-index-amortized-index-cost",
18
+ "warm-index-context-size",
19
+ "warm-index-correctness",
20
+ "warm-index-cumulative-token-usage",
21
+ ];
22
+ export async function runWarmIndexCampaignPresentation(args) {
23
+ const plotsDir = path.join(args.outputRoot, "plots");
24
+ const plots = await writePlotArtifacts({ experimentDir: args.outputRoot, outDir: plotsDir });
25
+ const chartIds = Object.keys(plots.artifactPaths.charts);
26
+ const hasExactlyExpectedCharts = chartIds.length === EXPECTED_WARM_INDEX_CHART_IDS.length &&
27
+ EXPECTED_WARM_INDEX_CHART_IDS.every((id) => chartIds.includes(id));
28
+ if (!hasExactlyExpectedCharts) {
29
+ throw new Error(`Warm-index campaign presentation requires exactly the four warm-index plot charts (${EXPECTED_WARM_INDEX_CHART_IDS.join(", ")}); got: ${chartIds.join(", ") || "none"}.`);
30
+ }
31
+ const capture = args.captureScreenshot ?? captureReportScreenshot;
32
+ const pngPath = path.join(args.outputRoot, "report.png");
33
+ // Non-fatal by design: a missing Playwright/browser runtime is a normal, expected environment,
34
+ // never a reason to fail the campaign (see captureReportScreenshot's captured/skipped/failed contract).
35
+ const screenshot = await capture(args.reportPaths.htmlPath, pngPath);
36
+ const gallery = await writeWarmIndexCampaignGallery({
37
+ outDir: path.join(args.outputRoot, "gallery"),
38
+ campaignOutputRoot: args.outputRoot,
39
+ reportPaths: {
40
+ jsonPath: args.reportPaths.jsonPath,
41
+ htmlPath: args.reportPaths.htmlPath,
42
+ textPath: args.reportPaths.textPath,
43
+ },
44
+ executionArtifactPath: args.executionArtifactPath,
45
+ plotArtifacts: plots,
46
+ screenshot,
47
+ campaignPreset: args.campaignPreset,
48
+ agentId: args.agentId,
49
+ });
50
+ const warnings = [];
51
+ if (screenshot.status === "skipped" && screenshot.warning) {
52
+ warnings.push(screenshot.warning);
53
+ }
54
+ else if (screenshot.status === "failed") {
55
+ warnings.push(`Report screenshot capture failed: ${screenshot.error ?? "unknown error"}.`);
56
+ }
57
+ warnings.push(...plots.summary.warnings);
58
+ return {
59
+ plots,
60
+ screenshot,
61
+ gallery: { manifest: gallery.manifest, manifestPath: gallery.manifestPath, indexPath: gallery.indexPath },
62
+ warnings,
63
+ };
64
+ }