@elench/testkit 0.1.150 → 0.1.151

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 (48) hide show
  1. package/README.md +21 -9
  2. package/lib/cli/assistant/view-model.mjs +1 -1
  3. package/lib/cli/components/blocks/run-tree.mjs +1 -1
  4. package/lib/cli/renderers/run/events.mjs +4 -3
  5. package/lib/cli/renderers/run/failure.mjs +2 -2
  6. package/lib/cli/renderers/run/inline-detail.mjs +2 -2
  7. package/lib/cli/renderers/run/interactive.mjs +2 -2
  8. package/lib/cli/renderers/run/text-reporter.mjs +9 -9
  9. package/lib/cli/state/run/model.mjs +7 -7
  10. package/lib/cli/state/run/state.mjs +3 -3
  11. package/lib/cli/terminal/colors.mjs +1 -1
  12. package/lib/config/runtime.mjs +130 -0
  13. package/lib/config-api/index.d.ts +22 -0
  14. package/lib/database/cleanup.mjs +76 -1
  15. package/lib/database/index.mjs +6 -0
  16. package/lib/database/local-postgres.mjs +95 -4
  17. package/lib/database/naming.mjs +7 -0
  18. package/lib/database/state-files.mjs +12 -0
  19. package/lib/docker-compat/matrix.mjs +5 -3
  20. package/lib/kiln/client.mjs +8 -0
  21. package/lib/local/kiln-driver.mjs +96 -69
  22. package/lib/ownership/docker.mjs +67 -1
  23. package/lib/regressions/github-transport.mjs +178 -4
  24. package/lib/regressions/github.mjs +52 -16
  25. package/lib/regressions/index.d.ts +56 -28
  26. package/lib/regressions/index.mjs +122 -47
  27. package/lib/regressions/workflow.mjs +266 -0
  28. package/lib/results/artifacts.mjs +8 -7
  29. package/lib/runner/formatting.mjs +17 -16
  30. package/lib/runner/orchestrator.mjs +5 -4
  31. package/lib/runner/regressions.mjs +175 -33
  32. package/lib/runner/run-finalization.mjs +34 -4
  33. package/node_modules/@elench/next-analysis/package.json +1 -1
  34. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  35. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  36. package/node_modules/@elench/ts-analysis/package.json +1 -1
  37. package/node_modules/es-toolkit/CHANGELOG.md +801 -0
  38. package/node_modules/es-toolkit/src/compat/_internal/Equals.d.ts +1 -0
  39. package/node_modules/es-toolkit/src/compat/_internal/IsWritable.d.ts +3 -0
  40. package/node_modules/es-toolkit/src/compat/_internal/MutableList.d.ts +4 -0
  41. package/node_modules/es-toolkit/src/compat/_internal/RejectReadonly.d.ts +4 -0
  42. package/node_modules/esprima/ChangeLog +235 -0
  43. package/package.json +6 -5
  44. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts +0 -188
  45. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +0 -1
  46. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js +0 -293
  47. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js.map +0 -1
  48. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/package.json +0 -25
@@ -1,4 +1,4 @@
1
- import { findMatchingRegressionEntries } from "../regressions/index.mjs";
1
+ import { findMatchingRegressionCases } from "../regressions/index.mjs";
2
2
  import { buildRegressionSyncSummaryLines } from "../regressions/github.mjs";
3
3
 
4
4
  export function formatDuration(durationMs) {
@@ -70,8 +70,8 @@ export function buildRunSummaryData(results, durationMs, regressionReport = null
70
70
  newRegressions: regressionSummary?.newRegressions || 0,
71
71
  knownRegressions: regressionSummary?.knownRegressions || 0,
72
72
  fixedKnownRegressions: regressionSummary?.fixedKnownRegressions || 0,
73
- catalogStale: regressionSummary?.catalogStale || 0,
74
- catalogSyncUnavailable: Boolean(regressionSummary?.catalogSyncUnavailable),
73
+ staleCases: regressionSummary?.staleCases || 0,
74
+ syncUnavailable: Boolean(regressionSummary?.syncUnavailable),
75
75
  usedStaleCache: Boolean(regressionSummary?.usedStaleCache),
76
76
  };
77
77
  }
