Static Rules Validation
The static rules framework (npm run lint:static-rules) performs build-time source analysis on content and template files. It complements lint:a11y:axe (which tests rendered pages post-build) by catching source-level issues — like missing alt text in markdown or broken internal links in docs/content — that rendered-page testing may not cover.
Who this is for
- Implementors adding new validation rules to the framework
- Developers understanding what the linter checks and how to suppress findings
- AI agents working with content or template files that the linter scans
Running the linter
npm run lint:static-rules # Standard run — warnings exit 0
npm run lint:static-rules -- --strict # Strict mode — warnings exit 1
npm run lint:static-rules -- --show-suppressed # Show suppressed findings
lint:static-rules runs as part of npm run validate (the site-level pipeline). The companion fixture test test:static-rules is a core unit test — run it via npm test (or npm run validate:all to run both pipelines together). See Validation Pipeline for the rationale behind the split.
Running the fixture tests
The fixture test suite verifies framework correctness against known-good and known-bad inputs:
npm run test:static-rules # Quiet — only failures and warnings shown
npm run test:static-rules -- --verbose # Full output — all passing assertions shown
By default only test groups with a failure or warning print anything, keeping pipeline output clean. The summary always reports all three counts:
test:static-rules: 56 passed, 0 warned, 0 failed
- passed — assertion held; no output in quiet mode
- warned — soft assertion noted for visibility; does not fail the run. The fixture suite is expected to stay at
0 warnedon a healthy run; non-zero warnings indicate the harness is surfacing advisory output rather than a failing assertion. - failed — assertion did not hold; exits non-zero
Current rules
alt/missing
Detects missing or empty alt text in content and template files.
Content files (.md, .mdx):
— empty alt text— whitespace-only alt text<img>withoutaltattribute
Astro templates (.astro):
<img>withoutaltattribute — flagged<img alt="">— explicit decorative image, passes<img alt={expr}>— dynamic expression, passes
A component allow-list in static-rules.config.ts suppresses <img> findings within listed component source files (e.g., components that enforce alt text through their own prop contracts). The allow-list does not suppress findings at usage sites.
broken-link
Detects broken path-based internal links in routed markdown/MDX content and selected .astro surfaces.
Content files (.md, .mdx):
- Markdown links (
[label](target)) are checked when the target is path-based - Raw HTML anchors (
<a href="...">) are checked - Same-site relative links resolve relative to the authored document route
- Query strings are ignored for path existence checks
- Fragments validate only when the resolved target is content-derived
Astro templates (.astro):
- Raw anchors with root-relative literal
hrefvalues are checked Link.astrohrefvalues are checked via the rule’sknownComponentsconfig- Non-root-relative literal
hrefvalues are warned as unsupported in.astro - Dynamic
href={...}expressions warn only when they contain obvious internal literal path signals that cannot be resolved statically
Deferred / out of scope:
- Same-origin absolute URLs
Button.astroand other non-listed components- Non-routed content collections such as
fragments - Fragment validation for non-content-derived targets (warned as unsupported instead)
Severity and enforcement
Each finding has a severity: error, warning, or info. Enforcement is a separate concern:
- Default mode: Only
error-severity findings cause exit 1. Warnings are informational. - Strict mode (
--strictorLINT_STRICT=1): Any finding atwarningor above causes exit 1. info-severity findings never affect exit code regardless of mode.
Configuration
Rules are registered in src/core/config/static-rules.config.ts. Each rule entry controls:
enabled— whether the rule runsseverity— overrides the rule’s default severityoptions— rule-specific settings (e.g., thealt/missingcomponent allow-list or thebroken-linkknown-component list)
Disabling a rule or changing its severity requires only a config edit — no framework changes.
Exception system
Two mechanisms suppress findings: inline comments for content-level suppressions and a centralized manifest for system-level suppressions.
Inline suppression
Add an HTML comment on the line immediately before the finding (blank lines between comment and target are allowed for Prettier compatibility):
<!-- lint-ignore alt/missing — decorative separator image -->

The reason after the em dash (—) is required. Omitting the reason is itself flagged as lint/missing-reason.
Inline suppression works in .md, .mdx, and .astro files.
Centralized manifest
src/core/config/static-rules-exceptions.ts supports system-level suppressions for cases where inline comments are impractical. Each entry specifies:
ruleId— which rule to suppressfile— target file pathcontext(optional) — narrows suppression to a specific findingreason— why this suppression existsapprovedBy/date(optional) — audit trail
When context is provided, only the matching finding is suppressed. When omitted, all findings for that rule in that file are suppressed.
Viewing suppressed findings
Suppressed findings are excluded from the main output by default. The summary footer always shows the suppressed count. Use --show-suppressed to include suppressed findings in the output for audit purposes.
Adding a new rule
To add a rule to the framework:
- Create a rule file in
scripts/lib/static-rules/rules/implementing theStaticRuleinterface fromtypes.ts - Register the rule in
scripts/lib/static-rules/rule-registry.ts - Add a config entry in
src/core/config/static-rules.config.ts - Add fixture files in
scripts/lib/static-rules/fixtures/and assertion cases intest-static-rules.ts
Each rule declares which source types it needs (content-md, astro-template, route-manifest). The runner only loads file-backed sources and inventories required by enabled rules.
Relationship to other validation steps
lint:a11y:axe— tests rendered pages post-build against WCAG 2.2 AA. Catches runtime accessibility issues including contrast. Static rules catch source-level issues that rendered-page testing may miss.lint:tokens— enforces the three-layer token contract (no raw colors in components). Static rules operate on different concerns (content quality, not token usage).lint:content-ingestion— validates content frontmatter schemas. Static rules validate content body quality (alt text, broken internal links).