@mseep/dembrandt 0.19.5
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.
- package/LICENSE +21 -0
- package/README.md +408 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +532 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/browser.d.ts +16 -0
- package/dist/lib/browser.js +27 -0
- package/dist/lib/browser.js.map +1 -0
- package/dist/lib/colors.d.ts +101 -0
- package/dist/lib/colors.js +405 -0
- package/dist/lib/colors.js.map +1 -0
- package/dist/lib/compare.d.ts +31 -0
- package/dist/lib/compare.js +46 -0
- package/dist/lib/compare.js.map +1 -0
- package/dist/lib/discovery.d.ts +31 -0
- package/dist/lib/discovery.js +243 -0
- package/dist/lib/discovery.js.map +1 -0
- package/dist/lib/drift.d.ts +64 -0
- package/dist/lib/drift.js +383 -0
- package/dist/lib/drift.js.map +1 -0
- package/dist/lib/dtcg/validate.d.ts +51 -0
- package/dist/lib/dtcg/validate.js +1403 -0
- package/dist/lib/dtcg/validate.js.map +1 -0
- package/dist/lib/exit-codes.d.ts +29 -0
- package/dist/lib/exit-codes.js +26 -0
- package/dist/lib/exit-codes.js.map +1 -0
- package/dist/lib/extractors/breakpoints.d.ts +5 -0
- package/dist/lib/extractors/breakpoints.js +450 -0
- package/dist/lib/extractors/breakpoints.js.map +1 -0
- package/dist/lib/extractors/colors.d.ts +2 -0
- package/dist/lib/extractors/colors.js +657 -0
- package/dist/lib/extractors/colors.js.map +1 -0
- package/dist/lib/extractors/components.d.ts +4 -0
- package/dist/lib/extractors/components.js +370 -0
- package/dist/lib/extractors/components.js.map +1 -0
- package/dist/lib/extractors/index.d.ts +9 -0
- package/dist/lib/extractors/index.js +1257 -0
- package/dist/lib/extractors/index.js.map +1 -0
- package/dist/lib/extractors/logo.d.ts +2 -0
- package/dist/lib/extractors/logo.js +626 -0
- package/dist/lib/extractors/logo.js.map +1 -0
- package/dist/lib/extractors/spacing.d.ts +4 -0
- package/dist/lib/extractors/spacing.js +163 -0
- package/dist/lib/extractors/spacing.js.map +1 -0
- package/dist/lib/extractors/teach.d.ts +1 -0
- package/dist/lib/extractors/teach.js +66 -0
- package/dist/lib/extractors/teach.js.map +1 -0
- package/dist/lib/extractors/typography.d.ts +1 -0
- package/dist/lib/extractors/typography.js +163 -0
- package/dist/lib/extractors/typography.js.map +1 -0
- package/dist/lib/findings.d.ts +34 -0
- package/dist/lib/findings.js +166 -0
- package/dist/lib/findings.js.map +1 -0
- package/dist/lib/formatters/dtcg.d.ts +10 -0
- package/dist/lib/formatters/dtcg.js +416 -0
- package/dist/lib/formatters/dtcg.js.map +1 -0
- package/dist/lib/formatters/html.d.ts +25 -0
- package/dist/lib/formatters/html.js +479 -0
- package/dist/lib/formatters/html.js.map +1 -0
- package/dist/lib/formatters/markdown.d.ts +5 -0
- package/dist/lib/formatters/markdown.js +568 -0
- package/dist/lib/formatters/markdown.js.map +1 -0
- package/dist/lib/formatters/pdf.d.ts +12 -0
- package/dist/lib/formatters/pdf.js +1121 -0
- package/dist/lib/formatters/pdf.js.map +1 -0
- package/dist/lib/formatters/terminal.d.ts +6 -0
- package/dist/lib/formatters/terminal.js +954 -0
- package/dist/lib/formatters/terminal.js.map +1 -0
- package/dist/lib/formatters/theme.d.ts +35 -0
- package/dist/lib/formatters/theme.js +37 -0
- package/dist/lib/formatters/theme.js.map +1 -0
- package/dist/lib/merger.d.ts +14 -0
- package/dist/lib/merger.js +362 -0
- package/dist/lib/merger.js.map +1 -0
- package/dist/lib/normalize.d.ts +29 -0
- package/dist/lib/normalize.js +59 -0
- package/dist/lib/normalize.js.map +1 -0
- package/dist/lib/robots.d.ts +12 -0
- package/dist/lib/robots.js +110 -0
- package/dist/lib/robots.js.map +1 -0
- package/dist/lib/run-summary.d.ts +40 -0
- package/dist/lib/run-summary.js +64 -0
- package/dist/lib/run-summary.js.map +1 -0
- package/dist/lib/types.d.ts +329 -0
- package/dist/lib/types.js +7 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/version.d.ts +134 -0
- package/dist/lib/version.js +153 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/mcp-server.d.ts +11 -0
- package/dist/mcp-server.js +311 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/package.json +106 -0
- package/dist/test/_vitest-shim.d.ts +13 -0
- package/dist/test/_vitest-shim.js +23 -0
- package/dist/test/_vitest-shim.js.map +1 -0
- package/dist/test/cli.test.d.ts +1 -0
- package/dist/test/cli.test.js +24 -0
- package/dist/test/cli.test.js.map +1 -0
- package/dist/test/colors.test.d.ts +1 -0
- package/dist/test/colors.test.js +64 -0
- package/dist/test/colors.test.js.map +1 -0
- package/dist/test/compare.test.d.ts +1 -0
- package/dist/test/compare.test.js +57 -0
- package/dist/test/compare.test.js.map +1 -0
- package/dist/test/drift.test.d.ts +1 -0
- package/dist/test/drift.test.js +53 -0
- package/dist/test/drift.test.js.map +1 -0
- package/dist/test/dtcg-formatter.test.d.ts +1 -0
- package/dist/test/dtcg-formatter.test.js +48 -0
- package/dist/test/dtcg-formatter.test.js.map +1 -0
- package/dist/test/dtcg-validate.test.d.ts +1 -0
- package/dist/test/dtcg-validate.test.js +2129 -0
- package/dist/test/dtcg-validate.test.js.map +1 -0
- package/dist/test/exit-codes.test.d.ts +1 -0
- package/dist/test/exit-codes.test.js +53 -0
- package/dist/test/exit-codes.test.js.map +1 -0
- package/dist/test/findings.test.d.ts +1 -0
- package/dist/test/findings.test.js +77 -0
- package/dist/test/findings.test.js.map +1 -0
- package/dist/test/html.test.d.ts +1 -0
- package/dist/test/html.test.js +95 -0
- package/dist/test/html.test.js.map +1 -0
- package/dist/test/markdown.test.d.ts +1 -0
- package/dist/test/markdown.test.js +145 -0
- package/dist/test/markdown.test.js.map +1 -0
- package/dist/test/merger.test.d.ts +1 -0
- package/dist/test/merger.test.js +98 -0
- package/dist/test/merger.test.js.map +1 -0
- package/dist/test/normalize.test.d.ts +1 -0
- package/dist/test/normalize.test.js +47 -0
- package/dist/test/normalize.test.js.map +1 -0
- package/dist/test/run-summary.test.d.ts +1 -0
- package/dist/test/run-summary.test.js +45 -0
- package/dist/test/run-summary.test.js.map +1 -0
- package/dist/test/version.test.d.ts +1 -0
- package/dist/test/version.test.js +73 -0
- package/dist/test/version.test.js.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Discovery
|
|
3
|
+
*
|
|
4
|
+
* Discovers internal pages from a starting URL via DOM link extraction
|
|
5
|
+
* or sitemap.xml parsing.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Score a URL path for importance (higher = more important).
|
|
9
|
+
* Prioritizes shallow, navigational paths over blog posts / legal pages.
|
|
10
|
+
*/
|
|
11
|
+
export function scoreUrl(pathname) {
|
|
12
|
+
const lc = pathname.toLowerCase();
|
|
13
|
+
// Exclude noise
|
|
14
|
+
const exclude = [
|
|
15
|
+
/\/(login|signin|sign-in|signup|sign-up|register|auth)/,
|
|
16
|
+
/\/(terms|privacy|legal|cookie|gdpr|tos)/,
|
|
17
|
+
/\/(cdn-cgi|wp-admin|wp-content|wp-json)/,
|
|
18
|
+
/\.(pdf|jpg|jpeg|png|gif|svg|webp|zip|tar|gz|mp4|mp3)$/,
|
|
19
|
+
/\/tag\//,
|
|
20
|
+
/\/author\//,
|
|
21
|
+
/\/page\/\d+/,
|
|
22
|
+
/\?/, // query strings (shouldn't appear here but safety)
|
|
23
|
+
];
|
|
24
|
+
if (exclude.some(r => r.test(lc)))
|
|
25
|
+
return -1;
|
|
26
|
+
let score = 100;
|
|
27
|
+
// Prefer shallow paths
|
|
28
|
+
const depth = (pathname.match(/\//g) || []).length;
|
|
29
|
+
score -= depth * 10;
|
|
30
|
+
// Boost key navigational pages
|
|
31
|
+
const boosts = [
|
|
32
|
+
[/^\/(pricing|plans?|cost)/, 30],
|
|
33
|
+
[/^\/(about|company|team|story)/, 25],
|
|
34
|
+
[/^\/(product|features?|solutions?)/, 25],
|
|
35
|
+
[/^\/(enterprise|business|platform)/, 20],
|
|
36
|
+
[/^\/(contact|demo|trial|start)/, 20],
|
|
37
|
+
[/^\/(docs|documentation|developers?|api)/, 15],
|
|
38
|
+
[/^\/(blog|resources|insights|news)/, 10],
|
|
39
|
+
];
|
|
40
|
+
for (const [re, boost] of boosts) {
|
|
41
|
+
if (re.test(lc)) {
|
|
42
|
+
score += boost;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return score;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Discover internal links from an already-loaded Playwright page.
|
|
50
|
+
* Call after extractBranding() has loaded and scrolled the page.
|
|
51
|
+
*
|
|
52
|
+
* @param {import('playwright-core').Page} page
|
|
53
|
+
* @param {string} baseUrl - The starting URL (used to determine same-origin)
|
|
54
|
+
* @param {number} maxPages - Maximum number of URLs to return
|
|
55
|
+
* @returns {Promise<string[]>} Ordered list of URLs to crawl (excluding homepage)
|
|
56
|
+
*/
|
|
57
|
+
export async function discoverLinks(page, baseUrl, maxPages) {
|
|
58
|
+
const origin = new URL(baseUrl).origin;
|
|
59
|
+
const links = await page.evaluate((origin) => {
|
|
60
|
+
const results = [];
|
|
61
|
+
const navSelectors = [
|
|
62
|
+
'nav a', 'header a', 'footer a',
|
|
63
|
+
'[role="navigation"] a', '[aria-label*="nav"] a',
|
|
64
|
+
'[data-nav] a', '.nav a', '.navbar a', '.header a', '.menu a',
|
|
65
|
+
];
|
|
66
|
+
// Score links by location: nav/header/footer = 2, elsewhere = 1
|
|
67
|
+
const scored = new Map(); // pathname → { href, locationScore }
|
|
68
|
+
function addLinks(selector, locationScore) {
|
|
69
|
+
document.querySelectorAll(selector).forEach(a => {
|
|
70
|
+
try {
|
|
71
|
+
const url = new URL(a.href);
|
|
72
|
+
if (url.origin !== origin)
|
|
73
|
+
return;
|
|
74
|
+
const pathname = url.pathname.replace(/\/$/, '') || '/';
|
|
75
|
+
if (!scored.has(pathname) || scored.get(pathname).locationScore < locationScore) {
|
|
76
|
+
scored.set(pathname, { href: url.origin + pathname, locationScore });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
navSelectors.forEach(sel => addLinks(sel, 2));
|
|
83
|
+
addLinks('a[href]', 1);
|
|
84
|
+
scored.forEach(({ href, locationScore }, pathname) => {
|
|
85
|
+
results.push({ href, pathname, locationScore });
|
|
86
|
+
});
|
|
87
|
+
return results;
|
|
88
|
+
}, origin);
|
|
89
|
+
// Remove homepage itself
|
|
90
|
+
const homepagePath = new URL(baseUrl).pathname.replace(/\/$/, '') || '/';
|
|
91
|
+
const scored = links
|
|
92
|
+
.filter(l => l.pathname !== homepagePath)
|
|
93
|
+
.map(l => ({ ...l, score: scoreUrl(l.pathname) + (l.locationScore * 5) }))
|
|
94
|
+
.filter(l => l.score >= 0)
|
|
95
|
+
.sort((a, b) => b.score - a.score);
|
|
96
|
+
return scored.slice(0, maxPages).map(l => l.href);
|
|
97
|
+
}
|
|
98
|
+
const FETCH_OPTS = {
|
|
99
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; Dembrandt/1.0)' },
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Discover sitemap URLs by checking robots.txt first, then common paths.
|
|
103
|
+
* Returns the first sitemap XML that responds successfully.
|
|
104
|
+
*/
|
|
105
|
+
async function findSitemapUrls(origin) {
|
|
106
|
+
// 1. Check robots.txt for Sitemap: directives
|
|
107
|
+
try {
|
|
108
|
+
const res = await fetch(`${origin}/robots.txt`, {
|
|
109
|
+
...FETCH_OPTS,
|
|
110
|
+
signal: AbortSignal.timeout(5000),
|
|
111
|
+
});
|
|
112
|
+
if (res.ok) {
|
|
113
|
+
const text = await res.text();
|
|
114
|
+
const sitemaps = [...text.matchAll(/^Sitemap:\s*(.+)$/gmi)]
|
|
115
|
+
.map(m => m[1].trim())
|
|
116
|
+
.filter(u => u.startsWith('http'));
|
|
117
|
+
if (sitemaps.length > 0)
|
|
118
|
+
return sitemaps;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
122
|
+
// 2. Fall back to common sitemap locations
|
|
123
|
+
return [
|
|
124
|
+
`${origin}/sitemap.xml`,
|
|
125
|
+
`${origin}/sitemap_index.xml`,
|
|
126
|
+
`${origin}/sitemap/sitemap-index.xml`,
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Fetch a single sitemap URL, returning its XML text or empty string.
|
|
131
|
+
*/
|
|
132
|
+
async function fetchSitemap(url) {
|
|
133
|
+
try {
|
|
134
|
+
const res = await fetch(url, {
|
|
135
|
+
...FETCH_OPTS,
|
|
136
|
+
signal: AbortSignal.timeout(10000),
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok)
|
|
139
|
+
return '';
|
|
140
|
+
const text = await res.text();
|
|
141
|
+
return text.includes('<') ? text : ''; // sanity check for XML
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Discover pages from a site's sitemap.xml.
|
|
149
|
+
* Checks robots.txt for Sitemap directives, then tries common paths.
|
|
150
|
+
* Follows sitemapindex references one level deep.
|
|
151
|
+
*
|
|
152
|
+
* @param {string} baseUrl - The starting URL (should be post-redirect)
|
|
153
|
+
* @param {number} maxPages - Maximum number of URLs to return
|
|
154
|
+
* @returns {Promise<string[]>} List of URLs from sitemap (excluding homepage)
|
|
155
|
+
*/
|
|
156
|
+
export async function parseSitemap(baseUrl, maxPages) {
|
|
157
|
+
const base = new URL(baseUrl);
|
|
158
|
+
// Find and fetch sitemap(s)
|
|
159
|
+
const candidates = await findSitemapUrls(base.origin);
|
|
160
|
+
let xml = '';
|
|
161
|
+
for (const candidate of candidates) {
|
|
162
|
+
xml = await fetchSitemap(candidate);
|
|
163
|
+
if (xml)
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
if (!xml)
|
|
167
|
+
return [];
|
|
168
|
+
// Also accept www/non-www variants of the same domain
|
|
169
|
+
const acceptedHostnames = new Set([base.hostname]);
|
|
170
|
+
if (base.hostname.startsWith('www.')) {
|
|
171
|
+
acceptedHostnames.add(base.hostname.slice(4));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
acceptedHostnames.add('www.' + base.hostname);
|
|
175
|
+
}
|
|
176
|
+
// Expand sitemapindex references up to two levels deep
|
|
177
|
+
let allXml = xml;
|
|
178
|
+
if (xml.includes('<sitemapindex')) {
|
|
179
|
+
const extractLocs = (text) => [...text.matchAll(/<loc>\s*(https?:\/\/[^<]+)\s*<\/loc>/g)]
|
|
180
|
+
.map(m => m[1].trim())
|
|
181
|
+
.filter(u => { try {
|
|
182
|
+
return acceptedHostnames.has(new URL(u).hostname);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
} });
|
|
187
|
+
const childUrls = extractLocs(xml).slice(0, 10);
|
|
188
|
+
const childXmls = await Promise.all(childUrls.map(u => fetchSitemap(u)));
|
|
189
|
+
// Check if children are also sitemapindex (e.g. Spotify's 3-level hierarchy)
|
|
190
|
+
const grandchildFetches = [];
|
|
191
|
+
const pageXmls = [];
|
|
192
|
+
for (const childXml of childXmls) {
|
|
193
|
+
if (!childXml)
|
|
194
|
+
continue;
|
|
195
|
+
if (childXml.includes('<sitemapindex')) {
|
|
196
|
+
const gcUrls = extractLocs(childXml).slice(0, 3);
|
|
197
|
+
grandchildFetches.push(...gcUrls.map(u => fetchSitemap(u)));
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
pageXmls.push(childXml);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (grandchildFetches.length > 0) {
|
|
204
|
+
const gcXmls = await Promise.all(grandchildFetches);
|
|
205
|
+
pageXmls.push(...gcXmls);
|
|
206
|
+
}
|
|
207
|
+
allXml = pageXmls.join('\n');
|
|
208
|
+
}
|
|
209
|
+
const homepagePath = base.pathname.replace(/\/$/, '') || '/';
|
|
210
|
+
const urls = [...allXml.matchAll(/<loc>\s*(https?:\/\/[^<]+)\s*<\/loc>/g)]
|
|
211
|
+
.map(m => m[1].trim())
|
|
212
|
+
.filter(u => {
|
|
213
|
+
try {
|
|
214
|
+
return acceptedHostnames.has(new URL(u).hostname);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
.map(u => {
|
|
221
|
+
const p = new URL(u);
|
|
222
|
+
return p.origin + (p.pathname.replace(/\/$/, '') || '/');
|
|
223
|
+
});
|
|
224
|
+
// Deduplicate
|
|
225
|
+
const seen = new Set();
|
|
226
|
+
const deduped = [];
|
|
227
|
+
for (const u of urls) {
|
|
228
|
+
const path = new URL(u).pathname;
|
|
229
|
+
if (path === homepagePath)
|
|
230
|
+
continue;
|
|
231
|
+
if (seen.has(u))
|
|
232
|
+
continue;
|
|
233
|
+
seen.add(u);
|
|
234
|
+
deduped.push(u);
|
|
235
|
+
}
|
|
236
|
+
// Score and sort
|
|
237
|
+
const scored = deduped
|
|
238
|
+
.map(u => ({ href: u, score: scoreUrl(new URL(u).pathname) }))
|
|
239
|
+
.filter(l => l.score >= 0)
|
|
240
|
+
.sort((a, b) => b.score - a.score);
|
|
241
|
+
return scored.slice(0, maxPages).map(l => l.href);
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../lib/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drift engine — the product core.
|
|
3
|
+
*
|
|
4
|
+
* Compares two Dembrandt native extracts (a baseline and a candidate) and
|
|
5
|
+
* returns a drift report: a 0-100 score (0 = identical), a pass/fail verdict,
|
|
6
|
+
* and a list of what changed. Pure functions, no dependencies, no infra.
|
|
7
|
+
*
|
|
8
|
+
* Baseline-agnostic by design: it does not know or care whether the baseline is
|
|
9
|
+
* a rolling `main` snapshot or a pinned `ds-vN` release. The caller decides what
|
|
10
|
+
* to compare against. Two-baseline workflows just call this twice.
|
|
11
|
+
*/
|
|
12
|
+
import type { BrandingResult as ExtractionResult } from "./types.js";
|
|
13
|
+
export interface DriftConfig {
|
|
14
|
+
/** ΔE at or below this: colors treated as identical. */
|
|
15
|
+
colorSame: number;
|
|
16
|
+
/** ΔE at or below this (and above colorSame): a color "shifted". Beyond: removed/added. */
|
|
17
|
+
colorShift: number;
|
|
18
|
+
/** Percent change at or below this: a dimension is unchanged. */
|
|
19
|
+
dimPct: number;
|
|
20
|
+
/** Percent change at or below this (and above dimPct): "shifted". Beyond: removed/added. */
|
|
21
|
+
dimShiftPct: number;
|
|
22
|
+
/** Relative weight of each category in the overall score. */
|
|
23
|
+
weights: {
|
|
24
|
+
color: number;
|
|
25
|
+
typography: number;
|
|
26
|
+
spacing: number;
|
|
27
|
+
radius: number;
|
|
28
|
+
shadow: number;
|
|
29
|
+
};
|
|
30
|
+
/** score > failThreshold => fail. */
|
|
31
|
+
failThreshold: number;
|
|
32
|
+
}
|
|
33
|
+
export declare const DEFAULT_DRIFT_CONFIG: DriftConfig;
|
|
34
|
+
export type DriftKind = "changed" | "added" | "removed";
|
|
35
|
+
export type DriftCategory = "color" | "typography" | "spacing" | "radius" | "shadow";
|
|
36
|
+
export interface DriftChange {
|
|
37
|
+
category: DriftCategory;
|
|
38
|
+
kind: DriftKind;
|
|
39
|
+
label: string;
|
|
40
|
+
before?: string;
|
|
41
|
+
after?: string;
|
|
42
|
+
/** ΔE for colors, percent change for dimensions. */
|
|
43
|
+
delta?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface CategoryResult {
|
|
46
|
+
category: DriftCategory;
|
|
47
|
+
score: number;
|
|
48
|
+
changed: number;
|
|
49
|
+
added: number;
|
|
50
|
+
removed: number;
|
|
51
|
+
}
|
|
52
|
+
export interface DriftReport {
|
|
53
|
+
score: number;
|
|
54
|
+
status: "stable" | "drift";
|
|
55
|
+
threshold: number;
|
|
56
|
+
summary: {
|
|
57
|
+
changed: number;
|
|
58
|
+
added: number;
|
|
59
|
+
removed: number;
|
|
60
|
+
};
|
|
61
|
+
categories: CategoryResult[];
|
|
62
|
+
changes: DriftChange[];
|
|
63
|
+
}
|
|
64
|
+
export declare function computeDrift(baseline: ExtractionResult, candidate: ExtractionResult, config?: Partial<DriftConfig>): DriftReport;
|