@dailephd/my-dev-kit-lab 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +11 -8
  3. package/benchmarks/contracts/context-window-scaling-cases.json +220 -0
  4. package/benchmarks/projects/context-window-scaling-fixed-ts/README.md +14 -0
  5. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/carrier-depot-catalog-01.ts +302 -0
  6. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/course-module-catalog-06.ts +302 -0
  7. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/library-shelf-catalog-07.ts +302 -0
  8. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/sensor-station-catalog-05.ts +302 -0
  9. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/staff-rota-catalog-03.ts +302 -0
  10. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/supplier-contract-catalog-02.ts +302 -0
  11. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-1/vehicle-route-catalog-04.ts +302 -0
  12. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/archive-box-catalog-12.ts +302 -0
  13. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/clinic-ward-catalog-08.ts +302 -0
  14. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/greenhouse-bay-catalog-13.ts +302 -0
  15. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/harbor-berth-catalog-10.ts +302 -0
  16. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/kitchen-section-catalog-15.ts +302 -0
  17. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/market-stall-catalog-09.ts +302 -0
  18. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/station-platform-catalog-14.ts +302 -0
  19. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-2/workshop-bench-catalog-11.ts +302 -0
  20. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/archive-box-catalog-28.ts +302 -0
  21. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/carrier-depot-catalog-17.ts +302 -0
  22. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/clinic-ward-catalog-24.ts +302 -0
  23. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/course-module-catalog-22.ts +302 -0
  24. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/greenhouse-bay-catalog-29.ts +302 -0
  25. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/harbor-berth-catalog-26.ts +302 -0
  26. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/kitchen-section-catalog-31.ts +302 -0
  27. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/library-shelf-catalog-23.ts +302 -0
  28. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/market-stall-catalog-25.ts +302 -0
  29. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/sensor-station-catalog-21.ts +302 -0
  30. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/staff-rota-catalog-19.ts +302 -0
  31. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/station-platform-catalog-30.ts +302 -0
  32. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/supplier-contract-catalog-18.ts +302 -0
  33. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/vehicle-route-catalog-20.ts +302 -0
  34. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/warehouse-zone-catalog-16.ts +302 -0
  35. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-3/workshop-bench-catalog-27.ts +302 -0
  36. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/archive-box-catalog-44.ts +302 -0
  37. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-33.ts +302 -0
  38. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/carrier-depot-catalog-49.ts +302 -0
  39. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-40.ts +302 -0
  40. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/clinic-ward-catalog-56.ts +302 -0
  41. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-38.ts +302 -0
  42. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/course-module-catalog-54.ts +302 -0
  43. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/greenhouse-bay-catalog-45.ts +302 -0
  44. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-42.ts +302 -0
  45. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/harbor-berth-catalog-58.ts +302 -0
  46. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/kitchen-section-catalog-47.ts +302 -0
  47. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-39.ts +302 -0
  48. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/library-shelf-catalog-55.ts +302 -0
  49. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-41.ts +302 -0
  50. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/market-stall-catalog-57.ts +302 -0
  51. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-37.ts +302 -0
  52. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/sensor-station-catalog-53.ts +302 -0
  53. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-35.ts +302 -0
  54. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/staff-rota-catalog-51.ts +302 -0
  55. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/station-platform-catalog-46.ts +302 -0
  56. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-34.ts +302 -0
  57. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/supplier-contract-catalog-50.ts +302 -0
  58. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-36.ts +302 -0
  59. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/vehicle-route-catalog-52.ts +302 -0
  60. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-32.ts +302 -0
  61. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/warehouse-zone-catalog-48.ts +302 -0
  62. package/benchmarks/projects/context-window-scaling-fixed-ts/src/filler/tier-4/workshop-bench-catalog-43.ts +302 -0
  63. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/auditRedaction.ts +25 -0
  64. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/inventoryReservation.ts +26 -0
  65. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/invoiceTotals.ts +25 -0
  66. package/benchmarks/projects/context-window-scaling-fixed-ts/src/tasks/shippingQuote.ts +32 -0
  67. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/auditRedaction.test.ts +17 -0
  68. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/inventoryReservation.test.ts +19 -0
  69. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/invoiceTotals.test.ts +14 -0
  70. package/benchmarks/projects/context-window-scaling-fixed-ts/tests/tasks/shippingQuote.test.ts +19 -0
  71. package/dist/src/cli/help.js +27 -10
  72. package/dist/src/commands/runExperimentDescribeCommand.js +5 -2
  73. package/dist/src/commands/runExperimentRunCommand.js +64 -8
  74. package/dist/src/evaluation/benchmarkMetadata.js +5 -1
  75. package/dist/src/evaluation/parseAgentAnswer.js +172 -24
  76. package/dist/src/evaluation/runMyDevKitRetrieval.js +120 -7
  77. package/dist/src/experiments/defaultRegistry.js +2 -0
  78. package/dist/src/experiments/plugins/contextWindowScaling/cliBudgets.js +31 -0
  79. package/dist/src/experiments/plugins/contextWindowScaling/config.js +84 -0
  80. package/dist/src/experiments/plugins/contextWindowScaling/contextBudget.js +43 -0
  81. package/dist/src/experiments/plugins/contextWindowScaling/evaluation.js +36 -0
  82. package/dist/src/experiments/plugins/contextWindowScaling/execution.js +187 -0
  83. package/dist/src/experiments/plugins/contextWindowScaling/executionArtifact.js +15 -0
  84. package/dist/src/experiments/plugins/contextWindowScaling/executionArtifactReader.js +186 -0
  85. package/dist/src/experiments/plugins/contextWindowScaling/index.js +14 -0
  86. package/dist/src/experiments/plugins/contextWindowScaling/metadata.js +12 -0
  87. package/dist/src/experiments/plugins/contextWindowScaling/metrics.js +149 -0
  88. package/dist/src/experiments/plugins/contextWindowScaling/plugin.js +185 -0
  89. package/dist/src/experiments/plugins/contextWindowScaling/projectProfile.js +51 -0
  90. package/dist/src/experiments/plugins/contextWindowScaling/relevantFiles.js +76 -0
  91. package/dist/src/experiments/plugins/contextWindowScaling/successEvidence.js +60 -0
  92. package/dist/src/experiments/plugins/contextWindowScaling/types.js +1 -0
  93. package/dist/src/experiments/plugins/incrementalChangeStaleness/comparisonV2.js +49 -0
  94. package/dist/src/experiments/plugins/incrementalChangeStaleness/config.js +5 -4
  95. package/dist/src/experiments/plugins/incrementalChangeStaleness/disposableTarget.js +20 -2
  96. package/dist/src/experiments/plugins/incrementalChangeStaleness/execution.js +42 -16
  97. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifact.js +4 -4
  98. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifactV2.js +289 -0
  99. package/dist/src/experiments/plugins/incrementalChangeStaleness/executionV2.js +195 -0
  100. package/dist/src/experiments/plugins/incrementalChangeStaleness/indexClone.js +76 -0
  101. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycle.js +64 -32
  102. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicy.js +6 -4
  103. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicyV2.js +78 -0
  104. package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycleV2.js +298 -0
  105. package/dist/src/experiments/plugins/incrementalChangeStaleness/plugin.js +213 -12
  106. package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSessionV2.js +5 -0
  107. package/dist/src/experiments/plugins/index.js +1 -0
  108. package/dist/src/experiments/plugins/warmIndexReuse/agentEvaluation.js +1 -1
  109. package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +3 -1
  110. package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +9 -2
  111. package/dist/src/mobile/android/advancedSecurity/redaction.js +19 -10
  112. package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +12 -2
  113. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +1 -1
  114. package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +7 -7
  115. package/dist/src/plots/buildContextWindowScalingPlotData.js +96 -0
  116. package/dist/src/plots/index.js +1 -0
  117. package/dist/src/plots/writePlotArtifacts.js +12 -3
  118. package/dist/src/report/experiments/buildContextWindowScalingReport.js +84 -0
  119. package/dist/src/report/experiments/buildIncrementalChangeStalenessPluginReport.js +33 -0
  120. package/dist/src/report/experiments/buildIncrementalChangeStalenessReport.js +1 -1
  121. package/dist/src/report/experiments/buildIncrementalChangeStalenessReportV2.js +80 -0
  122. package/dist/src/report/experiments/buildPluginExperimentReport.js +24 -4
  123. package/dist/src/report/experiments/contextWindowScalingReportModel.js +3 -0
  124. package/dist/src/report/experiments/incrementalChangeStalenessReportModelV2.js +3 -0
  125. package/dist/src/report/experiments/incrementalChangeStalenessReportRowsV2.js +146 -0
  126. package/dist/src/report/experiments/index.js +7 -0
  127. package/dist/src/report/experiments/renderContextWindowScalingHtml.js +99 -0
  128. package/dist/src/report/experiments/renderContextWindowScalingText.js +108 -0
  129. package/dist/src/report/experiments/renderIncrementalChangeStalenessHtml.js +4 -0
  130. package/dist/src/report/experiments/renderIncrementalChangeStalenessHtmlV2.js +111 -0
  131. package/dist/src/report/experiments/renderIncrementalChangeStalenessTextV2.js +96 -0
  132. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  133. package/dist/src/report/experiments/renderPluginExperimentReportText.js +14 -0
  134. package/dist/src/securityValidation/staticScans/codeql.js +8 -8
  135. package/docs/METRICS.md +58 -0
  136. package/examples/tutorial-browser/prepare.mjs +13 -13
  137. package/examples/tutorial-browser/server.mjs +13 -13
  138. package/package.json +1 -1
