@dailephd/my-dev-kit-lab 0.6.1 → 0.6.3

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 (61) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +9 -7
  3. package/benchmarks/contracts/incremental-change-staleness-scenarios.json +360 -0
  4. package/dist/src/cli/help.js +16 -9
  5. package/dist/src/commands/runExperimentDescribeCommand.js +5 -2
  6. package/dist/src/commands/runExperimentRunCommand.js +4 -7
  7. package/dist/src/evaluation/affectedNeighborhoodMetrics.js +51 -0
  8. package/dist/src/evaluation/benchmarkMetadata.js +5 -1
  9. package/dist/src/evaluation/parseAgentAnswer.js +172 -24
  10. package/dist/src/evaluation/runMyDevKitRetrieval.js +120 -7
  11. package/dist/src/experiments/defaultRegistry.js +2 -0
  12. package/dist/src/experiments/plugins/incrementalChangeStaleness/comparison.js +101 -0
  13. package/dist/src/experiments/plugins/incrementalChangeStaleness/comparisonV2.js +49 -0
  14. package/dist/src/experiments/plugins/incrementalChangeStaleness/config.js +67 -0
  15. package/dist/src/experiments/plugins/incrementalChangeStaleness/disposableTarget.js +125 -0
  16. package/dist/src/experiments/plugins/incrementalChangeStaleness/execution.js +430 -0
  17. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifact.js +160 -0
  18. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifactV2.js +289 -0
  19. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionV2.js +195 -0
  20. package/dist/src/experiments/plugins/incrementalChangeStaleness/index.js +13 -0
  21. package/dist/src/experiments/plugins/incrementalChangeStaleness/indexClone.js +76 -0
  22. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycle.js +308 -0
  23. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicy.js +154 -0
  24. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicyV2.js +78 -0
  25. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycleV2.js +298 -0
  26. package/dist/src/experiments/plugins/incrementalChangeStaleness/mutationExecution.js +144 -0
  27. package/dist/src/experiments/plugins/incrementalChangeStaleness/plugin.js +475 -0
  28. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioCatalog.js +42 -0
  29. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioMutation.js +116 -0
  30. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioSelection.js +79 -0
  31. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioTypes.js +37 -0
  32. package/dist/src/experiments/plugins/incrementalChangeStaleness/scenarioValidation.js +168 -0
  33. package/dist/src/experiments/plugins/incrementalChangeStaleness/sourceState.js +145 -0
  34. package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSession.js +6 -0
  35. package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSessionV2.js +5 -0
  36. package/dist/src/experiments/plugins/index.js +1 -0
  37. package/dist/src/experiments/plugins/warmIndexReuse/metrics.js +17 -20
  38. package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +3 -1
  39. package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +9 -2
  40. package/dist/src/mobile/android/advancedSecurity/redaction.js +19 -10
  41. package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +12 -2
  42. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +1 -1
  43. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +7 -7
  44. package/dist/src/report/experiments/buildIncrementalChangeStalenessPluginReport.js +33 -0
  45. package/dist/src/report/experiments/buildIncrementalChangeStalenessReport.js +112 -0
  46. package/dist/src/report/experiments/buildIncrementalChangeStalenessReportV2.js +80 -0
  47. package/dist/src/report/experiments/buildPluginExperimentReport.js +3 -0
  48. package/dist/src/report/experiments/incrementalChangeStalenessReportModel.js +4 -0
  49. package/dist/src/report/experiments/incrementalChangeStalenessReportModelV2.js +3 -0
  50. package/dist/src/report/experiments/incrementalChangeStalenessReportRowsV2.js +146 -0
  51. package/dist/src/report/experiments/index.js +6 -0
  52. package/dist/src/report/experiments/renderIncrementalChangeStalenessHtml.js +243 -0
  53. package/dist/src/report/experiments/renderIncrementalChangeStalenessHtmlV2.js +111 -0
  54. package/dist/src/report/experiments/renderIncrementalChangeStalenessTextV2.js +96 -0
  55. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  56. package/dist/src/report/experiments/renderPluginExperimentReportText.js +144 -0
  57. package/dist/src/securityValidation/staticScans/codeql.js +8 -8
  58. package/docs/METRICS.md +67 -1
  59. package/examples/tutorial-browser/prepare.mjs +13 -13
  60. package/examples/tutorial-browser/server.mjs +13 -13
  61. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,38 @@ All notable changes to my-dev-kit-lab are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.3] - 2026-09-30