@@ -100,11 +100,11 @@ export function buildCompactRunSummaryLines(
100
100
  if (summary.fixedKnownRegressions > 0) {
101
101
  lines.push(`Fixed known regressions: ${summary.fixedKnownRegressions}`);
102
102
  }
103
- if (summary.catalogStale > 0) {
104
- lines.push(`Catalog stale: ${summary.catalogStale}`);
103
+ if (summary.staleCases > 0) {
104
+ lines.push(`Case store stale: ${summary.staleCases}`);
105
105
  }
106
- if (summary.catalogSyncUnavailable) {
107
- lines.push("Catalog sync unavailable");
106
+ if (summary.syncUnavailable) {
107
+ lines.push("Case store sync unavailable");
108
108
  }
109
109
 
110
110
  lines.push("");
@@ -186,11 +186,11 @@ export function buildDebugRunSummaryLines(results, durationMs, regressionReport
186
186
  }
187
187
 
188
188
  const regressionSyncLines = buildRegressionSyncSummaryLines(
189
- regressionReport?.catalog?.configured ? {
189
+ regressionReport?.caseStore?.configured ? {
190
190
  summary: {
191
191
  byCode: {
192
- closed_but_failing: regressionReport.summary.catalogStale,
193
- validation_unavailable: regressionReport.summary.catalogSyncUnavailable ? 1 : 0,
192
+ closed_but_failing: regressionReport.summary.staleCases,
193
+ validation_unavailable: regressionReport.summary.syncUnavailable ? 1 : 0,
194
194
  used_stale_cache: regressionReport.summary.usedStaleCache ? 1 : 0,
195
195
  },
196
196
  },
@@ -209,7 +209,7 @@ export function buildDebugRunSummaryLines(results, durationMs, regressionReport
209
209
  return lines;
210
210
  }
211
211
 
212
- export function buildFailurePresentation(fileSummary, regressionCatalog = null) {
212
+ export function buildFailurePresentation(fileSummary, regressionCaseStore = null) {
213
213
  const rankedDetails = rankFailureDetails(fileSummary.failureDetails || []);
214
214
  const primaryDetail = rankedDetails[0] || null;
215
215
  const fallbackMessages = rankedDetails
@@ -221,7 +221,7 @@ export function buildFailurePresentation(fileSummary, regressionCatalog = null)
221
221
  const responseLine = formatFailureResponsePreview(primaryDetail);
222
222
  if (responseLine) details.push(responseLine);
223
223
 
224
- const regressionLine = formatInlineRegressionLine(fileSummary, regressionCatalog);
224
+ const regressionLine = formatInlineRegressionLine(fileSummary, regressionCaseStore);
225
225
  if (regressionLine) details.push(regressionLine);
226
226
 
227
227
  const requestLine = formatFailureRequestHint(primaryDetail);
@@ -318,13 +318,14 @@ function formatFailureRequestHint(detail) {
318
318
  return `request: ${method} ${path}`;
319
319
  }
320
320
 
321
- function formatInlineRegressionLine(fileSummary, regressionCatalog) {
322
- if (!regressionCatalog) return "regression: new";
323
- const matches = findMatchingRegressionEntries(regressionCatalog, fileSummary);
321
+ function formatInlineRegressionLine(fileSummary, regressionCaseStore) {
322
+ if (!regressionCaseStore) return "regression: new";
323
+ const matches = findMatchingRegressionCases(regressionCaseStore, fileSummary);
324
324
  if (matches.length === 0) return "regression: new";
325
325
 
326
326
  const entry = matches[0];
327
- return `regression: known #${entry.issue.number} ${entry.classification}`;
327
+ const issue = entry.issue ? ` #${entry.issue.number}` : "";
328
+ return `regression: known${issue} ${entry.classification}`;
328
329
  }
329
330
 
330
331
  function isThresholdWrapperMessage(message) {
@@ -12,7 +12,7 @@ import {
12
12
  recordGraphError,
13
13
  recordTaskOutcome,
14
14
  } from "./results.mjs";
15
- import { loadRegressionCatalogConfig } from "./regressions.mjs";
15
+ import { loadRegressionCaseStoreConfig } from "./regressions.mjs";
16
16
  import { formatError } from "./formatting.mjs";
17
17
  import {
18
18
  loadTimings,
@@ -58,12 +58,12 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
58
58
  },
59
59
  testkitVersion: readPackageMetadata().version,
60
60
  };
61
- const regressionCatalog = loadRegressionCatalogConfig(
61
+ const regressionCaseStore = loadRegressionCaseStoreConfig(
62
62
  productDir,
63
63
  configs[0]?.testkit?.regressions || null
64
64
  );
65
65
  const reporter = opts.reporter || null;
66
- reporter?.setRegressionCatalog?.(regressionCatalog);
66
+ reporter?.setRegressionCaseStore?.(regressionCaseStore);
67
67
  const logRegistry = createRunLogRegistry(productDir);
68
68
  const workerState = {
69
69
  workerCount: 0,
@@ -213,8 +213,9 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
213
213
  metadata,
214
214
  logRegistry,
215
215
  setupRegistry,
216
- regressionCatalog,
216
+ regressionCaseStore,
217
217
  regressionSyncConfig: configs[0]?.testkit?.regressions?.sync || null,
218
+ regressionWorkflowConfig: configs[0]?.testkit?.regressions?.workflow || null,
218
219
  telemetry,
219
220
  reporter,
220
221
  writeStatus: opts.writeStatus,
@@ -1,24 +1,27 @@
1
1
  import {
2
2
  buildRegressionFileIdentity,
3
- findMatchingRegressionEntries,
4
- loadRegressionCatalogConfig,
3
+ findMatchingRegressionCases,
4
+ loadRegressionCaseStoreConfig,
5
+ writeRegressionCaseStoreDocument,
5
6
  } from "../regressions/index.mjs";
6
7
 
7
- export { loadRegressionCatalogConfig };
8
+ export { loadRegressionCaseStoreConfig };
8
9
 
9
10
  export function applyRegressionAnalysisToArtifacts(
10
11
  runArtifact,
11
12
  statusArtifact,
12
- regressionCatalog,
13
+ regressionCaseStore,
13
14
  regressionSync
14
15
  ) {
15
16
  const runEntries = extractRunFileEntries(runArtifact);
16
17
  const statusEntries = extractStatusFileEntries(statusArtifact);
17
18
  const fileSummaries = new Map();
18
- const syncById = new Map((regressionSync?.entries || []).map((entry) => [entry.id, entry]));
19
- const staleEntryIds = new Set();
19
+ const syncById = new Map((regressionSync?.cases || []).map((entry) => [entry.id, entry]));
20
+ const staleCaseIds = new Set();
20
21
  const newRegressionDrafts = [];
21
22
  const fixedRegressionDrafts = [];
23
+ const workflowActions = [];
24
+ let caseStoreChanged = false;
22
25
 
23
26
  for (const entry of [...runEntries, ...statusEntries]) {
24
27
  const key = buildRegressionFileIdentity(entry.service, entry.type, entry.path);
@@ -37,23 +40,64 @@ export function applyRegressionAnalysisToArtifacts(
37
40
  const diagnosesByFileKey = new Map();
38
41
 
39
42
  for (const fileSummary of fileSummaries.values()) {
40
- const diagnosis = buildFileDiagnosis(fileSummary, regressionCatalog, syncById);
43
+ const diagnosis = buildFileDiagnosis(fileSummary, regressionCaseStore, syncById);
41
44
  const fileKey = buildRegressionFileIdentity(
42
45
  fileSummary.service,
43
46
  fileSummary.type,
44
47
  fileSummary.path
45
48
  );
46
49
  diagnosesByFileKey.set(fileKey, diagnosis);
47
- for (const entry of diagnosis.entries) {
48
- if (entry.catalogFindings.length > 0) {
49
- staleEntryIds.add(entry.id);
50
+ for (const entry of diagnosis.cases) {
51
+ if (entry.syncFindings.length > 0) {
52
+ staleCaseIds.add(entry.id);
50
53
  }
51
54
  }
52
55
  if (diagnosis.status === "new_regression") {
53
- newRegressionDrafts.push(buildNewRegressionDraft(fileSummary, regressionCatalog));
56
+ const draft = buildNewRegressionDraft(fileSummary, regressionCaseStore);
57
+ newRegressionDrafts.push(draft);
58
+ if (regressionCaseStore) {
59
+ const createdCase = createCaseFromDraft(draft);
60
+ if (!caseIdSet(regressionCaseStore).has(createdCase.id)) {
61
+ regressionCaseStore.cases.push(createdCase);
62
+ caseStoreChanged = true;
63
+ workflowActions.push({
64
+ type: "create_case",
65
+ caseId: createdCase.id,
66
+ reason: "new_regression",
67
+ status: "applied",
68
+ tests: [testRef(fileSummary)],
69
+ });
70
+ }
71
+ }
54
72
  }
55
73
  if (diagnosis.status === "fixed_known_regression") {
56
- for (const entry of diagnosis.entries) {
74
+ for (const entry of diagnosis.cases) {
75
+ const sourceCase = findCaseById(regressionCaseStore, entry.id);
76
+ if (sourceCase) {
77
+ const nextLifecycle = {
78
+ ...(sourceCase.lifecycle || {}),
79
+ lastVerifiedAt: new Date().toISOString(),
80
+ cleanRunCount: Number(sourceCase.lifecycle?.cleanRunCount || 0) + 1,
81
+ };
82
+ const previousState = JSON.stringify({
83
+ state: sourceCase.state,
84
+ lifecycle: sourceCase.lifecycle || {},
85
+ });
86
+ sourceCase.state = "fixed_pending_verification";
87
+ sourceCase.lifecycle = nextLifecycle;
88
+ caseStoreChanged = caseStoreChanged || previousState !== JSON.stringify({
89
+ state: sourceCase.state,
90
+ lifecycle: sourceCase.lifecycle || {},
91
+ });
92
+ workflowActions.push({
93
+ type: "mark_fixed_pending_verification",
94
+ caseId: entry.id,
95
+ issue: entry.issue,
96
+ reason: "matched_case_passed",
97
+ status: "applied",
98
+ tests: [testRef(fileSummary)],
99
+ });
100
+ }
57
101
  fixedRegressionDrafts.push({
58
102
  id: entry.id,
59
103
  issue: entry.issue,
@@ -62,6 +106,31 @@ export function applyRegressionAnalysisToArtifacts(
62
106
  });
63
107
  }
64
108
  }
109
+ if (diagnosis.status === "known_regression") {
110
+ for (const entry of diagnosis.cases) {
111
+ const sourceCase = findCaseById(regressionCaseStore, entry.id);
112
+ if (!sourceCase) continue;
113
+ const nextLifecycle = {
114
+ ...(sourceCase.lifecycle || {}),
115
+ cleanRunCount: 0,
116
+ };
117
+ if (sourceCase.state !== "active" || JSON.stringify(sourceCase.lifecycle || {}) !== JSON.stringify(nextLifecycle)) {
118
+ sourceCase.state = "active";
119
+ sourceCase.lifecycle = nextLifecycle;
120
+ caseStoreChanged = true;
121
+ }
122
+ if (entry.syncStatus === "closed_but_failing") {
123
+ workflowActions.push({
124
+ type: "reopen_issue",
125
+ caseId: entry.id,
126
+ issue: entry.issue,
127
+ reason: "closed_issue_reproduced",
128
+ status: "suggested",
129
+ tests: [testRef(fileSummary)],
130
+ });
131
+ }
132
+ }
133
+ }
65
134
  }
66
135
 
67
136
  for (const entry of [...runEntries, ...statusEntries]) {
@@ -74,12 +143,13 @@ export function applyRegressionAnalysisToArtifacts(
74
143
  const summaryTests = statusArtifact?.tests || runEntries;
75
144
  const report = buildRegressionReport(
76
145
  summaryTests,
77
- regressionCatalog,
146
+ regressionCaseStore,
78
147
  regressionSync,
79
148
  diagnosesByFileKey,
80
- staleEntryIds,
149
+ staleCaseIds,
81
150
  newRegressionDrafts,
82
- fixedRegressionDrafts
151
+ fixedRegressionDrafts,
152
+ workflowActions
83
153
  );
84
154
 
85
155
  runArtifact.regressions = report;
@@ -95,19 +165,25 @@ export function applyRegressionAnalysisToArtifacts(
95
165
  };
96
166
  }
97
167
 
98
- return { runArtifact, statusArtifact, regressionReport: report };
168
+ return {
169
+ runArtifact,
170
+ statusArtifact,
171
+ regressionReport: report,
172
+ regressionCaseStore: regressionCaseStore,
173
+ regressionCaseStoreChanged: caseStoreChanged,
174
+ };
99
175
  }
100
176
 
101
- function buildFileDiagnosis(fileSummary, regressionCatalog, syncById) {
102
- const matchedEntries = regressionCatalog
103
- ? findMatchingRegressionEntries(regressionCatalog, fileSummary)
177
+ function buildFileDiagnosis(fileSummary, regressionCaseStore, syncById) {
178
+ const matchedEntries = regressionCaseStore
179
+ ? findMatchingRegressionCases(regressionCaseStore, fileSummary)
104
180
  : [];
105
181
  const status = resolveDiagnosisStatus(fileSummary.status, matchedEntries.length);
106
182
 
107
183
  return {
108
184
  status,
109
185
  classifications: [...new Set(matchedEntries.map((entry) => entry.classification))].sort(),
110
- entries: matchedEntries.map((entry) => toDiagnosisEntry(entry, syncById.get(entry.id) || null)),
186
+ cases: matchedEntries.map((entry) => toDiagnosisEntry(entry, syncById.get(entry.id) || null)),
111
187
  };
112
188
  }
113
189
 
@@ -127,6 +203,7 @@ function resolveDiagnosisStatus(fileStatus, matchCount) {
127
203
  function toDiagnosisEntry(entry, syncEntry) {
128
204
  return {
129
205
  id: entry.id,
206
+ state: entry.state,
130
207
  classification: entry.classification,
131
208
  issue: entry.issue,
132
209
  summary: entry.summary,
@@ -134,25 +211,26 @@ function toDiagnosisEntry(entry, syncEntry) {
134
211
  lastReviewedAt: entry.lastReviewedAt,
135
212
  github: syncEntry?.github || null,
136
213
  syncStatus: syncEntry?.status || null,
137
- catalogFindings: Array.isArray(syncEntry?.findings) ? syncEntry.findings : [],
214
+ syncFindings: Array.isArray(syncEntry?.findings) ? syncEntry.findings : [],
138
215
  };
139
216
  }
140
217
 
141
218
  function buildRegressionReport(
142
219
  tests,
143
- regressionCatalog,
220
+ regressionCaseStore,
144
221
  regressionSync,
145
222
  diagnosesByFileKey,
146
- staleEntryIds,
223
+ staleCaseIds,
147
224
  newRegressionDrafts,
148
- fixedRegressionDrafts
225
+ fixedRegressionDrafts,
226
+ workflowActions = []
149
227
  ) {
150
228
  const summary = {
151
229
  newRegressions: 0,
152
230
  knownRegressions: 0,
153
231
  fixedKnownRegressions: 0,
154
- catalogStale: staleEntryIds.size,
155
- catalogSyncUnavailable: (regressionSync?.summary?.byCode?.validation_unavailable || 0) > 0,
232
+ staleCases: staleCaseIds.size,
233
+ syncUnavailable: (regressionSync?.summary?.byCode?.validation_unavailable || 0) > 0,
156
234
  usedStaleCache: (regressionSync?.summary?.byCode?.used_stale_cache || 0) > 0,
157
235
  };
158
236
 
@@ -177,10 +255,14 @@ function buildRegressionReport(
177
255
 
178
256
  return {
179
257
  summary,
180
- catalog: {
181
- configured: Boolean(regressionCatalog),
182
- entryCount: regressionCatalog?.entries?.length || 0,
183
- staleEntries: (regressionSync?.entries || []).filter((entry) =>
258
+ workflow: {
259
+ actions: workflowActions,
260
+ summary: summarizeWorkflowActions(workflowActions),
261
+ },
262
+ caseStore: {
263
+ configured: Boolean(regressionCaseStore),
264
+ caseCount: regressionCaseStore?.cases?.length || 0,
265
+ staleCases: (regressionSync?.cases || []).filter((entry) =>
184
266
  Array.isArray(entry.findings) && entry.findings.length > 0
185
267
  ),
186
268
  findings: regressionSync?.findings || [],
@@ -188,7 +270,7 @@ function buildRegressionReport(
188
270
  mode: regressionSync?.mode || null,
189
271
  checkedAt: regressionSync?.checkedAt || null,
190
272
  usedStaleCache: summary.usedStaleCache,
191
- unavailable: summary.catalogSyncUnavailable,
273
+ unavailable: summary.syncUnavailable,
192
274
  },
193
275
  },
194
276
  drafts: {
@@ -198,12 +280,12 @@ function buildRegressionReport(
198
280
  };
199
281
  }
200
282
 
201
- function buildNewRegressionDraft(fileSummary, regressionCatalog) {
283
+ function buildNewRegressionDraft(fileSummary, regressionCaseStore) {
202
284
  return {
203
285
  id: suggestRegressionId(fileSummary),
204
286
  classification: suggestRegressionClassification(fileSummary),
205
287
  issue: {
206
- repo: regressionCatalog?.issueRepo || null,
288
+ repo: regressionCaseStore?.issueRepo || null,
207
289
  number: null,
208
290
  },
209
291
  summary: suggestRegressionSummary(fileSummary),
@@ -219,6 +301,29 @@ function buildNewRegressionDraft(fileSummary, regressionCatalog) {
219
301
  };
220
302
  }
221
303
 
304
+ function createCaseFromDraft(draft) {
305
+ const now = new Date().toISOString();
306
+ return {
307
+ id: draft.id,
308
+ state: "untriaged",
309
+ classification: draft.classification,
310
+ issue: null,
311
+ summary: draft.summary,
312
+ cause: draft.cause,
313
+ lastReviewedAt: now.slice(0, 10),
314
+ fingerprints: draft.fingerprints,
315
+ lifecycle: {
316
+ firstSeenAt: now,
317
+ lastSeenAt: now,
318
+ cleanRunCount: 0,
319
+ },
320
+ coverage: {
321
+ required: true,
322
+ status: "covered_reproducing",
323
+ },
324
+ };
325
+ }
326
+
222
327
  function suggestRegressionId(fileSummary) {
223
328
  const base = `${fileSummary.service}-${fileSummary.type}-${fileSummary.path}`
224
329
  .toLowerCase()
@@ -277,6 +382,43 @@ function dedupeDraftsById(entries) {
277
382
  return [...map.values()];
278
383
  }
279
384
 
385
+ function caseIdSet(regressionCaseStore) {
386
+ return new Set((regressionCaseStore?.cases || []).map((entry) => entry.id));
387
+ }
388
+
389
+ function findCaseById(regressionCaseStore, id) {
390
+ return (regressionCaseStore?.cases || []).find((entry) => entry.id === id) || null;
391
+ }
392
+
393
+ function testRef(fileSummary) {
394
+ return {
395
+ service: fileSummary.service,
396
+ type: fileSummary.type,
397
+ path: fileSummary.path,
398
+ status: fileSummary.status,
399
+ };
400
+ }
401
+
402
+ function summarizeWorkflowActions(actions) {
403
+ const summary = {
404
+ applied: 0,
405
+ suggested: 0,
406
+ byType: {},
407
+ };
408
+ for (const action of actions) {
409
+ if (action.status === "applied") summary.applied += 1;
410
+ if (action.status === "suggested") summary.suggested += 1;
411
+ summary.byType[action.type] = (summary.byType[action.type] || 0) + 1;
412
+ }
413
+ return summary;
414
+ }
415
+
416
+ export function persistRegressionCaseStoreIfChanged(productDir, regressionCaseStore, changed) {
417
+ if (!changed || !regressionCaseStore?.__filePath) return false;
418
+ writeRegressionCaseStoreDocument(regressionCaseStore.__filePath, regressionCaseStore);
419
+ return true;
420
+ }
421
+
280
422
  function extractRunFileEntries(runArtifact) {
281
423
  const entries = [];
282
424
 
@@ -1,11 +1,15 @@
1
1
  import { buildRunArtifact, buildStatusArtifact } from "./reporting.mjs";
2
- import { applyRegressionAnalysisToArtifacts } from "./regressions.mjs";
2
+ import {
3
+ applyRegressionAnalysisToArtifacts,
4
+ persistRegressionCaseStoreIfChanged,
5
+ } from "./regressions.mjs";
3
6
  import { writeRunArtifact, writeStatusArtifact } from "./artifacts.mjs";
4
7
  import { summarizeDbBackend } from "./results.mjs";
5
8
  import { formatError } from "./formatting.mjs";
6
9
  import { uploadTelemetryArtifact } from "../telemetry/index.mjs";
7
10
  import { loadHistory, saveHistory, updateHistoryFromRunArtifact } from "../history/index.mjs";
8
11
  import { shouldFailRegressionSync, validateRegressionIssues } from "../regressions/github.mjs";
12
+ import { executeRegressionWorkflowActions } from "../regressions/workflow.mjs";
9
13
 
10
14
  export async function finalizeRunArtifacts({
11
15
  productDir,
@@ -22,8 +26,9 @@ export async function finalizeRunArtifacts({
22
26
  metadata,
23
27
  logRegistry,
24
28
  setupRegistry,
25
- regressionCatalog,
29
+ regressionCaseStore,
26
30
  regressionSyncConfig,
31
+ regressionWorkflowConfig,
27
32
  telemetry,
28
33
  reporter,
29
34
  writeStatus,
@@ -65,7 +70,7 @@ export async function finalizeRunArtifacts({
65
70
  : null;
66
71
  const regressionSync = await validateRegressionIssues({
67
72
  productDir,
68
- document: regressionCatalog,
73
+ document: regressionCaseStore,
69
74
  runArtifact,
70
75
  statusArtifact,
71
76
  config: regressionSyncConfig,
@@ -74,14 +79,39 @@ export async function finalizeRunArtifacts({
74
79
  const enrichedArtifacts = applyRegressionAnalysisToArtifacts(
75
80
  runArtifact,
76
81
  statusArtifact,
77
- regressionCatalog,
82
+ regressionCaseStore,
78
83
  regressionSync
79
84
  );
85
+ const workflowResult = await executeRegressionWorkflowActions({
86
+ productDir,
87
+ caseStore: enrichedArtifacts.regressionCaseStore,
88
+ report: enrichedArtifacts.regressionReport,
89
+ workflowConfig: regressionWorkflowConfig,
90
+ gitMetadata: metadata.git,
91
+ });
92
+ const caseStoreChanged = enrichedArtifacts.regressionCaseStoreChanged || workflowResult.changed;
93
+ enrichedArtifacts.runArtifact.regressions = enrichedArtifacts.regressionReport;
94
+ enrichedArtifacts.runArtifact.summary = {
95
+ ...(enrichedArtifacts.runArtifact.summary || {}),
96
+ regressions: enrichedArtifacts.regressionReport.summary,
97
+ };
98
+ if (enrichedArtifacts.statusArtifact) {
99
+ enrichedArtifacts.statusArtifact.regressions = enrichedArtifacts.regressionReport;
100
+ enrichedArtifacts.statusArtifact.summary = {
101
+ ...(enrichedArtifacts.statusArtifact.summary || {}),
102
+ regressions: enrichedArtifacts.regressionReport.summary,
103
+ };
104
+ }
80
105
 
81
106
  writeRunArtifact(productDir, enrichedArtifacts.runArtifact);
82
107
  if (writeStatus) {
83
108
  writeStatusArtifact(productDir, enrichedArtifacts.statusArtifact);
84
109
  }
110
+ persistRegressionCaseStoreIfChanged(
111
+ productDir,
112
+ enrichedArtifacts.regressionCaseStore,
113
+ caseStoreChanged
114
+ );
85
115
  const nextHistory = updateHistoryFromRunArtifact(
86
116
  loadHistory(productDir),
87
117
  enrichedArtifacts.runArtifact,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/next-analysis",
3
- "version": "0.1.150",
3
+ "version": "0.1.151",
4
4
  "description": "SWC-backed Next.js source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-bridge",
3
- "version": "0.1.150",
3
+ "version": "0.1.151",
4
4
  "description": "Browser bridge helpers for testkit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "typecheck": "tsc -p tsconfig.json --noEmit"
23
23
  },
24
24
  "dependencies": {
25
- "@elench/testkit-protocol": "0.1.150"
25
+ "@elench/testkit-protocol": "0.1.151"
26
26
  },
27
27
  "private": false
28
28
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-protocol",
3
- "version": "0.1.150",
3
+ "version": "0.1.151",
4
4
  "description": "Shared browser protocol for testkit bridge and extension consumers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/ts-analysis",
3
- "version": "0.1.150",
3
+ "version": "0.1.151",
4
4
  "description": "TypeScript compiler-backed source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {