chore: update vendored skills and record provenance

humanizer 2.2.0 -> 2.9.1 (blader/humanizer, MIT): adds a Voice
Calibration section and a passive-voice pattern, reworks negative
parallelisms and em dashes. Version moved to metadata.version upstream.

impeccable 3.6.0 -> 4.0.4 (pbakaus/impeccable, Apache-2.0): the repo
tags the skill and the npm CLI separately, so 3.6.0 was a real release
and npm's 3.5.0 was never the comparison. Adds native-platform reference
briefs.

Each now carries an UPSTREAM file; bin/check-vendored.sh reports drift.
This commit is contained in:
naps62
2026-08-01 14:25:26 +00:00
parent 2f7f512cec
commit 9023ed3ef9
144 changed files with 28302 additions and 5511 deletions
@@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { profileStep, recordProfileEvent } from '../../profile/profiler.mjs';
import { parseAnyColor, resolveLengthPx, resolveVarRefs } from '../../rules/checks.mjs';
import { CSS_NAMED_COLORS, collectCssCustomProps, cssLengthToPx, parseAnyColor, resolveLengthPx, resolveVarRefs } from '../../rules/checks.mjs';
// ---------------------------------------------------------------------------
// jsdom CSS-variable border override map
@@ -223,7 +223,7 @@ function unwrapCssAtLayer(source) {
// ---------------------------------------------------------------------------
const STATIC_INHERITED_PROPS = new Set([
'color', 'fontFamily', 'fontSize', 'fontStyle', 'fontWeight',
'color', 'fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'fontVariant',
'lineHeight', 'letterSpacing', 'textTransform', 'textAlign', 'hyphens',
'webkitHyphens',
]);
@@ -245,9 +245,14 @@ const STATIC_DEFAULT_STYLE = {
outlineColor: 'rgb(0, 0, 0)',
outlineStyle: 'none',
boxShadow: 'none',
// NOT in STATIC_INHERITED_PROPS even though text-shadow inherits in real
// CSS: the glow check only needs to fire once, on the element that
// declares the shadow, not on every descendant.
textShadow: 'none',
fontFamily: '',
fontSize: '16px',
fontStyle: 'normal',
fontVariant: 'normal',
fontWeight: '400',
lineHeight: 'normal',
letterSpacing: 'normal',
@@ -272,6 +277,7 @@ const STATIC_DEFAULT_STYLE = {
marginBottom: '0px',
marginLeft: '0px',
position: 'static',
visibility: 'visible',
top: 'auto',
right: 'auto',
bottom: 'auto',
@@ -301,6 +307,7 @@ const STATIC_PROP_MAP = {
'outline-color': 'outlineColor',
'outline-style': 'outlineStyle',
'box-shadow': 'boxShadow',
'text-shadow': 'textShadow',
'font-family': 'fontFamily',
'font-size': 'fontSize',
'font-style': 'fontStyle',
@@ -326,6 +333,7 @@ const STATIC_PROP_MAP = {
'margin-bottom': 'marginBottom',
'margin-left': 'marginLeft',
'position': 'position',
'visibility': 'visibility',
'top': 'top',
'right': 'right',
'bottom': 'bottom',
@@ -337,18 +345,29 @@ const STATIC_PROP_MAP = {
'overflow-y': 'overflowY',
};
// parseStaticColor tries parseAnyColor first, which already resolves every
// name in the shared CSS_NAMED_COLORS table. This fallback only carries the
// keywords parseAnyColor deliberately returns null for: the cascade needs
// `transparent` to read as an actual zero-alpha color.
const STATIC_NAMED_COLORS = {
black: { r: 0, g: 0, b: 0, a: 1 },
white: { r: 255, g: 255, b: 255, a: 1 },
transparent: { r: 0, g: 0, b: 0, a: 0 },
gray: { r: 128, g: 128, b: 128, a: 1 },
grey: { r: 128, g: 128, b: 128, a: 1 },
silver: { r: 192, g: 192, b: 192, a: 1 },
red: { r: 255, g: 0, b: 0, a: 1 },
green: { r: 0, g: 128, b: 0, a: 1 },
blue: { r: 0, g: 0, b: 255, a: 1 },
};
// Named-color alternation for plucking a color token out of shorthand values
// (issue #359: a hardcoded 9-name list here silently dropped `purple`,
// `crimson`, `teal`, ... from border shorthands, so the side defaulted to
// neutral black and side-tab never fired on .html files). Derived from the
// same table parseAnyColor resolves against, so extraction and parsing can't
// drift apart. Longest-first so names containing other names as substrings
// (rebeccapurple) are matched whole.
const NAMED_COLOR_TOKENS = [...Object.keys(CSS_NAMED_COLORS), ...Object.keys(STATIC_NAMED_COLORS)]
.sort((a, b) => b.length - a.length)
.join('|');
const STATIC_COLOR_TOKEN_RE = new RegExp(
`(?:rgba?\\([^)]+\\)|oklch\\([^)]+\\)|oklab\\([^)]+\\)|lch\\([^)]+\\)|lab\\([^)]+\\)|hsla?\\([^)]+\\)|hwb\\([^)]+\\)|#[0-9a-f]{3,8}\\b|\\b(?:${NAMED_COLOR_TOKENS})\\b)`,
'i'
);
function splitCssList(value) {
const parts = [];
let depth = 0, quote = '', start = 0;
@@ -418,7 +437,23 @@ function extractStaticColor(value) {
if (!value) return '';
const raw = String(value).trim();
if (/^var\(/i.test(raw)) return raw;
const colorLike = raw.match(/(?:rgba?\([^)]+\)|oklch\([^)]+\)|oklab\([^)]+\)|lch\([^)]+\)|lab\([^)]+\)|hsla?\([^)]+\)|hwb\([^)]+\)|#[0-9a-f]{3,8}\b|\b(?:black|white|gray|grey|silver|red|green|blue|transparent)\b)/i);
// color-mix(...) needs balanced-paren capture (its arguments regularly
// contain nested var()/oklch() calls AND the keyword `transparent`, which
// the flat regex below would otherwise pluck out of the middle of the
// expression and report as the whole color).
const mixStart = raw.search(/color-mix\(/i);
if (mixStart !== -1) {
let depth = 0;
for (let i = raw.indexOf('(', mixStart); i < raw.length; i++) {
if (raw[i] === '(') depth++;
else if (raw[i] === ')') {
depth--;
if (depth === 0) return raw.slice(mixStart, i + 1);
}
}
return '';
}
const colorLike = raw.match(STATIC_COLOR_TOKEN_RE);
if (!colorLike) return '';
return colorLike[0];
}
@@ -530,6 +565,15 @@ function expandStaticDeclaration(prop, value) {
const beforeImage = hasImage ? v.split(/(?:repeating-)?(?:linear|radial|conic)-gradient\(|url\(/i)[0] : v;
const color = extractStaticColor(hasImage ? beforeImage : v);
if (color) out.push(['backgroundColor', color]);
// The `background` shorthand resets every longhand it does not set.
// Without this, `pre code { background: none }` leaves an earlier
// `background: var(--surface)` color standing and the contrast checks
// measure text against a surface the browser never paints. var() values
// stay untouched: they may resolve to a color later in the pipeline.
if (!color && !hasImage && !/var\(/i.test(v)) {
out.push(['backgroundColor', 'rgba(0, 0, 0, 0)']);
out.push(['backgroundImage', 'none']);
}
return out;
}
if (p === 'border') {
@@ -700,7 +744,20 @@ function collectStaticCssRules(cssText, csstree) {
});
});
for (const selector of splitCssList(selectorText)) {
if (selector) rules.push({ selector, declarations, specificity: staticSpecificity(selector), order: order++ });
if (!selector) continue;
// :hover rules can't be matched statically as-is (no interaction
// state), but they carry real cascade weight while hovered. Tag
// them and record a state-stripped selector so the hover pass can
// find their targets; specificity stays computed from the ORIGINAL
// selector (per CSS, :hover counts as a class).
const isHover = /:hover\b/i.test(selector);
let matchSelector = null;
if (isHover) {
matchSelector = selector.replace(/:hover\b/gi, '').trim();
if (!matchSelector || /[>+~]\s*$/.test(matchSelector)) matchSelector = null;
else matchSelector = matchSelector.replace(/(^|[\s>+~])(?=$|[\s>+~])/g, '$1*');
}
rules.push({ selector, declarations, specificity: staticSpecificity(selector), order: order++, isHover, matchSelector });
}
return;
}
@@ -803,6 +860,13 @@ class StaticDocument {
this.domutils = modules.domutils;
this._wrappers = new WeakMap();
this._styleMap = new WeakMap();
this._hoverStyleMap = new WeakMap();
this._accentDashPseudo = new WeakSet();
// Elements whose ::before/::after paints a full-cover opaque surface
// (position absolute/fixed + inset 0 + solid background). The pseudo is
// the element's visible background for contrast purposes even though it
// never joins the element cascade.
this._pseudoSurface = new WeakMap();
}
wrap(node) {
let wrapped = this._wrappers.get(node);
@@ -839,6 +903,24 @@ class StaticDocument {
getStyle(el) {
return this._styleMap.get(el.node) || makeStaticStyle();
}
setHoverStyle(node, style) {
this._hoverStyleMap.set(node, style);
}
getHoverStyle(el) {
return this._hoverStyleMap.get(el.node) || null;
}
setAccentDashPseudo(node) {
this._accentDashPseudo.add(node);
}
hasAccentDashPseudo(el) {
return this._accentDashPseudo.has(el.node);
}
setPseudoSurface(node, color) {
this._pseudoSurface.set(node, color);
}
getPseudoSurface(el) {
return this._pseudoSurface.get(el.node) || null;
}
}
function makeStaticStyle(values = {}) {
@@ -854,6 +936,9 @@ function buildStaticWindow(staticDoc) {
return {
document: staticDoc,
getComputedStyle: (el) => staticDoc.getStyle(el),
getHoverStyle: (el) => staticDoc.getHoverStyle(el),
hasAccentDashPseudo: (el) => staticDoc.hasAccentDashPseudo(el),
getPseudoSurface: (el) => staticDoc.getPseudoSurface(el),
};
}
@@ -867,7 +952,10 @@ function collectStaticCssText(root, fileDir, profile, filePath, modules) {
const rel = link.attribs?.rel || '';
const href = link.attribs?.href || '';
if (!/\bstylesheet\b/i.test(rel) || !href || /^(https?:)?\/\//i.test(href)) continue;
const cssPath = path.resolve(fileDir, href);
// Cache-busting hrefs (styles.css?v=3) resolve to the file, not to a
// literal path with the query in it; a versioned link otherwise made the
// whole stylesheet invisible to every element-level check.
const cssPath = path.resolve(fileDir, href.split(/[?#]/)[0]);
try {
const css = profileStep(profile, {
engine: 'static-html',
@@ -884,6 +972,13 @@ function collectStaticCssText(root, fileDir, profile, filePath, modules) {
function buildStaticStyleMap(root, staticDoc, cssText, modules, profile, filePath) {
const specified = new Map();
// Declarations from :hover rules, matched via their state-stripped
// selectors. Merged per-property against the resting cascade in
// computeNode — a hover declaration only takes effect if it would win
// the cascade while the element is hovered (all resting rules still
// apply in that state).
const hoverSpecified = new Map();
const rootCustomProps = collectCssCustomProps(cssText);
const allNodes = modules.selectAll('*', root.children || []);
const rules = profileStep(profile, {
engine: 'static-html',
@@ -899,9 +994,65 @@ function buildStaticStyleMap(root, staticDoc, cssText, modules, profile, filePat
target: filePath,
}, () => {
for (const rule of rules) {
// ::before/::after rules can't join the element cascade (pseudo
// elements aren't DOM nodes), but one shape matters to the eyebrow
// check: the short chromatic "kicker dash" (content box 8-80px wide,
// 1-6px tall, accent-colored fill). Mark the base-selector matches
// so checkElementHeroEyebrow can see the dash.
if (!rule.isHover) {
const pm = rule.selector.match(/^(.+?)\s*::?(?:before|after)$/i);
if (pm) {
const decls = new Map();
for (const d of rule.declarations) decls.set(d.prop.toLowerCase(), d.value);
const w = cssLengthToPx(resolveVarRefs(decls.get('width') || decls.get('inline-size') || '', rootCustomProps));
const h = cssLengthToPx(resolveVarRefs(decls.get('height') || decls.get('block-size') || '', rootCustomProps));
if (w != null && h != null && w >= 8 && w <= 80 && h >= 1 && h <= 6) {
const bgRaw = String(resolveVarRefs(decls.get('background-color') || decls.get('background') || '', rootCustomProps));
const token = bgRaw.match(/(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color-mix)\([^)]*(?:\([^)]*\))?[^)]*\)|#[0-9a-f]{3,8}\b/i);
const c = parseAnyColor(token ? token[0] : bgRaw);
if (c && (c.a ?? 1) >= 0.1 && Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b) >= 30) {
try {
for (const node of modules.selectAll(pm[1], root.children || [])) {
staticDoc.setAccentDashPseudo(node);
}
} catch { /* unsupported base selector */ }
}
}
// Full-cover surface pseudo: the CTA construction where the
// element itself stays transparent and a ::before/::after with
// position absolute/fixed + inset 0 (or all four sides 0, or
// 100% width and height) plus an opaque background paints the
// visible surface. Mark base-selector matches so the contrast
// checks measure text against the surface the browser renders.
const pseudoPos = String(decls.get('position') || '').toLowerCase();
if (pseudoPos === 'absolute' || pseudoPos === 'fixed') {
const zeroLen = v => v != null && /^0(?:px)?$/.test(String(v).trim());
const insetRaw = String(decls.get('inset') || '').trim();
const coversBox = (insetRaw !== '' && insetRaw.split(/\s+/).every(t => /^0(?:px)?$/.test(t)))
|| ['top', 'right', 'bottom', 'left'].every(side => zeroLen(decls.get(side)))
|| (String(decls.get('width') || '').trim() === '100%'
&& String(decls.get('height') || '').trim() === '100%');
if (coversBox && decls.has('content')) {
const surfRaw = String(resolveVarRefs(decls.get('background-color') || decls.get('background') || '', rootCustomProps));
const surfToken = surfRaw.match(/(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color-mix)\([^)]*(?:\([^)]*\))?[^)]*\)|#[0-9a-f]{3,8}\b/i);
const surf = parseAnyColor(surfToken ? surfToken[0] : surfRaw);
if (surf && (surf.a ?? 1) >= 0.9 && !/gradient/i.test(surfRaw)) {
try {
for (const node of modules.selectAll(pm[1], root.children || [])) {
staticDoc.setPseudoSurface(node, surf);
}
} catch { /* unsupported base selector */ }
}
}
}
continue;
}
}
const matchSelector = rule.isHover ? rule.matchSelector : rule.selector;
if (!matchSelector) continue;
let matched;
try {
matched = modules.selectAll(rule.selector, root.children || []);
matched = modules.selectAll(matchSelector, root.children || []);
} catch {
recordProfileEvent(profile, {
engine: 'static-html',
@@ -910,13 +1061,13 @@ function buildStaticStyleMap(root, staticDoc, cssText, modules, profile, filePat
target: filePath,
ms: 0,
findings: 0,
detail: rule.selector,
detail: matchSelector,
});
continue;
}
for (const node of matched) {
for (const decl of rule.declarations) {
applyStaticDeclaration(specified, node, decl.prop, decl.value, {
applyStaticDeclaration(rule.isHover ? hoverSpecified : specified, node, decl.prop, decl.value, {
important: decl.important,
specificity: rule.specificity,
order: rule.order,
@@ -959,6 +1110,28 @@ function buildStaticStyleMap(root, staticDoc, cssText, modules, profile, filePat
}
const style = makeStaticStyle(values);
staticDoc.setStyle(node, style);
// Hover pass: limited to the two properties the hover-contrast check
// consumes. A hover declaration wins only if it beats the resting
// winner for that property under normal cascade rules (specificity /
// order / importance) — exactly what a browser computes while the
// element is hovered.
const hoverMap = hoverSpecified.get(node);
if (hoverMap) {
let hoverValues = null;
for (const prop of ['color', 'backgroundColor']) {
const hoverDecl = hoverMap.get(prop);
if (!hoverDecl) continue;
const restingDecl = specifiedMap.get(prop);
if (!compareStaticPriority(restingDecl, hoverDecl)) continue;
const next = normalizeStaticCssValue(prop, hoverDecl.value, customProps, parentStyle, values);
if (next === values[prop]) continue;
if (!hoverValues) hoverValues = { ...values };
hoverValues[prop] = next;
}
if (hoverValues) staticDoc.setHoverStyle(node, makeStaticStyle(hoverValues));
}
for (const child of node.children || []) {
if (child.type === 'tag') computeNode(child, style, customProps);
}
@@ -2,7 +2,13 @@ import fs from 'node:fs';
import path from 'node:path';
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
import {
checkSourceDesignSystem,
collectStaticDesignSystemFindings,
mergeDesignSystemFindings,
} from '../../design-system.mjs';
import { isFullPage } from '../../shared/page.mjs';
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
import { finding } from '../../findings.mjs';
import { profileFindings, profileStep, profileStepAsync } from '../../profile/profiler.mjs';
import {
@@ -12,20 +18,23 @@ import {
checkElementGlow,
checkElementGptBorderShadow,
checkElementHeroEyebrow,
checkElementHoverContrast,
checkElementIconTile,
checkElementItalicSerif,
checkElementMotion,
checkElementOversizedH1,
checkElementQuality,
checkElementRadialSpotlight,
checkCreamPalette,
checkHtmlPatterns,
checkKickerAboveHeadingFromDoc,
checkNumberedSectionLabelsFromDoc,
checkPageLayout,
checkPageQualityFromDoc,
checkRepeatedSectionKickersFromDoc,
checkRepeatedContainerTextFromDoc,
resolveBackground,
resolveBorderRadiusPx,
} from '../../rules/checks.mjs';
import { filterByProviders } from '../../registry/antipatterns.mjs';
import { detectText, runTextContentAnalyzers } from '../regex/detect-text.mjs';
import {
StaticDocument,
@@ -51,9 +60,6 @@ function checkStaticPageTypography(document, window) {
for (const font of overusedFound) {
findings.push({ id: 'overused-font', snippet: `Primary font: ${font}` });
}
if (fonts.size === 1 && document.querySelectorAll('*').length >= 20) {
findings.push({ id: 'single-font', snippet: `only font used is ${[...fonts][0]}` });
}
const sizes = new Set();
for (const el of document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, li, td, th, label, button, div')) {
const fontSize = parseFloat(window.getComputedStyle(el).fontSize);
@@ -84,8 +90,9 @@ function checkElementBrokenImage(el) {
}
const STATIC_ELEMENT_RULES = [
{ id: 'border-rules', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementBorders(tag, style, null, resolveBorderRadiusPx(el, style, parseFloat(style.width) || 0, window)) },
{ id: 'border-rules', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementBorders(tag, style, null, resolveBorderRadiusPx(el, style, parseFloat(style.width) || 0, window), el) },
{ id: 'color-rules', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementColors(el, style, tag, window, customPropMap, false) },
{ id: 'hover-color-rules', selector: '*', run: (el, tag, style, window) => checkElementHoverContrast(el, style, tag, window) },
{ id: 'dark-glow', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementGlow(tag, style, resolveBackground(el.parentElement || el, window, customPropMap)) },
{ id: 'motion-rules', selector: '*', run: (el, tag, style) => checkElementMotion(tag, style) },
{ id: 'icon-tile-stack', selector: 'h1,h2,h3,h4,h5,h6', run: (el, tag, _style, window) => checkElementIconTile(el, tag, window) },
@@ -96,6 +103,7 @@ const STATIC_ELEMENT_RULES = [
{ id: 'oversized-h1', selector: 'h1', run: (el, tag, style, window) => checkElementOversizedH1(el, style, tag, window) },
{ id: 'clipped-overflow-container', selector: '*', run: (el, tag, style, window) => checkElementClippedOverflow(el, style, tag, window) },
{ id: 'gpt-thin-border-wide-shadow', selector: '*', run: (el, tag, style) => checkElementGptBorderShadow(el, style) },
{ id: 'radial-spotlight-glow', selector: '*', run: (el, tag, style, window) => checkElementRadialSpotlight(el, style, tag, window) },
];
async function detectHtml(filePath, options = {}) {
@@ -168,6 +176,22 @@ async function detectHtml(filePath, options = {}) {
}
}
if (options?.designSystem) {
const sourceDesignFindings = profileFindings(profile, {
engine: 'static-html',
phase: 'source',
ruleId: 'design-system',
target: filePath,
}, () => checkSourceDesignSystem(html, filePath, { designSystem: options.designSystem }));
const staticDesignFindings = profileFindings(profile, {
engine: 'static-html',
phase: 'page',
ruleId: 'design-system',
target: filePath,
}, () => collectStaticDesignSystemFindings(document, window, filePath, options.designSystem));
findings.push(...mergeDesignSystemFindings(staticDesignFindings, sourceDesignFindings));
}
if (isFullPage(html)) {
const runPageCheck = (ruleId, callback) => profile
? profileFindings(profile, { engine: 'static-html', phase: 'page', ruleId, target: filePath }, callback)
@@ -175,7 +199,13 @@ async function detectHtml(filePath, options = {}) {
for (const f of runPageCheck('typography-rules', () => checkStaticPageTypography(document, window))) {
findings.push(finding(f.id, filePath, f.snippet));
}
for (const f of runPageCheck('repeated-section-kickers', () => checkRepeatedSectionKickersFromDoc(document, window))) {
for (const f of runPageCheck('kicker-above-heading', () => checkKickerAboveHeadingFromDoc(document, window))) {
findings.push(finding(f.id, filePath, f.snippet));
}
for (const f of runPageCheck('numbered-section-labels', () => checkNumberedSectionLabelsFromDoc(document, window))) {
findings.push(finding(f.id, filePath, f.snippet));
}
for (const f of runPageCheck('repeated-container-text', () => checkRepeatedContainerTextFromDoc(document, window))) {
findings.push(finding(f.id, filePath, f.snippet));
}
for (const f of runPageCheck('layout-rules', () => checkPageLayout(document, window))) {
@@ -187,10 +217,33 @@ async function detectHtml(filePath, options = {}) {
for (const f of runPageCheck('skipped-heading', () => checkPageQualityFromDoc(document))) {
findings.push(finding(f.id, filePath, f.snippet));
}
for (const f of runPageCheck('html-patterns', () => checkHtmlPatterns(html).filter(item =>
// Scoped corpora for the pattern checks (see buildHtmlPatternCorpora in
// rules/checks.mjs): CSS-property regexes must not fire on prose ABOUT
// css — `<code>background-clip: text</code>` in a changelog is
// documentation, not styling. cssText already carries the <style>
// blocks and any linked local stylesheets; style/class attributes come
// from the parsed document, so escaped code samples never contribute.
const styleAttrParts = [];
const classAttrParts = [];
for (const el of document.querySelectorAll('*')) {
const styleAttr = el.getAttribute('style');
if (styleAttr) styleAttrParts.push(`style="${styleAttr}"`);
const classAttr = el.getAttribute('class');
if (classAttr) classAttrParts.push(classAttr);
}
const patternCorpora = {
styleText: [cssText, ...styleAttrParts].join('\n'),
classText: classAttrParts.join('\n'),
};
for (const f of runPageCheck('html-patterns', () => checkHtmlPatterns(html, patternCorpora).filter(item =>
item.id !== 'bounce-easing' && item.id !== 'layout-transition'
))) {
findings.push(finding(f.id, filePath, f.snippet));
const item = finding(f.id, filePath, f.snippet);
// Position-aware severity promotion: checks may attach a per-finding
// severity (e.g. a pulsing dot inside a header/nav landmark) that
// overrides the registry default.
if (f.severity) item.severity = f.severity;
findings.push(item);
}
// Text-content analyzers (em-dash overuse, marketing buzzwords,
// numbered section markers, aphoristic cadence) live in the regex
@@ -202,7 +255,10 @@ async function detectHtml(filePath, options = {}) {
}
}
return filterByProviders(findings, options.providers);
// Static-HTML findings carry no line number, so only whole-file
// `impeccable-disable` directives apply here — exactly the standalone-document
// waiver this primitive targets. Bypassed by `--no-config` / `--no-inline-ignores`.
return options?.inlineIgnores === false ? findings : applyInlineIgnores(findings, html);
}
export { checkStaticPageTypography, STATIC_ELEMENT_RULES, detectHtml };