8
+
9
+ ### Four-treatment partial-refresh experiments
10
+
11
+ - Replaced CodeQL-flagged polynomial regular-expression parsing paths with linear scanning and strengthened credential evidence fingerprints with context-salted scrypt derivation.
12
+
13
+ - Extended the `incremental-change-staleness` plugin from two treatments to four, in fixed order: `stale-index` (`my-dev-kit-no-refresh`), `changed-files-refresh` (`my-dev-kit-changed-files-refresh`), `affected-neighborhood-refresh` (`my-dev-kit-affected-neighborhood-refresh`), and `full-refresh` (`my-dev-kit-full-refresh`). No new experiment plugin was added.
14
+ - Changed the default kit command of `incremental-change-staleness` from `npx @dailephd/my-dev-kit@1.12.4` to `npx @dailephd/my-dev-kit@1.12.5`, the first published version with `index --incremental --refresh-scope changed-files|affected-neighborhood`. Lab requests the scope internally; no Lab-level `--refresh-scope` flag exists.
15
+ - Added a four-treatment lifecycle: four isolated disposable targets, equivalent trusted baselines, containment-checked whole-directory index clones for the two partial treatments, a mutation barrier, and per-treatment freshness validation. `stale-index` performs no post-mutation index invocation.
16
+ - Added truthful refresh evidence: upstream `requestedScope`, `appliedScope`, `selectionStatus`, `fallbackReason`, fresh-extraction, reused-file, forced-neighbor, and seed/affected counts, and a Lab `refreshRealization` of `NO_REFRESH`, `APPLIED_PARTIAL`, `FALLBACK_FULL`, or `FULL_REFRESH`. A partial treatment that falls back is `not-comparable-as-partial-refresh`.
17
+ - Added exactly three reference comparisons against `full-refresh`: `stale-index` (unchanged v0.6.2 stale-risk vocabulary) and the two partial treatments (`observed-regression-relative-to-full`, `no-observed-regression-relative-to-full`, `inconclusive`, `not-comparable-as-partial-refresh`). There is no winner, ranking, safety score, or automatic refresh policy.
18
+ - New runs write execution schema `my-dev-kit-lab-incremental-change-staleness-execution-v2` and report schema `my-dev-kit-lab-incremental-change-staleness-report-v2` (JSON, text, HTML); historical V1 artifacts and reports remain readable. Added additive numeric `upstream-refresh-*` metrics for the partial treatments, kept separate from the Lab affected-neighborhood metrics.
19
+ - `experiment run --help` now states that `--target` applies only to plugins that support external targets and that self-only plugins reject an explicit external `--target`.
20
+ - Automatic warm-index campaign report screenshots are best-effort presentation evidence: failed capture stays explicitly failed with its warning and no fabricated PNG, but does not fail an otherwise successful campaign. Required campaign, report, plot, and gallery failures remain fatal.
21
+ - Updated the packed-package acceptance gate (`npm run verify:packed-package`) to pin the real registry package `@dailephd/my-dev-kit@1.12.5` and to verify all six scenarios with four treatments, applied partial refresh, operational discrimination, and installed-package, upstream-package, and canonical-benchmark immutability.
22
+ - Added repository-controlled GitHub CodeQL advanced-setup analysis for JavaScript/TypeScript and Actions; the release candidate passed exact-SHA analysis with no open applicable alerts.
23
+ - No new plugin, `graph-diff` dependency, true-symbol-diff metric, multi-hop traversal, automatic refresh policy, external-target support, or new plot, screenshot, or gallery item was added.
24
+
25
+ ## [0.6.2] - 2026-09-27
26
+
27
+ ### Incremental-change and staleness plugin
28
+
29
+ - Added the `incremental-change-staleness` experiment plugin (`src/experiments/plugins/incrementalChangeStaleness/`), registered alongside `context-strategy-comparison` and `warm-index-reuse` with registry status `experimental` and exactly two treatments, `stale-index` and `full-refresh`.
30
+ - Added a separate versioned scenario catalog (`benchmarks/contracts/incremental-change-staleness-scenarios.json`, schema `1.0.0`) with six frozen controlled-change scenarios (`U1` unrelated-file, `L2` local-implementation, `E1` exported-symbol, `P1` public-API, `I1` import-graph, `T1` test-only), a bounded declarative mutation contract (relative path, pre/post SHA-256, ordered literal exact-preimage replacements, exactly-one-match validation), and an `inherit`/`scenario` answer-key policy reusing the existing benchmark answer-key vocabulary.
31
+ - Added disposable treatment targets: each scenario runs against two independent disposable copies of the same immutable benchmark project, proving pre-mutation and post-mutation controlled-source equivalence before and after the shared deterministic mutation.
32
+ - Added the stale/full-refresh index lifecycle: both treatments build a baseline index/snapshot/graph before mutation; `stale-index` keeps retrieving from that baseline index, while `full-refresh` builds a distinct complete post-mutation index and retrieves from it. Affected-neighborhood evidence (the released v0.6.1 six numeric metrics, `relationship`, and `reindexRecommendation`) is asserted symmetric across both treatments before retrieval; the six-metric conversion is now shared through `src/evaluation/affectedNeighborhoodMetrics.ts`.
33
+ - Added deterministic fake-agent retrieval/correctness evidence, required-file evidence (`present`/`missing`/`unknown`), a `correctnessRelation` and `requiredFileEvidenceRelation` (`stale-worse`/`same`/`stale-better`/`unknown`), and a conservative `staleRiskClassification` (`observed-stale-regression`/`no-observed-stale-regression`/`inconclusive`) with no numeric stale-risk score, no treatment winner, and no safety verdict.
34
+ - Added the persisted execution artifact `incremental-change-staleness-execution.json` (schema `my-dev-kit-lab-incremental-change-staleness-execution-v1`) and plugin `report.json`/`report.txt`/`report.html` presentation built from that persisted evidence only, with fixed limitations text.
35
+ - `experiment run --kit-command` now also accepts `incremental-change-staleness`, in addition to `warm-index-reuse`; it remains rejected for `context-strategy-comparison`.
36
+ - Validated the exact npm tarball end to end against the real published `@dailephd/my-dev-kit@1.12.4`: all six scenarios ran through the installed CLI, producing the execution artifact and all three reports while keeping the installed package, canonical benchmarks, and source checkout immutable.
37
+ - No partial refresh, no `graph-diff` dependency, no true-symbol-diff metric, and no new plot, screenshot, gallery item, or top-level CLI command were added.
38
+
7
39
  ## [0.6.1] - 2026-09-26
8
40
 
9
41
  ### Affected-neighborhood experiments
package/README.md CHANGED
@@ -6,7 +6,7 @@ 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.6.1 (affected-neighborhood experiments), and the package and installed CLI are version 0.6.1. The previous release is v0.6.0 (index freshness and changed-file detection). Version v0.6.2 (incremental-change and staleness plugin) is the next planned version. 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.
9
+ The latest release is v0.6.3 (four-treatment partial-refresh experiments), and the published package and installed CLI are version 0.6.3. The previous release is v0.6.2 (incremental-change and staleness experiments). 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.
10
10
 