@@ -224,13 +224,13 @@ export async function runIncrementalChangeStalenessFakeAgent(args) {
224
224
  };
225
225
  }
226
226
  }
227
- function correctnessComparable(fakeAgent) {
227
+ export function correctnessComparable(fakeAgent) {
228
228
  if (!fakeAgent || !fakeAgent.correctness.available || fakeAgent.correctness.score === null) {
229
229
  return { available: false };
230
230
  }
231
231
  return { available: true, score: fakeAgent.correctness.score };
232
232
  }
233
- const INCONCLUSIVE_NO_EXECUTION = {
233
+ export const INCONCLUSIVE_NO_EXECUTION = {
234
234
  correctnessRelation: "unknown",
235
235
  requiredFileEvidenceRelation: "unknown",
236
236
  staleRiskClassification: "inconclusive",
@@ -240,10 +240,12 @@ export const defaultIncrementalChangeStalenessExecutionDeps = {
240
240
  runRetrieval: runIncrementalChangeStalenessTreatmentRetrieval,
241
241
  runFakeAgent: runIncrementalChangeStalenessFakeAgent
242
242
  };
243
- async function executeOneTreatment(args) {
244
- const treatment = args.session.treatments[args.treatmentId];
245
- const baselineFreshness = treatment.changeAuthority.postMutationBaselineFreshness;
246
- const activeIndexPhase = treatment.activeRetrieval.role;
243
+ /**
244
+ * Retrieval + deterministic fake-agent + required-file evidence for ONE treatment against its own active
245
+ * index. Shared by the v0.6.2 two-treatment path and the v0.6.3 four-treatment path; takes only the
246
+ * lifecycle-provided facts it needs so it never reaches into another treatment's state.
247
+ */
248
+ export async function executeTreatmentEvaluation(args) {
247
249
  let retrieval = null;
248
250
  let retrievalStatus = "not-run";
249
251
  let failureReason = null;
@@ -251,7 +253,7 @@ async function executeOneTreatment(args) {
251
253
  retrieval = await args.deps.runRetrieval({
252
254
  derivedTask: args.derivedTask,
253
255
  kitCommand: args.kitCommand,
254
- indexDir: treatment.activeRetrieval.index.indexDir,
256
+ indexDir: args.activeIndexDir,
255
257
  commandsDir: args.retrievalCommandsDir
256
258
  });
257
259
  retrievalStatus = classifyIncrementalChangeStalenessRetrieval(retrieval);
@@ -284,8 +286,8 @@ async function executeOneTreatment(args) {
284
286
  treatmentId: args.treatmentId,
285
287
  status,
286
288
  failureReason,
287
- activeIndexPhase,
288
- baselineFreshness,
289
+ activeIndexPhase: args.activeIndexPhase,
290
+ baselineFreshness: args.baselineFreshness,
289
291
  affectedNeighborhood: args.affectedNeighborhood,
290
292
  retrieval,
291
293
  retrievalStatus,
@@ -293,6 +295,36 @@ async function executeOneTreatment(args) {
293
295
  requiredFileEvidence
294
296
  };
295
297
  }
298
+ async function executeOneTreatment(args) {
299
+ const treatment = args.session.treatments[args.treatmentId];
300
+ return executeTreatmentEvaluation({
301
+ treatmentId: args.treatmentId,
302
+ baselineFreshness: treatment.changeAuthority.postMutationBaselineFreshness,
303
+ activeIndexPhase: treatment.activeRetrieval.role,
304
+ activeIndexDir: treatment.activeRetrieval.index.indexDir,
305
+ derivedTask: args.derivedTask,
306
+ affectedNeighborhood: args.affectedNeighborhood,
307
+ requiredFiles: args.requiredFiles,
308
+ kitCommand: args.kitCommand,
309
+ retrievalCommandsDir: args.retrievalCommandsDir,
310
+ agentOutDir: args.agentOutDir,
311
+ projectProfiles: args.projectProfiles,
312
+ cwd: args.cwd,
313
+ env: args.env,
314
+ deps: args.deps
315
+ });
316
+ }
317
+ /** Resolves the scenario's query/answer and derives the one in-memory task shared by every matched treatment. */
318
+ export async function resolveIncrementalChangeStalenessScenarioTask(options) {
319
+ const { scenario } = options;
320
+ const resolved = await resolveIncrementalChangeStalenessQueryAndAnswer({ scenario, repoRoot: options.repoRoot });
321
+ const cases = await readEvaluationCases(path.resolve(options.repoRoot, WARM_INDEX_BENCHMARK_CASES_PATH), options.repoRoot);
322
+ const baseEvaluationCase = cases.find((candidate) => candidate.id === scenario.baseCaseId);
323
+ if (!baseEvaluationCase) {
324
+ throw new Error(`Scenario ${scenario.id}: base case ${scenario.baseCaseId} was not found for task-descriptor construction.`);
325
+ }
326
+ return { resolved, derivedTask: buildIncrementalChangeStalenessDerivedTask({ scenario, baseEvaluationCase, resolved }) };
327
+ }
296
328
  /**
297
329
  * Executes the frozen Batch 4 order (section 36) for one ready lifecycle session: resolve query and
298
330
  * answer, derive the task descriptor, assess both treatments' affected neighborhoods from BASELINE
@@ -323,13 +355,7 @@ export async function executeIncrementalChangeStalenessScenario(options) {
323
355
  };
324
356
  }
325
357
  const session = options.lifecycle.session;
326
- const resolved = await resolveIncrementalChangeStalenessQueryAndAnswer({ scenario, repoRoot: options.repoRoot });
327
- const cases = await readEvaluationCases(path.resolve(options.repoRoot, WARM_INDEX_BENCHMARK_CASES_PATH), options.repoRoot);
328
- const baseEvaluationCase = cases.find((candidate) => candidate.id === scenario.baseCaseId);
329
- if (!baseEvaluationCase) {
330
- throw new Error(`Scenario ${scenario.id}: base case ${scenario.baseCaseId} was not found for task-descriptor construction.`);
331
- }
332
- const derivedTask = buildIncrementalChangeStalenessDerivedTask({ scenario, baseEvaluationCase, resolved });
358
+ const { resolved, derivedTask } = await resolveIncrementalChangeStalenessScenarioTask({ repoRoot: options.repoRoot, scenario });
333
359
  const staleAffected = assessIncrementalChangeStalenessAffectedNeighborhood(session.treatments["stale-index"], derivedTask);
334
360
  const fullAffected = assessIncrementalChangeStalenessAffectedNeighborhood(session.treatments["full-refresh"], derivedTask);
335
361
  const symmetry = checkIncrementalChangeStalenessAffectedNeighborhoodSymmetry(staleAffected, fullAffected);
@@ -9,7 +9,7 @@ import { resolveWithinRoot } from "../../../core/pathSafety.js";
9
9
  // ---------------------------------------------------------------------------
10
10
  export const INCREMENTAL_CHANGE_STALENESS_EXECUTION_ARTIFACT_FILE = "incremental-change-staleness-execution.json";
11
11
  export const INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION = "my-dev-kit-lab-incremental-change-staleness-execution-v1";
12
- function sortedUnique(values) {
12
+ export function sortedUnique(values) {
13
13
  return [...new Set(values)].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
14
14
  }
15
15
  function summarizeCommand(command) {
@@ -24,7 +24,7 @@ function summarizeCommand(command) {
24
24
  error: command.error ?? null
25
25
  };
26
26
  }
27
- function summarizeRetrieval(treatment) {
27
+ export function summarizeRetrieval(treatment) {
28
28
  const retrieval = treatment.retrieval;
29
29
  if (!retrieval) {
30
30
  return {
@@ -56,7 +56,7 @@ function summarizeRetrieval(treatment) {
56
56
  commands: retrieval.commands.map(summarizeCommand)
57
57
  };
58
58
  }
59
- function summarizeFakeAgent(treatment) {
59
+ export function summarizeFakeAgent(treatment) {
60
60
  if (!treatment.fakeAgent)
61
61
  return null;
62
62
  const agent = treatment.fakeAgent;
@@ -69,7 +69,7 @@ function summarizeFakeAgent(treatment) {
69
69
  errors: [...agent.errors]
70
70
  };
71
71
  }
72
- function summarizeRequiredFileEvidence(evidence) {
72
+ export function summarizeRequiredFileEvidence(evidence) {
73
73
  return {
74
74
  status: evidence.status,
75
75
  requiredFiles: sortedUnique(evidence.requiredFiles),
@@ -0,0 +1,289 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { resolveWithinRoot } from "../../../core/pathSafety.js";
3
+ import { classifyStaleRisk } from "./comparison.js";
4
+ import { classifyPartialRefreshAgainstFull } from "./comparisonV2.js";
5
+ import { INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS, INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_INTENTS } from "./disposableTarget.js";
6
+ import { INCREMENTAL_CHANGE_STALENESS_EXECUTION_ARTIFACT_FILE, INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION, sortedUnique, summarizeFakeAgent, summarizeRequiredFileEvidence, summarizeRetrieval } from "./executionArtifact.js";
7
+ // ---------------------------------------------------------------------------
8
+ // v0.6.3 Batch 3 -- persisted four-treatment execution artifact. A distinct schema from V1 written to the
9
+ // same canonical filename; the schemaVersion is the discriminator. Bounded structured evidence only:
10
+ // never raw retrieved context bodies, graph artifacts, or file contents. Descriptive counts, no ranking.
11
+ // ---------------------------------------------------------------------------
12
+ export const INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION_V2 = "my-dev-kit-lab-incremental-change-staleness-execution-v2";
13
+ const PARTIAL_IDS = ["changed-files-refresh", "affected-neighborhood-refresh"];
14
+ const REQUESTED_SCOPE = {
15
+ "changed-files-refresh": "changed-files",
16
+ "affected-neighborhood-refresh": "affected-neighborhood"
17
+ };
18
+ const COMPARISON_CANDIDATE_ORDER = ["stale-index", "changed-files-refresh", "affected-neighborhood-refresh"];
19
+ function record(build) {
20
+ return Object.fromEntries(INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS.map((id) => [id, build(id)]));
21
+ }
22
+ function summarizeTreatmentV2(treatment) {
23
+ return {
24
+ treatmentId: treatment.treatmentId,
25
+ treatmentIntent: treatment.treatmentIntent,
26
+ status: treatment.status,
27
+ failureReason: treatment.failureReason,
28
+ activeIndexPhase: treatment.activeIndexPhase,
29
+ baselineFreshness: structuredClone(treatment.baselineFreshness),
30
+ refreshedFreshness: treatment.refreshedFreshness ? structuredClone(treatment.refreshedFreshness) : null,
31
+ refreshExecution: structuredClone(treatment.refreshExecution),
32
+ affectedNeighborhood: structuredClone(treatment.affectedNeighborhood),
33
+ // Bounded status/telemetry only; never embeds retrieval.contextText.
34
+ retrieval: summarizeRetrieval(treatment),
35
+ fakeAgent: summarizeFakeAgent(treatment),
36
+ requiredFileEvidence: summarizeRequiredFileEvidence(treatment.requiredFileEvidence)
37
+ };
38
+ }
39
+ function summarizeLifecycleV2(execution) {
40
+ const session = execution.session;
41
+ if (!session)
42
+ return null;
43
+ const refreshedStatus = {};
44
+ for (const id of INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS) {
45
+ const freshness = session.treatments[id].refreshedFreshness;
46
+ if (freshness)
47
+ refreshedStatus[id] = freshness.status;
48
+ }
49
+ return {
50
+ controlledFilePaths: [...session.controlledChangedPaths],
51
+ treatmentIntents: record((id) => INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_INTENTS[id]),
52
+ preMutationEquivalence: {
53
+ "changed-files-refresh": session.preMutationEquivalence["changed-files-refresh"].result,
54
+ "affected-neighborhood-refresh": session.preMutationEquivalence["affected-neighborhood-refresh"].result,
55
+ "full-refresh": session.preMutationEquivalence["full-refresh"].result
56
+ },
57
+ postMutationEquivalence: {
58
+ "changed-files-refresh": session.postMutationEquivalence["changed-files-refresh"].result,
59
+ "affected-neighborhood-refresh": session.postMutationEquivalence["affected-neighborhood-refresh"].result,
60
+ "full-refresh": session.postMutationEquivalence["full-refresh"].result
61
+ },
62
+ baselineFreshnessStatus: record((id) => session.treatments[id].changeAuthority.postMutationBaselineFreshness.status),
63
+ refreshedFreshnessStatus: refreshedStatus,
64
+ partialRealization: {
65
+ "changed-files-refresh": session.treatments["changed-files-refresh"].refreshRealization,
66
+ "affected-neighborhood-refresh": session.treatments["affected-neighborhood-refresh"].refreshRealization
67
+ },
68
+ indexInvocationCounts: record((id) => session.treatments[id].indexInvocationCount),
69
+ freshnessAssessmentCounts: record((id) => session.treatments[id].freshnessAssessmentCount),
70
+ totalIndexInvocationCount: session.totalIndexInvocationCount,
71
+ totalFreshnessAssessmentCount: session.totalFreshnessAssessmentCount,
72
+ myDevKitVersion: session.toolIdentity.version ?? null,
73
+ sourceRoots: [...session.baseCase.sourceRoots]
74
+ };
75
+ }
76
+ function summarizeScenarioV2(execution) {
77
+ return {
78
+ scenarioId: execution.scenarioId,
79
+ category: execution.category,
80
+ benchmarkProjectId: execution.benchmarkProjectId,
81
+ baseCaseId: execution.baseCaseId,
82
+ answerPolicy: execution.answerPolicy,
83
+ query: execution.query,
84
+ expectedFiles: sortedUnique(execution.expectedFiles),
85
+ expectedSymbols: sortedUnique(execution.expectedSymbols),
86
+ status: execution.status,
87
+ failureReason: execution.failureReason,
88
+ lifecycle: summarizeLifecycleV2(execution),
89
+ treatments: execution.treatments.map(summarizeTreatmentV2),
90
+ referenceComparisons: structuredClone(execution.referenceComparisons)
91
+ };
92
+ }
93
+ function partialComparison(scenario, candidate) {
94
+ const found = scenario.referenceComparisons.find((comparison) => comparison.candidateTreatmentId === candidate);
95
+ return found && found.kind === "partial-refresh" ? found : null;
96
+ }
97
+ function countPartial(scenarios, candidate, classification) {
98
+ return scenarios.filter((scenario) => partialComparison(scenario, candidate)?.classification === classification).length;
99
+ }
100
+ function countRealization(scenarios, candidate, realization) {
101
+ return scenarios.filter((scenario) => scenario.treatments.find((treatment) => treatment.treatmentId === candidate)?.refreshExecution.realization === realization).length;
102
+ }
103
+ function summarizeV2(scenarios) {
104
+ const staleClass = (scenario) => {
105
+ const stale = scenario.referenceComparisons.find((comparison) => comparison.candidateTreatmentId === "stale-index");
106
+ return stale && stale.kind === "stale-risk" ? stale.comparison.staleRiskClassification : null;
107
+ };
108
+ return {
109
+ scenarioCount: scenarios.length,
110
+ readyScenarioCount: scenarios.filter((scenario) => scenario.status === "ready").length,
111
+ failedScenarioCount: scenarios.filter((scenario) => scenario.status === "failed").length,
112
+ staleObservedRegressionCount: scenarios.filter((scenario) => staleClass(scenario) === "observed-stale-regression").length,
113
+ staleNoObservedRegressionCount: scenarios.filter((scenario) => staleClass(scenario) === "no-observed-stale-regression").length,
114
+ staleInconclusiveCount: scenarios.filter((scenario) => staleClass(scenario) === "inconclusive").length,
115
+ changedFilesAppliedPartialCount: countRealization(scenarios, "changed-files-refresh", "APPLIED_PARTIAL"),
116
+ changedFilesFallbackFullCount: countRealization(scenarios, "changed-files-refresh", "FALLBACK_FULL"),
117
+ affectedNeighborhoodAppliedPartialCount: countRealization(scenarios, "affected-neighborhood-refresh", "APPLIED_PARTIAL"),
118
+ affectedNeighborhoodFallbackFullCount: countRealization(scenarios, "affected-neighborhood-refresh", "FALLBACK_FULL"),
119
+ changedFilesObservedRegressionRelativeToFullCount: countPartial(scenarios, "changed-files-refresh", "observed-regression-relative-to-full"),
120
+ changedFilesNoObservedRegressionRelativeToFullCount: countPartial(scenarios, "changed-files-refresh", "no-observed-regression-relative-to-full"),
121
+ changedFilesInconclusiveCount: countPartial(scenarios, "changed-files-refresh", "inconclusive"),
122
+ changedFilesNotComparableCount: countPartial(scenarios, "changed-files-refresh", "not-comparable-as-partial-refresh"),
123
+ affectedNeighborhoodObservedRegressionRelativeToFullCount: countPartial(scenarios, "affected-neighborhood-refresh", "observed-regression-relative-to-full"),
124
+ affectedNeighborhoodNoObservedRegressionRelativeToFullCount: countPartial(scenarios, "affected-neighborhood-refresh", "no-observed-regression-relative-to-full"),
125
+ affectedNeighborhoodInconclusiveCount: countPartial(scenarios, "affected-neighborhood-refresh", "inconclusive"),
126
+ affectedNeighborhoodNotComparableCount: countPartial(scenarios, "affected-neighborhood-refresh", "not-comparable-as-partial-refresh")
127
+ };
128
+ }
129
+ function contradiction(scenarioId, message) {
130
+ throw new Error(`Contradictory v2 execution evidence for scenario ${scenarioId}: ${message}`);
131
+ }
132
+ function sameList(left, right) {
133
+ return left.length === right.length && left.every((value, index) => value === right[index]);
134
+ }
135
+ /**
136
+ * Rejects internally contradictory evidence; called before anything is built or written. Unavailable/null
137
+ * evidence is preserved, never coerced.
138
+ */
139
+ export function validateIncrementalChangeStalenessScenarioRecordV2(scenario) {
140
+ const id = scenario.scenarioId;
141
+ // Comparisons: exactly three, fixed order, explicit identities, reference is full-refresh, never full vs full.
142
+ if (!sameList(scenario.referenceComparisons.map((comparison) => comparison.candidateTreatmentId), COMPARISON_CANDIDATE_ORDER)) {
143
+ contradiction(id, "reference comparisons must be exactly stale-index, changed-files-refresh, affected-neighborhood-refresh in order.");
144
+ }
145
+ for (const comparison of scenario.referenceComparisons) {
146
+ if (comparison.referenceTreatmentId !== "full-refresh")
147
+ contradiction(id, `${comparison.candidateTreatmentId} comparison references ${comparison.referenceTreatmentId}, not full-refresh.`);
148
+ if ((comparison.candidateTreatmentId === "stale-index") !== (comparison.kind === "stale-risk")) {
149
+ contradiction(id, `${comparison.candidateTreatmentId} comparison uses the wrong comparison kind (${comparison.kind}).`);
150
+ }
151
+ }
152
+ // Stale comparison must use the unchanged stale-risk semantics.
153
+ const stale = scenario.referenceComparisons[0];
154
+ if (stale.kind === "stale-risk") {
155
+ const expected = classifyStaleRisk(stale.comparison.correctnessRelation, stale.comparison.requiredFileEvidenceRelation);
156
+ if (expected.staleRiskClassification !== stale.comparison.staleRiskClassification || !sameList(expected.reasonCodes, stale.comparison.reasonCodes)) {
157
+ contradiction(id, "stale-index comparison does not follow the stale-risk classification rules.");
158
+ }
159
+ }
160
+ // Treatment collection.
161
+ if (scenario.treatments.length === 0) {
162
+ if (scenario.status === "ready")
163
+ contradiction(id, "a ready scenario must carry four treatment records.");
164
+ for (const comparison of scenario.referenceComparisons) {
165
+ if (comparison.kind === "partial-refresh" && comparison.refreshRealization !== null) {
166
+ contradiction(id, `${comparison.candidateTreatmentId} carries a refresh realization although no treatment executed.`);
167
+ }
168
+ }
169
+ return;
170
+ }
171
+ if (scenario.status !== "ready")
172
+ contradiction(id, "a failed scenario must not carry treatment records.");
173
+ if (!sameList(scenario.treatments.map((treatment) => treatment.treatmentId), INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS)) {
174
+ contradiction(id, "treatments must be exactly stale-index, changed-files-refresh, affected-neighborhood-refresh, full-refresh, each once, in order.");
175
+ }
176
+ for (const treatment of scenario.treatments) {
177
+ if (treatment.treatmentIntent !== INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_INTENTS[treatment.treatmentId]) {
178
+ contradiction(id, `${treatment.treatmentId} carries treatment intent ${treatment.treatmentIntent}.`);
179
+ }
180
+ const refresh = treatment.refreshExecution;
181
+ if (treatment.treatmentId === "stale-index") {
182
+ if (refresh.kind !== "no-refresh" || refresh.realization !== "NO_REFRESH" || refresh.incrementalRefresh !== null)
183
+ contradiction(id, "stale-index must be no-refresh without incrementalRefresh evidence.");
184
+ if (treatment.activeIndexPhase !== "baseline")
185
+ contradiction(id, "stale-index active index must be the baseline.");
186
+ if (treatment.refreshedFreshness !== null)
187
+ contradiction(id, "stale-index has no refreshed freshness.");
188
+ }
189
+ else {
190
+ if (treatment.activeIndexPhase !== "refreshed")
191
+ contradiction(id, `${treatment.treatmentId} active index must be the refreshed index.`);
192
+ if (treatment.refreshedFreshness === null)
193
+ contradiction(id, `${treatment.treatmentId} is missing refreshed freshness.`);
194
+ }
195
+ if (treatment.treatmentId === "full-refresh") {
196
+ if (refresh.kind !== "full" || refresh.realization !== "FULL_REFRESH" || refresh.incrementalRefresh !== null)
197
+ contradiction(id, "full-refresh must be a full refresh without incrementalRefresh evidence.");
198
+ }
199
+ if (treatment.treatmentId === "changed-files-refresh" || treatment.treatmentId === "affected-neighborhood-refresh") {
200
+ if (refresh.kind !== "incremental")
201
+ contradiction(id, `${treatment.treatmentId} must be an incremental refresh.`);
202
+ if (refresh.kind === "incremental") {
203
+ const upstream = refresh.incrementalRefresh;
204
+ if (upstream.requestedScope !== REQUESTED_SCOPE[treatment.treatmentId])
205
+ contradiction(id, `${treatment.treatmentId} requested scope ${upstream.requestedScope} does not match the treatment.`);
206
+ if (refresh.realization === "APPLIED_PARTIAL" && (upstream.selectionStatus !== "applied" || upstream.appliedScope !== upstream.requestedScope)) {
207
+ contradiction(id, `${treatment.treatmentId} is APPLIED_PARTIAL but upstream reported status=${upstream.selectionStatus} applied=${upstream.appliedScope}.`);
208
+ }
209
+ if (refresh.realization === "FALLBACK_FULL" && (upstream.selectionStatus !== "fallback-full" || upstream.appliedScope !== "full")) {
210
+ contradiction(id, `${treatment.treatmentId} is FALLBACK_FULL but upstream reported status=${upstream.selectionStatus} applied=${upstream.appliedScope}.`);
211
+ }
212
+ }
213
+ }
214
+ }
215
+ // Partial comparisons must agree with their treatment's realization and the frozen precedence.
216
+ for (const candidate of PARTIAL_IDS) {
217
+ const comparison = partialComparison(scenario, candidate);
218
+ const treatment = scenario.treatments.find((entry) => entry.treatmentId === candidate);
219
+ if (!comparison || !treatment || treatment.refreshExecution.kind !== "incremental")
220
+ contradiction(id, `${candidate} comparison or treatment is missing.`);
221
+ else {
222
+ if (comparison.refreshRealization !== treatment.refreshExecution.realization) {
223
+ contradiction(id, `${candidate} comparison realization ${comparison.refreshRealization} differs from the treatment realization ${treatment.refreshExecution.realization}.`);
224
+ }
225
+ const expected = classifyPartialRefreshAgainstFull({
226
+ realization: treatment.refreshExecution.realization,
227
+ correctnessRelation: comparison.correctnessRelation,
228
+ requiredFileEvidenceRelation: comparison.requiredFileEvidenceRelation
229
+ });
230
+ if (expected.classification !== comparison.classification || !sameList(expected.reasonCodes, comparison.reasonCodes)) {
231
+ contradiction(id, `${candidate} comparison does not follow the partial-refresh reference rules.`);
232
+ }
233
+ }
234
+ }
235
+ if (scenario.lifecycle) {
236
+ for (const count of [...Object.values(scenario.lifecycle.indexInvocationCounts), ...Object.values(scenario.lifecycle.freshnessAssessmentCounts)]) {
237
+ if (!Number.isInteger(count) || count < 0)
238
+ contradiction(id, "lifecycle counts must be non-negative integers.");
239
+ }
240
+ }
241
+ else {
242
+ contradiction(id, "a ready scenario must carry a lifecycle summary.");
243
+ }
244
+ }
245
+ export function validateIncrementalChangeStalenessExecutionArtifactV2(artifact) {
246
+ if (artifact.schemaVersion !== INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION_V2) {
247
+ throw new Error(`Unexpected execution artifact schema ${String(artifact.schemaVersion)}.`);
248
+ }
249
+ for (const scenario of artifact.scenarios)
250
+ validateIncrementalChangeStalenessScenarioRecordV2(scenario);
251
+ }
252
+ /**
253
+ * Builds and validates the v2 artifact from already-executed scenario evidence, in the caller's canonical
254
+ * scenario order. Throws on internal contradiction instead of serializing it.
255
+ */
256
+ export function buildIncrementalChangeStalenessExecutionArtifactV2(args) {
257
+ const scenarios = args.executions.map(summarizeScenarioV2);
258
+ const artifact = {
259
+ schemaVersion: INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION_V2,
260
+ runId: args.runId,
261
+ pluginId: args.pluginId,
262
+ scenarios,
263
+ summary: summarizeV2(scenarios)
264
+ };
265
+ validateIncrementalChangeStalenessExecutionArtifactV2(artifact);
266
+ return artifact;
267
+ }
268
+ /** Writes to the canonical filename shared with v1; validates first so contradictory evidence is never serialized. */
269
+ export async function writeIncrementalChangeStalenessExecutionArtifactV2(outDir, artifact) {
270
+ validateIncrementalChangeStalenessExecutionArtifactV2(artifact);
271
+ await mkdir(outDir, { recursive: true });
272
+ const artifactPath = resolveWithinRoot(outDir, INCREMENTAL_CHANGE_STALENESS_EXECUTION_ARTIFACT_FILE);
273
+ await writeFile(artifactPath, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
274
+ return artifactPath;
275
+ }
276
+ /** Discriminates V1 from V2 by schemaVersion only; historical V1 evidence is never converted to V2. */
277
+ export function parseIncrementalChangeStalenessExecutionArtifact(json) {
278
+ const schemaVersion = json && typeof json === "object" ? json.schemaVersion : undefined;
279
+ if (schemaVersion === INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION) {
280
+ return { version: "v1", artifact: json };
281
+ }
282
+ if (schemaVersion === INCREMENTAL_CHANGE_STALENESS_EXECUTION_SCHEMA_VERSION_V2) {
283
+ return { version: "v2", artifact: json };
284
+ }
285
+ throw new Error(`Unrecognized incremental-change-staleness execution artifact schema: ${String(schemaVersion)}.`);
286
+ }
287
+ export async function readIncrementalChangeStalenessExecutionArtifact(artifactPath) {
288
+ return parseIncrementalChangeStalenessExecutionArtifact(JSON.parse(await readFile(artifactPath, "utf8")));
289
+ }
@@ -0,0 +1,195 @@
1
+ import path from "node:path";
2
+ import { resolveWithinRoot } from "../../../core/pathSafety.js";
3
+ import { classifyStaleRisk, compareCorrectness, compareRequiredFileEvidence } from "./comparison.js";
4
+ import { compareCandidateCorrectness, compareCandidateRequiredFileEvidence, classifyPartialRefreshAgainstFull } from "./comparisonV2.js";
5
+ import { INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS, INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_INTENTS } from "./disposableTarget.js";
6
+ import { assessIncrementalChangeStalenessAffectedNeighborhood, checkIncrementalChangeStalenessAffectedNeighborhoodSymmetry, correctnessComparable, defaultIncrementalChangeStalenessExecutionDeps, executeTreatmentEvaluation, INCONCLUSIVE_NO_EXECUTION, resolveIncrementalChangeStalenessScenarioTask } from "./execution.js";
7
+ /**
8
+ * Four-way check with stale-index only as the deterministic reference. Reuses the v0.6.2 pairwise check
9
+ * (status, relationship, reindexRecommendation, and the six numeric metrics) and additionally requires
10
+ * equal seedNodeCount. Conflicting evidence is never averaged or merged.
11
+ */
12
+ export function checkAffectedNeighborhoodFourWaySymmetry(assessments) {
13
+ const reference = assessments["stale-index"];
14
+ const reasons = [];
15
+ for (const treatmentId of INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS) {
16
+ if (treatmentId === "stale-index")
17
+ continue;
18
+ const other = assessments[treatmentId];
19
+ const pairwise = checkIncrementalChangeStalenessAffectedNeighborhoodSymmetry(reference, other);
20
+ if (!pairwise.symmetric)
21
+ reasons.push(`${treatmentId}: ${pairwise.reason}`);
22
+ if (reference.seedNodeCount !== other.seedNodeCount) {
23
+ reasons.push(`${treatmentId}: seedNodeCount (${reference.seedNodeCount} vs ${other.seedNodeCount})`);
24
+ }
25
+ }
26
+ return reasons.length === 0 ? { symmetric: true } : { symmetric: false, reason: `Affected-neighborhood assessments are asymmetric across treatments: ${reasons.join(" | ")}` };
27
+ }
28
+ const PARTIAL_TREATMENT_IDS = ["changed-files-refresh", "affected-neighborhood-refresh"];
29
+ function placeholderComparisons() {
30
+ return [
31
+ { kind: "stale-risk", candidateTreatmentId: "stale-index", referenceTreatmentId: "full-refresh", comparison: { ...INCONCLUSIVE_NO_EXECUTION, reasonCodes: [...INCONCLUSIVE_NO_EXECUTION.reasonCodes] } },
32
+ ...PARTIAL_TREATMENT_IDS.map((candidateTreatmentId) => ({
33
+ kind: "partial-refresh",
34
+ candidateTreatmentId,
35
+ referenceTreatmentId: "full-refresh",
36
+ refreshRealization: null,
37
+ correctnessRelation: "unknown",
38
+ requiredFileEvidenceRelation: "unknown",
39
+ classification: "inconclusive",
40
+ reasonCodes: ["correctness-unavailable", "required-file-evidence-unavailable"]
41
+ }))
42
+ ];
43
+ }
44
+ function refreshExecutionOf(session, treatmentId) {
45
+ const treatment = session.treatments[treatmentId];
46
+ if (treatment.refreshRealization !== null) {
47
+ const incrementalRefresh = treatment.refreshedIndex.incrementalRefresh;
48
+ if (!incrementalRefresh) {
49
+ throw new Error(`${treatmentId} lifecycle reported ${treatment.refreshRealization} without upstream incrementalRefresh evidence.`);
50
+ }
51
+ return { kind: "incremental", realization: treatment.refreshRealization, incrementalRefresh: structuredClone(incrementalRefresh) };
52
+ }
53
+ return treatment.postMutationIndexBuilt
54
+ ? { kind: "full", realization: "FULL_REFRESH", incrementalRefresh: null }
55
+ : { kind: "no-refresh", realization: "NO_REFRESH", incrementalRefresh: null };
56
+ }
57
+ /**
58
+ * Executes the four matched treatments in fixed order for one lifecycle-ready V2 session, then builds
59
+ * exactly three comparisons against full-refresh. A lifecycle that is not ready, or an affected-neighborhood
60
+ * asymmetry, is a failed scenario with no fabricated treatment evidence.
61
+ */
62
+ export async function executeIncrementalChangeStalenessScenarioV2(options) {
63
+ const deps = { ...defaultIncrementalChangeStalenessExecutionDeps, ...options.deps };
64
+ const scenario = options.scenario;
65
+ if (options.lifecycle.status !== "ready") {
66
+ return {
67
+ scenarioId: scenario.id,
68
+ category: scenario.category,
69
+ benchmarkProjectId: options.baseCase.benchmarkProjectId,
70
+ baseCaseId: options.baseCase.caseId,
71
+ answerPolicy: scenario.answerPolicy,
72
+ query: null,
73
+ expectedFiles: [],
74
+ expectedSymbols: [],
75
+ status: "failed",
76
+ failureReason: `${options.lifecycle.failure.code}: ${options.lifecycle.failure.message}`,
77
+ session: null,
78
+ treatments: [],
79
+ referenceComparisons: placeholderComparisons()
80
+ };
81
+ }
82
+ const session = options.lifecycle.session;
83
+ const { resolved, derivedTask } = await resolveIncrementalChangeStalenessScenarioTask({ repoRoot: options.repoRoot, scenario });
84
+ const scenarioBase = {
85
+ scenarioId: scenario.id,
86
+ category: scenario.category,
87
+ benchmarkProjectId: scenario.benchmarkProjectId,
88
+ baseCaseId: scenario.baseCaseId,
89
+ answerPolicy: scenario.answerPolicy,
90
+ query: resolved.query,
91
+ expectedFiles: resolved.expectedFiles,
92
+ expectedSymbols: resolved.expectedSymbols,
93
+ session
94
+ };
95
+ // Affected-neighborhood evidence is derived from each treatment's ORIGINAL BASELINE change authority.
96
+ const affected = {};
97
+ for (const treatmentId of INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS) {
98
+ affected[treatmentId] = assessIncrementalChangeStalenessAffectedNeighborhood(session.treatments[treatmentId], derivedTask);
99
+ }
100
+ const symmetry = checkAffectedNeighborhoodFourWaySymmetry(affected);
101
+ if (!symmetry.symmetric) {
102
+ return {
103
+ ...scenarioBase,
104
+ status: "failed",
105
+ failureReason: `AFFECTED_NEIGHBORHOOD_ASYMMETRY: ${symmetry.reason}`,
106
+ treatments: [],
107
+ referenceComparisons: placeholderComparisons()
108
+ };
109
+ }
110
+ const retrievalCommandsDir = (treatmentId) => resolveWithinRoot(options.runOwnedRoot, path.join("commands", scenario.id, treatmentId, "retrieval"));
111
+ const agentOutDir = (treatmentId) => resolveWithinRoot(options.runOwnedRoot, path.join("agents", scenario.id, treatmentId));
112
+ // Fixed deterministic order, sequential; each treatment retrieves only from its own active index.
113
+ const treatments = [];
114
+ for (const treatmentId of INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_IDS) {
115
+ const lifecycleTreatment = session.treatments[treatmentId];
116
+ const evaluation = await executeTreatmentEvaluation({
117
+ treatmentId,
118
+ baselineFreshness: lifecycleTreatment.changeAuthority.postMutationBaselineFreshness,
119
+ activeIndexPhase: lifecycleTreatment.activeRetrieval.role,
120
+ activeIndexDir: lifecycleTreatment.activeRetrieval.index.indexDir,
121
+ derivedTask,
122
+ affectedNeighborhood: affected[treatmentId],
123
+ requiredFiles: resolved.expectedFiles,
124
+ kitCommand: session.kitCommand,
125
+ retrievalCommandsDir: retrievalCommandsDir(treatmentId),
126
+ agentOutDir: agentOutDir(treatmentId),
127
+ projectProfiles: options.projectProfiles,
128
+ cwd: options.cwd,
129
+ env: options.env,
130
+ deps
131
+ });
132
+ treatments.push({
133
+ ...evaluation,
134
+ treatmentIntent: INCREMENTAL_CHANGE_STALENESS_V2_TREATMENT_INTENTS[treatmentId],
135
+ refreshedFreshness: lifecycleTreatment.refreshedFreshness,
136
+ refreshExecution: refreshExecutionOf(session, treatmentId)
137
+ });
138
+ }
139
+ return {
140
+ ...scenarioBase,
141
+ status: "ready",
142
+ failureReason: null,
143
+ treatments,
144
+ referenceComparisons: buildReferenceComparisons(treatments)
145
+ };
146
+ }
147
+ /** Exactly three comparisons against full-refresh: stale-index, changed-files-refresh, affected-neighborhood-refresh. */
148
+ export function buildReferenceComparisons(treatments) {
149
+ const byId = new Map(treatments.map((treatment) => [treatment.treatmentId, treatment]));
150
+ const reference = byId.get("full-refresh");
151
+ const stale = byId.get("stale-index");
152
+ if (!reference || !stale)
153
+ throw new Error("Reference comparisons require the stale-index and full-refresh treatment executions.");
154
+ // Historical v0.6.2 semantics, unchanged.
155
+ const staleCorrectness = compareCorrectness(correctnessComparable(stale.fakeAgent), correctnessComparable(reference.fakeAgent));
156
+ const staleRequired = compareRequiredFileEvidence(stale.requiredFileEvidence, reference.requiredFileEvidence);
157
+ const staleRisk = classifyStaleRisk(staleCorrectness, staleRequired);
158
+ const comparisons = [
159
+ {
160
+ kind: "stale-risk",
161
+ candidateTreatmentId: "stale-index",
162
+ referenceTreatmentId: "full-refresh",
163
+ comparison: {
164
+ correctnessRelation: staleCorrectness,
165
+ requiredFileEvidenceRelation: staleRequired,
166
+ staleRiskClassification: staleRisk.staleRiskClassification,
167
+ reasonCodes: staleRisk.reasonCodes
168
+ }
169
+ }
170
+ ];
171
+ for (const candidateTreatmentId of PARTIAL_TREATMENT_IDS) {
172
+ const candidate = byId.get(candidateTreatmentId);
173
+ if (!candidate || candidate.refreshExecution.kind !== "incremental") {
174
+ throw new Error(`${candidateTreatmentId} has no incremental refresh execution to compare.`);
175
+ }
176
+ const correctnessRelation = compareCandidateCorrectness(correctnessComparable(candidate.fakeAgent), correctnessComparable(reference.fakeAgent));
177
+ const requiredFileEvidenceRelation = compareCandidateRequiredFileEvidence(candidate.requiredFileEvidence, reference.requiredFileEvidence);
178
+ const classified = classifyPartialRefreshAgainstFull({
179
+ realization: candidate.refreshExecution.realization,
180
+ correctnessRelation,
181
+ requiredFileEvidenceRelation
182
+ });
183
+ comparisons.push({
184
+ kind: "partial-refresh",
185
+ candidateTreatmentId,
186
+ referenceTreatmentId: "full-refresh",
187
+ refreshRealization: candidate.refreshExecution.realization,
188
+ correctnessRelation,
189
+ requiredFileEvidenceRelation,
190
+ classification: classified.classification,
191
+ reasonCodes: classified.reasonCodes
192
+ });
193
+ }
194
+ return comparisons;
195
+ }