@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
@@ -19,13 +19,26 @@ export function parseGitHubRepoSlug(remoteUrl) {
19
19
  return null;
20
20
  }
21
21
 
22
- export async function createDefaultGitHubIssueTransport(env = process.env) {
22
+ export async function createDefaultGitHubIssueTransport(env = process.env, options = {}) {
23
23
  const token = env.GH_TOKEN || env.GITHUB_TOKEN || null;
24
+ const apiBaseUrl = normalizeOptionalString(options.apiBaseUrl || env.TESTKIT_GITHUB_API_URL) || "https://api.github.com";
24
25
  if (token) {
25
26
  return {
26
27
  type: "token",
27
28
  async fetchRepoIssues(repo, numbers) {
28
- return fetchRepoIssuesViaToken(repo, numbers, token);
29
+ return fetchRepoIssuesViaToken(repo, numbers, token, { apiBaseUrl });
30
+ },
31
+ async fetchOpenBugIssues(repo, options = {}) {
32
+ return fetchOpenBugIssuesViaRest(repo, token, { apiBaseUrl, ...options });
33
+ },
34
+ async createIssue(repo, input) {
35
+ return createIssueViaRest(repo, input, token, { apiBaseUrl });
36
+ },
37
+ async updateIssueState(repo, number, state) {
38
+ return updateIssueStateViaRest(repo, number, state, token, { apiBaseUrl });
39
+ },
40
+ async createIssueComment(repo, number, body) {
41
+ return createIssueCommentViaRest(repo, number, body, token, { apiBaseUrl });
29
42
  },
30
43
  };
31
44
  }
@@ -41,6 +54,18 @@ export async function createDefaultGitHubIssueTransport(env = process.env) {
41
54
  async fetchRepoIssues(repo, numbers) {
42
55
  return fetchRepoIssuesViaGh(repo, numbers, env);
43
56
  },
57
+ async fetchOpenBugIssues(repo, options = {}) {
58
+ return fetchOpenBugIssuesViaGh(repo, options, env);
59
+ },
60
+ async createIssue(repo, input) {
61
+ return createIssueViaGh(repo, input, env);
62
+ },
63
+ async updateIssueState(repo, number, state) {
64
+ return updateIssueStateViaGh(repo, number, state, env);
65
+ },
66
+ async createIssueComment(repo, number, body) {
67
+ return createIssueCommentViaGh(repo, number, body, env);
68
+ },
44
69
  };
45
70
  } catch {
46
71
  return null;
@@ -65,9 +90,9 @@ export async function fetchIssuesByRepo(client, issueNumbersByRepo) {
65
90
  return issuesByRepo;
66
91
  }
67
92
 
68
- async function fetchRepoIssuesViaToken(repo, numbers, token) {
93
+ async function fetchRepoIssuesViaToken(repo, numbers, token, { apiBaseUrl = "https://api.github.com" } = {}) {
69
94
  const query = buildIssueQuery(repo, numbers);
70
- const response = await fetch("https://api.github.com/graphql", {
95
+ const response = await fetch(`${apiBaseUrl.replace(/\/+$/u, "")}/graphql`, {
71
96
  method: "POST",
72
97
  headers: {
73
98
  "Content-Type": "application/json",
@@ -90,6 +115,61 @@ async function fetchRepoIssuesViaToken(repo, numbers, token) {
90
115
  return normalizeGraphqlIssuesResponse(repo, numbers, payload?.data);
91
116
  }
92
117
 
118
+ async function fetchOpenBugIssuesViaRest(repo, token, { apiBaseUrl = "https://api.github.com", labels = ["bug"] } = {}) {
119
+ const labelQuery = labels.length > 0 ? `&labels=${encodeURIComponent(labels.join(","))}` : "";
120
+ const response = await fetch(`${apiBaseUrl.replace(/\/+$/u, "")}/repos/${repo}/issues?state=open${labelQuery}`, {
121
+ headers: githubRestHeaders(token),
122
+ });
123
+ const payload = await response.json().catch(() => null);
124
+ if (!response.ok) {
125
+ throw new Error(`GitHub issue list failed with ${response.status}${payload?.message ? `: ${payload.message}` : ""}`);
126
+ }
127
+ return normalizeRestIssues(repo, Array.isArray(payload) ? payload : []);
128
+ }
129
+
130
+ async function createIssueViaRest(repo, input, token, { apiBaseUrl = "https://api.github.com" } = {}) {
131
+ const response = await fetch(`${apiBaseUrl.replace(/\/+$/u, "")}/repos/${repo}/issues`, {
132
+ method: "POST",
133
+ headers: githubRestHeaders(token),
134
+ body: JSON.stringify({
135
+ title: input.title,
136
+ body: input.body,
137
+ labels: input.labels || [],
138
+ }),
139
+ });
140
+ const payload = await response.json().catch(() => null);
141
+ if (!response.ok) {
142
+ throw new Error(`GitHub issue create failed with ${response.status}${payload?.message ? `: ${payload.message}` : ""}`);
143
+ }
144
+ return normalizeRestIssue(repo, payload);
145
+ }
146
+
147
+ async function updateIssueStateViaRest(repo, number, state, token, { apiBaseUrl = "https://api.github.com" } = {}) {
148
+ const response = await fetch(`${apiBaseUrl.replace(/\/+$/u, "")}/repos/${repo}/issues/${number}`, {
149
+ method: "PATCH",
150
+ headers: githubRestHeaders(token),
151
+ body: JSON.stringify({ state }),
152
+ });
153
+ const payload = await response.json().catch(() => null);
154
+ if (!response.ok) {
155
+ throw new Error(`GitHub issue update failed with ${response.status}${payload?.message ? `: ${payload.message}` : ""}`);
156
+ }
157
+ return normalizeRestIssue(repo, payload);
158
+ }
159
+
160
+ async function createIssueCommentViaRest(repo, number, body, token, { apiBaseUrl = "https://api.github.com" } = {}) {
161
+ const response = await fetch(`${apiBaseUrl.replace(/\/+$/u, "")}/repos/${repo}/issues/${number}/comments`, {
162
+ method: "POST",
163
+ headers: githubRestHeaders(token),
164
+ body: JSON.stringify({ body }),
165
+ });
166
+ const payload = await response.json().catch(() => null);
167
+ if (!response.ok) {
168
+ throw new Error(`GitHub issue comment failed with ${response.status}${payload?.message ? `: ${payload.message}` : ""}`);
169
+ }
170
+ return payload;
171
+ }
172
+
93
173
  async function fetchRepoIssuesViaGh(repo, numbers, env) {
94
174
  const query = buildIssueQuery(repo, numbers);
95
175
  const { stdout } = await execFileAsync("gh", ["api", "graphql", "-f", `query=${query}`], {
@@ -104,6 +184,65 @@ async function fetchRepoIssuesViaGh(repo, numbers, env) {
104
184
  return normalizeGraphqlIssuesResponse(repo, numbers, payload?.data);
105
185
  }
106
186
 
187
+ async function fetchOpenBugIssuesViaGh(repo, { labels = ["bug"] } = {}, env) {
188
+ const args = ["issue", "list", "--repo", repo, "--state", "open", "--json", "number,title,state,url,labels"];
189
+ for (const label of labels) args.push("--label", label);
190
+ const { stdout } = await execFileAsync("gh", args, {
191
+ encoding: "utf8",
192
+ env,
193
+ maxBuffer: 1024 * 1024,
194
+ });
195
+ return normalizeRestIssues(repo, JSON.parse(stdout || "[]"));
196
+ }
197
+
198
+ async function createIssueViaGh(repo, input, env) {
199
+ const args = ["issue", "create", "--repo", repo, "--title", input.title, "--body", input.body || ""];
200
+ for (const label of input.labels || []) args.push("--label", label);
201
+ const { stdout } = await execFileAsync("gh", args, {
202
+ encoding: "utf8",
203
+ env,
204
+ maxBuffer: 1024 * 1024,
205
+ });
206
+ const url = stdout.trim();
207
+ const number = Number(url.match(/\/issues\/(\d+)$/u)?.[1]);
208
+ return {
209
+ repo,
210
+ number,
211
+ exists: Number.isInteger(number),
212
+ title: input.title,
213
+ state: "OPEN",
214
+ url,
215
+ labels: input.labels || [],
216
+ source: "github",
217
+ };
218
+ }
219
+
220
+ async function updateIssueStateViaGh(repo, number, state, env) {
221
+ await execFileAsync("gh", ["issue", state === "open" ? "reopen" : "close", String(number), "--repo", repo], {
222
+ encoding: "utf8",
223
+ env,
224
+ maxBuffer: 1024 * 1024,
225
+ });
226
+ return {
227
+ repo,
228
+ number,
229
+ exists: true,
230
+ title: null,
231
+ state: state.toUpperCase(),
232
+ url: `https://github.com/${repo}/issues/${number}`,
233
+ source: "github",
234
+ };
235
+ }
236
+
237
+ async function createIssueCommentViaGh(repo, number, body, env) {
238
+ await execFileAsync("gh", ["issue", "comment", String(number), "--repo", repo, "--body", body], {
239
+ encoding: "utf8",
240
+ env,
241
+ maxBuffer: 1024 * 1024,
242
+ });
243
+ return { ok: true };
244
+ }
245
+
107
246
  function buildIssueQuery(repo, numbers) {
108
247
  const [owner, name] = repo.split("/");
109
248
  return `
@@ -159,6 +298,41 @@ function normalizeGraphqlIssuesResponse(repo, numbers, data) {
159
298
  return map;
160
299
  }
161
300
 
301
+ function normalizeRestIssues(repo, issues) {
302
+ const map = new Map();
303
+ for (const issue of issues) {
304
+ const normalized = normalizeRestIssue(repo, issue);
305
+ if (normalized?.number) map.set(normalized.number, normalized);
306
+ }
307
+ return map;
308
+ }
309
+
310
+ function normalizeRestIssue(repo, issue) {
311
+ if (!issue || typeof issue !== "object") return null;
312
+ return {
313
+ repo,
314
+ number: Number(issue.number),
315
+ exists: true,
316
+ title: normalizeOptionalString(issue.title),
317
+ state: normalizeOptionalString(issue.state)?.toUpperCase() || null,
318
+ url: normalizeOptionalString(issue.html_url || issue.url),
319
+ labels: Array.isArray(issue.labels)
320
+ ? issue.labels.map((label) => typeof label === "string" ? label : label?.name).filter(Boolean)
321
+ : [],
322
+ checkedAt: null,
323
+ source: "github",
324
+ };
325
+ }
326
+
327
+ function githubRestHeaders(token) {
328
+ return {
329
+ "Content-Type": "application/json",
330
+ Accept: "application/vnd.github+json",
331
+ Authorization: `Bearer ${token}`,
332
+ "X-GitHub-Api-Version": "2022-11-28",
333
+ };
334
+ }
335
+
162
336
  function normalizeOptionalString(value) {
163
337
  if (typeof value !== "string") return null;
164
338
  const normalized = value.trim();
@@ -1,4 +1,4 @@
1
- import { findMatchingRegressionEntries } from "./index.mjs";
1
+ import { findMatchingRegressionCases } from "./index.mjs";
2
2
  import {
3
3
  applyStaleCacheFallback,
4
4
  loadIssueCache,
@@ -40,11 +40,13 @@ export function normalizeRegressionSyncConfig(value) {
40
40
  value.cacheTtlSeconds == null
41
41
  ? DEFAULT_CACHE_TTL_SECONDS
42
42
  : normalizePositiveInteger(value.cacheTtlSeconds, "testkit.config.ts regressions.sync.cacheTtlSeconds");
43
+ const apiBaseUrl = normalizeOptionalString(value.apiBaseUrl);
43
44
 
44
45
  return {
45
46
  provider,
46
47
  mode,
47
48
  cacheTtlSeconds,
49
+ apiBaseUrl,
48
50
  };
49
51
  }
50
52
 
@@ -62,7 +64,8 @@ export async function validateRegressionIssues({
62
64
  if (!document || !normalizedConfig || normalizedConfig.mode === "off") return null;
63
65
 
64
66
  const checks = collectObservedRegressionEntries(document, runArtifact, statusArtifact);
65
- const issueNumbersByRepo = groupIssueNumbersByRepo(document.entries);
67
+ const cases = document.cases || [];
68
+ const issueNumbersByRepo = groupIssueNumbersByRepo(cases);
66
69
  const repoSlug = gitMetadata?.repoSlug || null;
67
70
  const remoteUrl = gitMetadata?.remoteUrl || null;
68
71
  const cache = loadIssueCache(productDir, CACHE_PATH, CACHE_SCHEMA_VERSION);
@@ -71,7 +74,9 @@ export async function validateRegressionIssues({
71
74
  let issuesByRepo = cacheResolution.issuesByRepo;
72
75
 
73
76
  if (cacheResolution.missingByRepo.size > 0) {
74
- const client = transport || (await createDefaultGitHubIssueTransport());
77
+ const client = transport || (await createDefaultGitHubIssueTransport(process.env, {
78
+ apiBaseUrl: normalizedConfig.apiBaseUrl,
79
+ }));
75
80
  if (client) {
76
81
  try {
77
82
  const fetchedIssues = await fetchIssuesByRepo(client, cacheResolution.missingByRepo);
@@ -105,9 +110,11 @@ export async function validateRegressionIssues({
105
110
  }
106
111
  }
107
112
 
108
- const entries = document.entries.map((entry) => {
113
+ const caseResults = cases.map((entry) => {
109
114
  const observed = checks.get(entry.id) || createObservedCheck(entry);
110
- const issueData = issuesByRepo.get(entry.issue.repo)?.get(entry.issue.number) || null;
115
+ const issueData = entry.issue
116
+ ? issuesByRepo.get(entry.issue.repo)?.get(entry.issue.number) || null
117
+ : null;
111
118
  return buildIssueValidationEntry({ entry, observed, issueData });
112
119
  });
113
120
 
@@ -117,12 +124,12 @@ export async function validateRegressionIssues({
117
124
  code: "detected_repo_mismatch",
118
125
  severity: normalizedConfig.mode === "error" ? "error" : "warning",
119
126
  message:
120
- `Regression catalog issueRepo ${document.issueRepo} does not match detected repo ${repoSlug}` +
127
+ `Regression case store issueRepo ${document.issueRepo} does not match detected repo ${repoSlug}` +
121
128
  (remoteUrl ? ` (${remoteUrl})` : ""),
122
129
  });
123
130
  }
124
131
 
125
- const summary = buildIssueValidationSummary(entries, globalFindings);
132
+ const summary = buildIssueValidationSummary(caseResults, globalFindings);
126
133
  return {
127
134
  schemaVersion: 2,
128
135
  provider: "github",
@@ -139,7 +146,7 @@ export async function validateRegressionIssues({
139
146
  },
140
147
  findings: globalFindings,
141
148
  summary,
142
- entries,
149
+ cases: caseResults,
143
150
  };
144
151
  }
145
152
 
@@ -182,17 +189,18 @@ export function buildRegressionSyncSummaryLines(result) {
182
189
  if (parts.length === 0) return [];
183
190
  return [
184
191
  "",
185
- "Catalog issues:",
192
+ "Case store issues:",
186
193
  ...parts.map((part) => ` - ${part}`),
187
194
  ];
188
195
  }
189
196
 
190
197
  function collectObservedRegressionEntries(document, runArtifact, statusArtifact) {
191
198
  const observedTests = collectObservedTests(runArtifact, statusArtifact);
192
- const checks = new Map(document.entries.map((entry) => [entry.id, createObservedCheck(entry)]));
199
+ const cases = document.cases || [];
200
+ const checks = new Map(cases.map((entry) => [entry.id, createObservedCheck(entry)]));
193
201
 
194
202
  for (const test of observedTests) {
195
- const matchedEntries = findMatchingRegressionEntries(document, test);
203
+ const matchedEntries = findMatchingRegressionCases(document, test);
196
204
  for (const entry of matchedEntries) {
197
205
  const observed = checks.get(entry.id) || createObservedCheck(entry);
198
206
  observed.matchedTests += 1;
@@ -252,6 +260,7 @@ function createObservedCheck(entry) {
252
260
  function groupIssueNumbersByRepo(entries) {
253
261
  const map = new Map();
254
262
  for (const entry of entries) {
263
+ if (!entry.issue) continue;
255
264
  if (!map.has(entry.issue.repo)) {
256
265
  map.set(entry.issue.repo, []);
257
266
  }
@@ -271,11 +280,38 @@ function buildIssueValidationEntry({ entry, observed, issueData }) {
271
280
  const findings = [];
272
281
  const normalizedIssueState = normalizeIssueState(issueData?.state);
273
282
 
283
+ if (!entry.issue) {
284
+ return {
285
+ id: entry.id,
286
+ summary: entry.summary,
287
+ issue: null,
288
+ observed: {
289
+ matchedTests: observed.matchedTests,
290
+ executedTests: observed.executedTests,
291
+ passedTests: observed.passedTests,
292
+ failedTests: observed.failedTests,
293
+ skippedTests: observed.skippedTests,
294
+ notRunTests: observed.notRunTests,
295
+ reproduced: observed.failedTests > 0,
296
+ },
297
+ github: {
298
+ exists: null,
299
+ title: null,
300
+ state: null,
301
+ url: null,
302
+ checkedAt: null,
303
+ cached: false,
304
+ },
305
+ findings,
306
+ status: observed.matchedTests > 0 ? "unlinked" : "not_observed",
307
+ };
308
+ }
309
+
274
310
  if (issueData && issueData.exists === false) {
275
311
  findings.push({
276
312
  code: "issue_missing",
277
313
  severity: "error",
278
- message: `Regression catalog entry ${entry.id} references missing issue #${entry.issue.number} in ${entry.issue.repo}`,
314
+ message: `Regression case ${entry.id} references missing issue #${entry.issue.number} in ${entry.issue.repo}`,
279
315
  });
280
316
  }
281
317
 
@@ -369,7 +405,7 @@ function resolveIssueValidationStatus(issueData, observed) {
369
405
  return "not_observed";
370
406
  }
371
407
 
372
- function buildIssueValidationSummary(entries, globalFindings) {
408
+ function buildIssueValidationSummary(caseResults, globalFindings) {
373
409
  const byCode = {};
374
410
  const byStatus = {};
375
411
  let errors = 0;
@@ -381,7 +417,7 @@ function buildIssueValidationSummary(entries, globalFindings) {
381
417
  if (finding.severity === "warning") warnings += 1;
382
418
  }
383
419
 
384
- for (const entry of entries) {
420
+ for (const entry of caseResults) {
385
421
  byStatus[entry.status] = (byStatus[entry.status] || 0) + 1;
386
422
  for (const finding of entry.findings) {
387
423
  byCode[finding.code] = (byCode[finding.code] || 0) + 1;
@@ -391,8 +427,8 @@ function buildIssueValidationSummary(entries, globalFindings) {
391
427
  }
392
428
 
393
429
  return {
394
- entries: entries.length,
395
- observedEntries: entries.filter((entry) => entry.observed.matchedTests > 0).length,
430
+ cases: caseResults.length,
431
+ observedCases: caseResults.filter((entry) => entry.observed.matchedTests > 0).length,
396
432
  errors,
397
433
  warnings,
398
434
  byCode,
@@ -1,10 +1,20 @@
1
1
  export type RegressionClassification =
2
2
  | "expected_failure"
3
+ | "flaky"
4
+ | "harness_bug"
3
5
  | "infra"
4
6
  | "product_bug"
5
7
  | "stale_test"
6
8
  | "test_bug";
7
9
 
10
+ export type RegressionCaseState =
11
+ | "active"
12
+ | "coverage_missing"
13
+ | "fixed_pending_verification"
14
+ | "fix_claimed"
15
+ | "resolved"
16
+ | "untriaged";
17
+
8
18
  export interface RegressionIssueRef {
9
19
  repo: string;
10
20
  number: number;
@@ -19,27 +29,31 @@ export interface RegressionFingerprint {
19
29
  errorIncludes?: string;
20
30
  }
21
31
 
22
- export interface RegressionCatalogEntry {
32
+ export interface RegressionCase {
23
33
  id: string;
34
+ state: RegressionCaseState;
24
35
  classification: RegressionClassification;
25
- issue: RegressionIssueRef;
36
+ owner?: string;
37
+ issue: RegressionIssueRef | null;
26
38
  summary: string;
27
39
  cause: string;
28
40
  lastReviewedAt: string;
29
41
  fingerprints: RegressionFingerprint[];
42
+ lifecycle: Record<string, unknown>;
43
+ coverage: Record<string, unknown>;
30
44
  }
31
45
 
32
- export interface RegressionCatalogDocument {
33
- schemaVersion: 1;
46
+ export interface RegressionCaseStoreDocument {
47
+ schemaVersion: 2;
34
48
  issueRepo?: string | null;
35
- entries: RegressionCatalogEntry[];
49
+ cases: RegressionCase[];
36
50
  }
37
51
 
38
- export interface RegressionCatalogValidationResult {
52
+ export interface RegressionCaseStoreValidationResult {
39
53
  errors: string[];
40
54
  warnings: string[];
41
55
  stats: {
42
- entries: number;
56
+ cases: number;
43
57
  fingerprints: number;
44
58
  failedTests: number;
45
59
  diagnosedFailedTests: number;
@@ -51,6 +65,7 @@ export interface RegressionSyncConfig {
51
65
  provider?: "github";
52
66
  mode?: "off" | "warn" | "error";
53
67
  cacheTtlSeconds?: number;
68
+ apiBaseUrl?: string;
54
69
  }
55
70
 
56
71
  export interface RegressionSyncFinding {
@@ -62,7 +77,7 @@ export interface RegressionSyncFinding {
62
77
  export interface RegressionSyncEntry {
63
78
  id: string;
64
79
  summary: string;
65
- issue: RegressionIssueRef;
80
+ issue: RegressionIssueRef | null;
66
81
  observed: {
67
82
  matchedTests: number;
68
83
  executedTests: number;
@@ -100,41 +115,52 @@ export interface RegressionSyncResult {
100
115
  };
101
116
  findings: RegressionSyncFinding[];
102
117
  summary: {
103
- entries: number;
104
- observedEntries: number;
118
+ cases: number;
119
+ observedCases: number;
105
120
  errors: number;
106
121
  warnings: number;
107
122
  byCode: Record<string, number>;
108
123
  byStatus: Record<string, number>;
109
124
  };
110
- entries: RegressionSyncEntry[];
125
+ cases: RegressionSyncEntry[];
111
126
  }
112
127
 
113
- export declare function loadRegressionCatalogConfig(
128
+ export declare function loadRegressionCaseStoreConfig(
114
129
  productDir: string,
115
130
  config: { file?: string | null } | null
116
- ): RegressionCatalogDocument | null;
117
- export declare function loadRegressionCatalogDocument(
131
+ ): RegressionCaseStoreDocument | null;
132
+ export declare function loadRegressionCaseStoreDocument(
118
133
  filePath: string,
119
134
  relativePath?: string
120
- ): RegressionCatalogDocument;
121
- export declare function normalizeRegressionCatalogDocument(
135
+ ): RegressionCaseStoreDocument;
136
+ export declare function writeRegressionCaseStoreDocument(
137
+ filePath: string,
138
+ document: RegressionCaseStoreDocument
139
+ ): void;
140
+ export declare function serializeRegressionCaseStore(
141
+ document: RegressionCaseStoreDocument
142
+ ): {
143
+ schemaVersion: 2;
144
+ issueRepo?: string | null;
145
+ cases: RegressionCase[];
146
+ };
147
+ export declare function normalizeRegressionCaseStoreDocument(
122
148
  document: unknown,
123
149
  relativePath?: string
124
- ): RegressionCatalogDocument;
125
- export declare function validateRegressionCatalogDocument(
126
- document: RegressionCatalogDocument,
150
+ ): RegressionCaseStoreDocument;
151
+ export declare function validateRegressionCaseStoreDocument(
152
+ document: RegressionCaseStoreDocument,
127
153
  options?: {
128
154
  productDir?: string;
129
155
  statusArtifactPath?: string;
130
156
  statusArtifact?: unknown;
131
157
  }
132
- ): RegressionCatalogValidationResult;
133
- export declare function renderRegressionCatalogMarkdown(
134
- document: RegressionCatalogDocument
158
+ ): RegressionCaseStoreValidationResult;
159
+ export declare function renderRegressionCaseStoreMarkdown(
160
+ document: RegressionCaseStoreDocument
135
161
  ): string;
136
- export declare function findMatchingRegressionEntries(
137
- document: RegressionCatalogDocument,
162
+ export declare function findMatchingRegressionCases(
163
+ document: RegressionCaseStoreDocument,
138
164
  fileSummary: {
139
165
  service?: string;
140
166
  type?: string;
@@ -143,9 +169,9 @@ export declare function findMatchingRegressionEntries(
143
169
  error?: string | null;
144
170
  failureDetails?: Array<{ key?: string; title?: string }>;
145
171
  }
146
- ): RegressionCatalogEntry[];
147
- export declare function matchesRegressionEntry(
148
- entry: RegressionCatalogEntry,
172
+ ): RegressionCase[];
173
+ export declare function matchesRegressionCase(
174
+ entry: RegressionCase,
149
175
  fileSummary: {
150
176
  service?: string;
151
177
  type?: string;
@@ -159,6 +185,7 @@ export declare function buildRegressionFileIdentity(
159
185
  type: string,
160
186
  filePath: string
161
187
  ): string;
188
+ export declare function issueRefToString(issue: RegressionIssueRef | null): string | null;
162
189
 
163
190
  export declare function normalizeRegressionSyncConfig(
164
191
  value: unknown
@@ -166,10 +193,11 @@ export declare function normalizeRegressionSyncConfig(
166
193
  provider: "github";
167
194
  mode: "off" | "warn" | "error";
168
195
  cacheTtlSeconds: number;
196
+ apiBaseUrl: string | null;
169
197
  } | null;
170
198
  export declare function validateRegressionIssues(options: {
171
199
  productDir: string;
172
- document: RegressionCatalogDocument | null;
200
+ document: RegressionCaseStoreDocument | null;
173
201
  runArtifact?: unknown;
174
202
  statusArtifact?: unknown;
175
203
  config?: RegressionSyncConfig | null;