11
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.
12
12
 
@@ -14,9 +14,10 @@ v0.4.6 adds a supported `my-dev-kit-lab` installed CLI router (`--help`, `--vers
14
14
 
15
15
  - **Run context-strategy experiments:** compare `raw-full-file` with `my-dev-kit-guided` using deterministic fixtures or locally configured Codex and Claude CLIs.
16
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).
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, and a three-item gallery (report, plots, bounded execution evidence). Its report screenshot is best-effort presentation evidence: unavailable browser runtime or failed capture is reported explicitly, no PNG is fabricated, and neither outcome fails an otherwise successful campaign. 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
18
  - **Record index freshness evidence (v0.6.0):** 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 in v0.6.0. See [docs/METRICS.md](docs/METRICS.md#index-freshness-evidence-v060) and [docs/WORKFLOWS.md](docs/WORKFLOWS.md#warm-index-reuse-experiment).
19
- - **Assess affected-neighborhood evidence (v0.6.1, current):** using the baseline my-dev-kit graph of the same prepared warm index (loaded once per session), each task's confirmed modified or missing indexed files, and the baseline symbols those files contain, are mapped to graph nodes. The affected neighborhood is those seed nodes plus their direct one-hop neighbors (edges of any kind, either direction). The task's `expectedFiles` and file-scoped `expectedSymbols` are mapped to graph nodes and compared with that neighborhood, which yields six numeric warm-side metrics (`changedFileCount`, `changedSymbolCount`, `affectedNodeCount`, `affectedEdgeCount`, `taskOverlapCount`, `taskOverlapPercent`) plus categorical evidence: a `related`, `unrelated`, or `unknown` relationship and a `recommended`, `not-indicated`, or `unknown` `reindexRecommendation`. Incomplete evidence is reported as `unknown`, never as unrelated. The assessment is recorded additively in `warm-index-execution.json` and shown in the existing `report.json`/`report.txt`/`report.html` warm-index reports. It is observational only: retrieval, execution status, and provider status are unchanged, nothing is reindexed automatically, and no CLI flag, experiment plugin, plot, or gallery item was added. See [docs/METRICS.md](docs/METRICS.md#affected-neighborhood-metrics-v061) and [docs/WORKFLOWS.md](docs/WORKFLOWS.md#warm-index-reuse-experiment).
19
+ - **Compare no-refresh, partial-refresh, and full-refresh retrieval after a controlled change (v0.6.3, current release):** the `incremental-change-staleness` experiment plugin runs six frozen controlled-change scenario families (unrelated-file, local-implementation, exported-symbol, public-API, import-graph, and test-only changes) against four disposable target copies — `stale-index` (no refresh), `changed-files-refresh` and `affected-neighborhood-refresh` (real partial refreshes performed by my-dev-kit 1.12.5 through `index --incremental --refresh-scope`, selected internally with no Lab-level flag), and `full-refresh` (a complete new post-change index, used as a comparison reference rather than a preferred treatment). Each partial treatment truthfully records whether upstream applied the requested partial scope or fell back to a full rebuild (`APPLIED_PARTIAL` or `FALLBACK_FULL`); a fallback is not counted as partial-refresh evidence. Three comparisons against `full-refresh` are persisted with retrieval, deterministic fake-agent correctness, and required-file evidence in `incremental-change-staleness-execution.json` (schema `my-dev-kit-lab-incremental-change-staleness-execution-v2`) and the existing `report.json`/`report.txt`/`report.html`; historical v0.6.2 (V1) artifacts stay readable. There is no winner, ranking, or safety claim, and the plugin is self-only (`--target` is not accepted). `--kit-command` also accepts `incremental-change-staleness` and now defaults to my-dev-kit 1.12.5. See [docs/WORKFLOWS.md](docs/WORKFLOWS.md) and [docs/METRICS.md](docs/METRICS.md).
20
+ - **Assess affected-neighborhood evidence (v0.6.1, released; retained in the current package):** using the baseline my-dev-kit graph of the same prepared warm index (loaded once per session), each task's confirmed modified or missing indexed files, and the baseline symbols those files contain, are mapped to graph nodes. The affected neighborhood is those seed nodes plus their direct one-hop neighbors (edges of any kind, either direction). The task's `expectedFiles` and file-scoped `expectedSymbols` are mapped to graph nodes and compared with that neighborhood, which yields six numeric warm-side metrics (`changedFileCount`, `changedSymbolCount`, `affectedNodeCount`, `affectedEdgeCount`, `taskOverlapCount`, `taskOverlapPercent`) plus categorical evidence: a `related`, `unrelated`, or `unknown` relationship and a `recommended`, `not-indicated`, or `unknown` `reindexRecommendation`. Incomplete evidence is reported as `unknown`, never as unrelated. The assessment is recorded additively in `warm-index-execution.json` and shown in the existing `report.json`/`report.txt`/`report.html` warm-index reports. It is observational only: retrieval, execution status, and provider status are unchanged, nothing is reindexed automatically, and no CLI flag, experiment plugin, plot, or gallery item was added. See [docs/METRICS.md](docs/METRICS.md#affected-neighborhood-metrics-v061) and [docs/WORKFLOWS.md](docs/WORKFLOWS.md#warm-index-reuse-experiment).
20
21
  - **Audit repository health:** run conservative code-rot detectors for TypeScript/JavaScript, Python, Java, and Kotlin, or adapt security findings into the common audit report.
21
22
  - **Validate CLI/package security:** inspect dependencies, package contents, path and subprocess boundaries, malformed inputs, optional static scanners, and bounded fuzz targets.
22
23
  - **Validate Android projects:** run nineteen static checks by default, with Gradle operations, external tools, and network access available only through explicit opt-in flags.
@@ -181,9 +182,9 @@ npm run experiment:run -- \
181
182
  --out lab-output/warm-index-campaign
182
183
  ```
183
184
 
184
- 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.
185
+ This automatically writes `report.json`/`.txt`/`.html`, the four warm-index charts under `plots/`, an optional best-effort `report.png`, and a three-item `gallery/` (report, plots, bounded execution evidence) beneath `--out`. Screenshot capture failure remains `failed`, is reported in the command output and gallery warning, and does not fail an otherwise successful campaign or fabricate a PNG. Required experiment, report, plot, and gallery failures remain fatal. `--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.
185
186
 
186
- 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.
187
+ For experiment plugins that support explicit targets — `context-strategy-comparison` and ordinary (non-campaign) `warm-index-reuse` runs — omitting `--target` uses self mode against my-dev-kit-lab. When those plugins receive `--target <path>`, the lab remains the tool root and the target project is inspected separately. `incremental-change-staleness` does not accept `--target`; it always runs against the lab's bundled benchmark projects. Generated experiment outputs stay under lab-controlled output directories by default, not inside an inspected target project.
187
188
 
188
189
  ---
189
190
 
@@ -235,10 +236,11 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
235
236
  - 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)
236
237
  - 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
237
238
  - 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
238
- - 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
239
+ - The generic experiment-plugin framework has three registered plugins: `context-strategy-comparison`, the v0.5.0-introduced `warm-index-reuse`, and the `incremental-change-staleness` plugin (introduced in v0.6.2; extended to four treatments in current release v0.6.3). Later plugins such as context-window scaling remain future roadmap work
239
240
  - `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
240
241
  - The current release does not guarantee token savings; it produces auditable evidence for specific cases, targets, agents, and strategies
241
242
  - Released v0.6.1 affected-neighborhood evidence is bounded to the baseline graph of the prepared index and to the snapshot-represented files: a changed symbol means the symbol was present in a confirmed changed indexed file, not that its own text was proven to change; `not-indicated` means only that this bounded analysis found no supporting evidence for reindexing, not that skipping a reindex is safe; unresolved or ambiguous expected symbols keep a task at `unknown` rather than unrelated
243
+ - v0.6.3 (current release) partial-refresh evidence is scoped to the executed deterministic scenarios and the configured my-dev-kit 1.12.5-or-later tool. A partial treatment can fall back to a full rebuild and is then reported as not comparable as partial refresh, so an applied partial refresh in one run does not guarantee the next; `no-observed-regression-relative-to-full` is not a safety claim, and full refresh is a reference rather than a winner. Correctness remains deterministic fake-agent evidence, and `incremental-change-staleness` accepts no external `--target`.
242
244
  - Provider telemetry dashboards, semantic LLM judging, and cloud API billing integration are not yet implemented
243
245
  - The six new stage-context strategies have no CLI flags yet, are configured programmatically, and do not yet include plots, screenshots, or gallery integration
244
246
  - The published upstream artifacts the stage-context strategies read do not expose considered-but-unselected reads or unnecessary-read evidence; those metrics report `unavailable` rather than zero
@@ -257,7 +259,7 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
257
259
 
258
260
  Android validation uses `--profile android`. Its default path is static and non-destructive: it starts zero Gradle processes, zero external tools, and zero network operations. Only confirmed `SecurityFinding` records can become audit issues; Android `CandidateEvidence` remains review-only evidence.
259
261
 
260
- Optional scanners are reported as `skipped` when unavailable, never as passed. The framework does not provide runtime isolation proof, device or APK/AAB analysis, signing verification, Play Console validation, automatic fixes, or manual pentesting. Manual pentest remains deferred until post-v1/version TBD.
262
+ Optional local scanners are reported as `skipped` when unavailable, never as passed. This local availability rule does not make CodeQL coverage optional for release readiness: the exact candidate must pass the repository-controlled GitHub CodeQL workflow and have its Code Scanning alerts reviewed. The framework does not provide runtime isolation proof, device or APK/AAB analysis, signing verification, Play Console validation, automatic fixes, or manual pentesting. Manual pentest remains deferred until post-v1/version TBD.
261
263
 
262
264
  See [COMMANDS.md](docs/COMMANDS.md) for exact syntax and [Security Validation Framework](docs/security-validation-framework.md) for checks, evidence semantics, verdicts, and limitations.
263
265
 
@@ -0,0 +1,360 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "scenarios": [
4
+ {
5
+ "id": "U1",
6
+ "category": "unrelated-file-change",
7
+ "benchmarkProjectId": "task-analytics-large-mixed",
8
+ "baseCaseId": "warm-large-ts-leaderboard",
9
+ "answerPolicy": "inherit",
10
+ "expectedRelationship": "unrelated",
11
+ "mutation": {
12
+ "files": [
13
+ {
14
+ "path": "py/task_analytics/quality.py",
15
+ "expectedPreSha256": "5faab1bf018de6e647a11a2edffd641a51c0c05b399cb6f58a2c22b0ff035554",
16
+ "operations": [
17
+ {
18
+ "expectedPreimage": "metric.completion_rate >= 80 and metric.stale_tasks == 0",
19
+ "replacement": "metric.completion_rate >= 81 and metric.stale_tasks == 0"
20
+ }
21
+ ],
22
+ "expectedPostSha256": "9f59d256710a9345eb6e0f768e45c4676041b4cac215cd84cd1075cfd730ab18"
23
+ }
24
+ ]
25
+ },
26
+ "notes": "Negative control across the intentionally separate Python and TypeScript implementations. The follow-up TypeScript leaderboard query/answer is unaffected by this Python-only threshold change, so this record states the scenario planning expectation as unrelated. Runtime classification in later batches must still produce unknown instead of unrelated when evidence is incomplete, per v0.6.1 semantics; Batch 1 performs no runtime classification."
27
+ },
28
+ {
29
+ "id": "L2",
30
+ "category": "local-implementation-change",
31
+ "benchmarkProjectId": "task-workflow-medium-ts",
32
+ "baseCaseId": "warm-medium-complete-idempotent",
33
+ "answerPolicy": "scenario",
34
+ "scenarioQuery": "complete task by id after the completedAt timestamp change idempotent unknown task",
35
+ "scenarioAnswerKey": {
36
+ "expectedFiles": [
37
+ "src/services/completeTask.ts",
38
+ "src/store/taskStore.ts"
39
+ ],
40
+ "expectedSymbols": [
41
+ "completeTask",
42
+ "TaskWorkflowStore"
43
+ ],
44
+ "expectedFacts": [
45
+ {
46
+ "id": "warm062-l2-sets-new-completed-at",
47
+ "text": "The first completion sets completed to true and completedAt to \"2026-03-01T00:00:00.000Z\".",
48
+ "weight": 1,
49
+ "required": true
50
+ },
51
+ {
52
+ "id": "warm062-l2-still-idempotent",
53
+ "text": "When the task is already completed the updater returns it unchanged, so completing it again does not rewrite completedAt.",
54
+ "weight": 1,
55
+ "required": true
56
+ },
57
+ {
58
+ "id": "warm062-l2-unknown-task-throws",
59
+ "text": "An unknown task id still throws \"Unknown task id: <id>\" from TaskWorkflowStore.updateTask.",
60
+ "weight": 1,
61
+ "required": true
62
+ },
63
+ {
64
+ "id": "warm062-l2-targets-by-id",
65
+ "text": "completeTask(store, taskId) still changes exactly one task by id through TaskWorkflowStore.updateTask.",
66
+ "weight": 1,
67
+ "required": true
68
+ }
69
+ ],
70
+ "minimumCorrectFacts": 4,
71
+ "notes": "v0.6.2 L2 scenario: local implementation change to the completedAt literal. Exported name, callable signature, idempotence, and unknown-task behavior are preserved."
72
+ },
73
+ "mutation": {
74
+ "files": [
75
+ {
76
+ "path": "src/services/completeTask.ts",
77
+ "expectedPreSha256": "27f6833abb1c5f8fa7226ec8a4857ba267dd48272d7cf094c7a3462e425ca64c",
78
+ "operations": [
79
+ {
80
+ "expectedPreimage": "\"2026-02-01T00:00:00.000Z\"",
81
+ "replacement": "\"2026-03-01T00:00:00.000Z\""
82
+ }
83
+ ],
84
+ "expectedPostSha256": "0d148ddab0dbd43b9bc8eb40a09de9dfd4567b9097fb0f028b74ebf921798cc8"
85
+ }
86
+ ]
87
+ },
88
+ "notes": "Local implementation change: only the fixed completedAt literal changes."
89
+ },
90
+ {
91
+ "id": "E1",
92
+ "category": "exported-symbol-change",
93
+ "benchmarkProjectId": "task-analytics-large-mixed",
94
+ "baseCaseId": "warm-large-python-pipeline",
95
+ "answerPolicy": "scenario",
96
+ "scenarioQuery": "pipeline parse rows project metrics quality label health report after the healthy completion-rate boundary changed",
97
+ "scenarioAnswerKey": {
98
+ "expectedFiles": [
99
+ "py/task_analytics/pipeline.py",
100
+ "py/task_analytics/parser.py",
101
+ "py/task_analytics/metrics.py",
102
+ "py/task_analytics/quality.py",
103
+ "py/task_analytics/reporting.py"
104
+ ],
105
+ "expectedSymbols": [
106
+ "build_report_from_rows",
107
+ "parse_task_rows",
108
+ "calculate_project_metrics",
109
+ "determine_quality_label",
110
+ "build_health_report"
111
+ ],
112
+ "expectedFacts": [
113
+ {
114
+ "id": "warm062-e1-new-healthy-boundary",
115
+ "text": "determine_quality_label now returns \"healthy\" only when completion_rate is at least 85 (previously 80) and stale_tasks is 0.",
116
+ "weight": 1,
117
+ "required": true
118
+ },
119
+ {
120
+ "id": "warm062-e1-signature-unchanged",
121
+ "text": "determine_quality_label keeps its exported name and single-argument signature; only its healthy-boundary behavior changed.",
122
+ "weight": 1,
123
+ "required": true
124
+ },
125
+ {
126
+ "id": "warm062-e1-role-in-pipeline",
127
+ "text": "build_health_report still calls determine_quality_label for each metric produced by calculate_project_metrics inside build_report_from_rows.",
128
+ "weight": 1,
129
+ "required": true
130
+ },
131
+ {
132
+ "id": "warm062-e1-deterministic-output",
133
+ "text": "The report remains one line per project in sorted project_id order, so fixed rows and current_day still produce the same output under the new boundary.",
134
+ "weight": 1,
135
+ "required": true
136
+ }
137
+ ],
138
+ "minimumCorrectFacts": 3,
139
+ "notes": "v0.6.2 E1 scenario: exported-symbol change. Export presence, symbol name, and callable signature remain stable; externally observable behavior changes."
140
+ },
141
+ "mutation": {
142
+ "files": [
143
+ {
144
+ "path": "py/task_analytics/quality.py",
145
+ "expectedPreSha256": "5faab1bf018de6e647a11a2edffd641a51c0c05b399cb6f58a2c22b0ff035554",
146
+ "operations": [
147
+ {
148
+ "expectedPreimage": "metric.completion_rate >= 80 and metric.stale_tasks == 0",
149
+ "replacement": "metric.completion_rate >= 85 and metric.stale_tasks == 0"
150
+ }
151
+ ],
152
+ "expectedPostSha256": "9cd4df5fd2fd29b99a1e82b744a7bd7afddb123a2fe1d861c1edcce21a32998c"
153
+ }
154
+ ]
155
+ },
156
+ "notes": "Same canonical quality.py file as U1, but with an independent scenario-specific mutation to threshold 85 instead of 81."
157
+ },
158
+ {
159
+ "id": "P1",
160
+ "category": "public-api-change",
161
+ "benchmarkProjectId": "task-analytics-large-mixed",
162
+ "baseCaseId": "warm-large-python-pipeline",
163
+ "answerPolicy": "scenario",
164
+ "scenarioQuery": "calculate_project_metrics signature stale_day_threshold parameter default behavior for callers that omit it",
165
+ "scenarioAnswerKey": {
166
+ "expectedFiles": [
167
+ "py/task_analytics/metrics.py",
168
+ "py/task_analytics/pipeline.py"
169
+ ],
170
+ "expectedSymbols": [
171
+ "calculate_project_metrics",
172
+ "STALE_DAY_THRESHOLD"
173
+ ],
174
+ "expectedFacts": [
175
+ {
176
+ "id": "warm062-p1-new-optional-param",
177
+ "text": "calculate_project_metrics(tasks, current_day, stale_day_threshold=STALE_DAY_THRESHOLD) now accepts an optional stale_day_threshold parameter.",
178
+ "weight": 1,
179
+ "required": true
180
+ },
181
+ {
182
+ "id": "warm062-p1-default-preserved",
183
+ "text": "The parameter defaults to the existing STALE_DAY_THRESHOLD constant (10), so callers that omit it keep identical prior behavior.",
184
+ "weight": 1,
185
+ "required": true
186
+ },
187
+ {
188
+ "id": "warm062-p1-predicate-uses-param",
189
+ "text": "The stale-task predicate now compares current_day minus task.updated_day against stale_day_threshold instead of the module constant directly.",
190
+ "weight": 1,
191
+ "required": true
192
+ },
193
+ {
194
+ "id": "warm062-p1-pipeline-caller-unaffected",
195
+ "text": "pipeline.py build_report_from_rows calls calculate_project_metrics(tasks, current_day) without the new argument, so its behavior is unchanged.",
196
+ "weight": 1,
197
+ "required": true
198
+ }
199
+ ],
200
+ "minimumCorrectFacts": 3,
201
+ "notes": "v0.6.2 P1 scenario: public API change. Existing callers that omit the new argument preserve prior default behavior; the callable contract itself changes."
202
+ },
203
+ "mutation": {
204
+ "files": [
205
+ {
206
+ "path": "py/task_analytics/metrics.py",
207
+ "expectedPreSha256": "da2bfce46ec51016f87b3456a201e69a9d71721002e1781b44fdc59a96650e91",
208
+ "operations": [
209
+ {
210
+ "expectedPreimage": "def calculate_project_metrics(tasks, current_day):",
211
+ "replacement": "def calculate_project_metrics(tasks, current_day, stale_day_threshold=STALE_DAY_THRESHOLD):"
212
+ },
213
+ {
214
+ "expectedPreimage": "current_day - task.updated_day >= STALE_DAY_THRESHOLD",
215
+ "replacement": "current_day - task.updated_day >= stale_day_threshold"
216
+ }
217
+ ],
218
+ "expectedPostSha256": "c91af3c5549606c522a6cef8264d4da1a6febed0a58757ad5a96cafcb3ed58c9"
219
+ }
220
+ ]
221
+ },
222
+ "notes": "Two ordered exact replacements: the signature gains the optional parameter first, then the stale predicate is switched to use it."
223
+ },
224
+ {
225
+ "id": "I1",
226
+ "category": "import-graph-change",
227
+ "benchmarkProjectId": "task-analytics-large-mixed",
228
+ "baseCaseId": "warm-large-ts-analytics-snapshot",
229
+ "answerPolicy": "scenario",
230
+ "scenarioQuery": "analytics snapshot project completion rate stale tasks average story points task store after buildAnalyticsSnapshot task-listing dependency changed",
231
+ "scenarioAnswerKey": {
232
+ "expectedFiles": [
233
+ "ts/src/services/buildAnalyticsSnapshot.ts",
234
+ "ts/src/models/analyticsSnapshot.ts",
235
+ "ts/src/store/taskStore.ts"
236
+ ],
237
+ "expectedSymbols": [
238
+ "buildAnalyticsSnapshot",
239
+ "ProjectSnapshot",
240
+ "AnalyticsTaskStore"
241
+ ],
242
+ "expectedFacts": [
243
+ {
244
+ "id": "warm062-i1-no-longer-imports-helper",
245
+ "text": "buildAnalyticsSnapshot no longer imports or calls listTasksByProject.",
246
+ "weight": 1,
247
+ "required": true
248
+ },
249
+ {
250
+ "id": "warm062-i1-inline-filtering",
251
+ "text": "buildAnalyticsSnapshot now obtains a target projects tasks directly with taskStore.list().filter((task) => task.projectId === project.id).",
252
+ "weight": 1,
253
+ "required": true
254
+ },
255
+ {
256
+ "id": "warm062-i1-output-unchanged",
257
+ "text": "The inline filtering is behaviorally identical to the removed helper, so totalTasks, completedTasks, openTasks, staleTasks, and averageStoryPoints per project are unchanged.",
258
+ "weight": 1,
259
+ "required": true
260
+ },
261
+ {
262
+ "id": "warm062-i1-totals-aggregate-unchanged",
263
+ "text": "The snapshot totals still sum totalTasks, completedTasks, openTasks, and staleTasks across the per-project snapshots.",
264
+ "weight": 1,
265
+ "required": true
266
+ }
267
+ ],
268
+ "minimumCorrectFacts": 3,
269
+ "notes": "v0.6.2 I1 scenario: import-graph change. The intended snapshot output remains equivalent while the dependency edge on listTasksByProject disappears."
270
+ },
271
+ "mutation": {
272
+ "files": [
273
+ {
274
+ "path": "ts/src/services/buildAnalyticsSnapshot.ts",
275
+ "expectedPreSha256": "928af15041715ffa19ae3674e8d4f82e1c5f694cfa2bad1fea03db31bce6cedd",
276
+ "operations": [
277
+ {
278
+ "expectedPreimage": "import { listTasksByProject } from \"./listTasksByProject.js\";\r\n",
279
+ "replacement": ""
280
+ },
281
+ {
282
+ "expectedPreimage": "const tasks = listTasksByProject(taskStore, project.id);",
283
+ "replacement": "const tasks = taskStore.list().filter((task) => task.projectId === project.id);"
284
+ }
285
+ ],
286
+ "expectedPostSha256": "1ab0616f11107a2ffabfc1d7ee2b0614d9c9c58cd62fb461313a87c9754570b1"
287
+ }
288
+ ]
289
+ },
290
+ "notes": "Two ordered exact replacements: remove the now-unused import, then inline the equivalent filtering that listTasksByProject itself performed."
291
+ },
292
+ {
293
+ "id": "T1",
294
+ "category": "test-only-change",
295
+ "benchmarkProjectId": "task-analytics-large-mixed",
296
+ "baseCaseId": "warm-large-python-pipeline",
297
+ "answerPolicy": "scenario",
298
+ "scenarioQuery": "In py/tests/test_quality.py, what updated_day value does task-1 use in test_determine_quality_label, and what quality label does the test assert after calculating project metrics?",
299
+ "scenarioAnswerKey": {
300
+ "expectedFiles": [
301
+ "py/tests/test_quality.py"
302
+ ],
303
+ "expectedSymbols": [
304
+ "determine_quality_label",
305
+ "test_determine_quality_label"
306
+ ],
307
+ "expectedFacts": [
308
+ {
309
+ "id": "warm062-t1-task1-updated-day-nine",
310
+ "text": "task-1 row in test_determine_quality_label now uses updated_day 9 (previously 8).",
311
+ "weight": 1,
312
+ "required": true
313
+ },
314
+ {
315
+ "id": "warm062-t1-both-rows-completed",
316
+ "text": "Both fixture rows remain completed, so total_tasks stays 2 and completed_tasks stays 2.",
317
+ "weight": 1,
318
+ "required": true
319
+ },
320
+ {
321
+ "id": "warm062-t1-completion-rate-unchanged",
322
+ "text": "The computed project completion_rate therefore remains 100.0.",
323
+ "weight": 1,
324
+ "required": true
325
+ },
326
+ {
327
+ "id": "warm062-t1-no-stale-task-introduced",
328
+ "text": "current_day (15) minus the new updated_day (9) is 6, still below STALE_DAY_THRESHOLD (10), so stale_tasks remains 0.",
329
+ "weight": 1,
330
+ "required": true
331
+ },
332
+ {
333
+ "id": "warm062-t1-still-healthy",
334
+ "text": "The test still asserts determine_quality_label(metrics[0]) == \"healthy\"; production source and behavior are unchanged.",
335
+ "weight": 1,
336
+ "required": true
337
+ }
338
+ ],
339
+ "minimumCorrectFacts": 4,
340
+ "notes": "v0.6.2 T1 scenario: test-only change. Only the task-1 updated_day test input changes; production source in quality.py, metrics.py, and parser.py is unchanged, and the healthy-label assertion still passes."
341
+ },
342
+ "mutation": {
343
+ "files": [
344
+ {
345
+ "path": "py/tests/test_quality.py",
346
+ "expectedPreSha256": "b5610a2720012532baf6d18e114f375424008e0daadbc27291bfca2ce6316875",
347
+ "operations": [
348
+ {
349
+ "expectedPreimage": "{\"task_id\": \"task-1\", \"project_id\": \"alpha\", \"completed\": True, \"story_points\": 5, \"updated_day\": 8}",
350
+ "replacement": "{\"task_id\": \"task-1\", \"project_id\": \"alpha\", \"completed\": True, \"story_points\": 5, \"updated_day\": 9}"
351
+ }
352
+ ],
353
+ "expectedPostSha256": "eda7f39eed5b08d615b02a01b3b05655ed2aad06b65c39ad0ea09e9b93992ea5"
354
+ }
355
+ ]
356
+ },
357
+ "notes": "Test-only change: exactly one bounded test input literal changes. Production source and production behavior are unchanged; the query/answer targets the changed test evidence itself."
358
+ }
359
+ ]
360
+ }
@@ -143,7 +143,10 @@ export function renderExperimentRunHelp() {
143
143
  " --experiment <id> Registered experiment plugin id (see \"my-dev-kit-lab experiment list\")",
144
144
  "",
145
145
  "Common options (all plugins):",
146
- " --target <path> Local target project path (default: self)",
146
+ " --target <path> Local target project path (default: self); only for",
147
+ " plugins that support external targets. Self-only",
148
+ " plugins (for example incremental-change-staleness)",
149
+ " reject an explicit external --target.",
147
150
  " --out <dir> Output directory for experiment artifacts",
148
151
  " (default: beneath the workspace when installed)",
149
152
  " --cases <path> Path to the evaluation cases file",
@@ -153,10 +156,12 @@ export function renderExperimentRunHelp() {
153
156
  " --case <ids> Comma-separated case ids to run",
154
157
  " --benchmark-project <ids> Comma-separated benchmark project ids to run",
155
158
  "",
159
+ "my-dev-kit command override (warm-index-reuse and incremental-change-staleness):",
160
+ " --kit-command <command> my-dev-kit command used by the selected experiment plugin",
161
+ " (defaults: warm-index-reuse uses npx @dailephd/my-dev-kit@latest;",
162
+ " incremental-change-staleness uses npx @dailephd/my-dev-kit@1.12.5)",
163
+ "",
156
164
  "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
165
  " Legacy/default mode uses the deterministic fake agent only;",
161
166
  " agent options below are not accepted by this plugin.",
162
167
  " --campaign <preset> Runs the selected real-agent campaign preset: codex-full,",
@@ -173,11 +178,13 @@ export function renderExperimentRunHelp() {
173
178
  " replaced by a context-size estimate. A campaign run",
174
179
  " automatically produces the report JSON/text/HTML, the",
175
180
  " 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
+ " screenshot, and a gallery manifest/index. The automatic",
182
+ " campaign report screenshot is best-effort presentation",
183
+ " evidence: skipped or failed capture is reported explicitly",
184
+ " (failed remains failed), no PNG is fabricated, and",
185
+ " screenshot status alone does not fail an otherwise",
186
+ " successful campaign command. Core experiment, report,",
187
+ " plot, and gallery failures remain fatal. Legacy",
181
188
  " non-campaign warm-index runs retain their existing",
182
189
  " report-only behavior.",
183
190
  "",
@@ -147,9 +147,12 @@ function describeExamples(plugin) {
147
147
  const screenshotOption = hasConfigField(plugin, "agents") ? " --no-screenshot" : "";
148
148
  const examples = [
149
149
  `my-dev-kit-lab experiment describe --experiment ${plugin.metadata.id}`,
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}`
150
+ `my-dev-kit-lab experiment run --experiment ${plugin.metadata.id}${agentOptions}`
152
151
  ];
152
+ // Only advertise an explicit --target example for plugins that support external targets.
153
+ if (plugin.metadata.supportedTargets.includes("external-local")) {
154
+ examples.push(`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id} --target "Z:\\Users\\newuser\\Projects\\my-dev-kit-v1"${agentOptions}${screenshotOption}`);
155
+ }
153
156
  // Only advertise campaign examples for plugins that declare the campaignPreset config field.
154
157
  if (hasConfigField(plugin, "campaignPreset")) {
155
158
  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>`);
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import { parseAgentCommandTemplate } from "../agents/index.js";
3
3
  import { parseAgentId } from "../agents/agentRegistry.js";
4
4
  import { readBenchmarkProjectProfiles, readEvaluationCases } from "../evaluation/index.js";
5
- import { contextStrategyComparisonPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, parseWarmIndexCampaignPresetId, resolveExperimentTarget, runExperiment, warmIndexReusePlugin } from "../experiments/index.js";
5
+ import { contextStrategyComparisonPlugin, createDefaultExperimentPluginRegistry, getWarmIndexCampaignPreset, incrementalChangeStalenessPlugin, 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";
@@ -76,7 +76,6 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
76
76
  // Automatic presentation (report already produced above) applies only to warm-index-reuse
77
77
  // campaign runs; legacy fake-agent warm-index runs and context-strategy-comparison stay
78
78
  // report-only, unchanged.
79
- let screenshotFailed = false;
80
79
  if (args.experimentId === warmIndexReusePlugin.metadata.id && args.config.campaignPreset) {
81
80
  const executionArtifactPath = readMetadataString(result.metadata?.executionArtifactPath);
82
81
  const campaignAgentId = readMetadataString(result.metadata?.campaignAgentId);
@@ -106,7 +105,6 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
106
105
  else {
107
106
  outputLines.push("Screenshot: failed");
108
107
  outputLines.push(presentation.screenshot.error ?? "Screenshot capture failed.");
109
- screenshotFailed = true;
110
108
  }
111
109
  outputLines.push(`Gallery manifest: ${presentation.gallery.manifestPath}`);
112
110
  outputLines.push(`Gallery index: ${presentation.gallery.indexPath}`);
@@ -114,8 +112,6 @@ export async function runExperimentRunCommandFromArgs(argv, options = {}) {
114
112
  console.log(outputLines.join("\n"));
115
113
  if (result.status === "failed")
116
114
  return 1;
117
- if (screenshotFailed)
118
- return 1;
119
115
  return 0;
120
116
  }
121
117
  catch (error) {
@@ -220,8 +216,9 @@ export function parseRunExperimentArgs(argv) {
220
216
  if (!experimentId) {
221
217
  throw new Error("Usage: --experiment <id> [--target <path>] [--out <directory>]");
222
218
  }
223
- if (kitCommand !== undefined && experimentId !== warmIndexReusePlugin.metadata.id) {
224
- throw new Error(`--kit-command is only supported for --experiment ${warmIndexReusePlugin.metadata.id}.`);
219
+ const KIT_COMMAND_PLUGIN_IDS = [warmIndexReusePlugin.metadata.id, incrementalChangeStalenessPlugin.metadata.id];
220
+ if (kitCommand !== undefined && !KIT_COMMAND_PLUGIN_IDS.includes(experimentId)) {
221
+ throw new Error(`--kit-command is only supported for --experiment ${KIT_COMMAND_PLUGIN_IDS.join(" or ")}.`);
225
222
  }
226
223
  if (campaignPreset !== undefined) {
227
224
  if (experimentId !== warmIndexReusePlugin.metadata.id) {