@jordanmgsoftware/seo-cli
v0.4.7
Published
SOTA SEO CLI for MG Software projects: crawl, audit, link graph, anti-spam (March 2026), GEO/AEO, programmatic SEO checks, plus dedicated security & privacy audits (HTTP headers, TLS, cookies, dependency vulns, takeover, GDPR/PII). Runs on production URLs
Readme
@jordanmgsoftware/seo-cli
SOTA SEO CLI by MG Software — runs on production URLs, localhost, or build output.
# add to a Next.js project
bun add -d @jordanmgsoftware/seo-cli
bun run mg-seo init
bun run seo-audit
# global / direct usage
bun add -g @jordanmgsoftware/seo-cli # or: npm i -g @jordanmgsoftware/seo-cli
mg-seo init # generate mg-seo.config.ts
mg-seo crawl https://example.com
mg-seo crawl http://localhost:3000
mg-seo audit https://example.com
mg-seo audit http://localhost:3000 --auto-start
seo-audit # defaults to http://localhost:3000 + auto-start
mg-seo links https://example.com
mg-seo security https://example.com # dedicated security audit
mg-seo privacy https://example.com # dedicated privacy audit
mg-seo reportWhy
A pragmatic, code-first alternative to Screaming Frog / Sitebulb that:
- Lives in your dev loop — works on
localhostand on built static output (e.g..next,dist/) - Integrates with CI — exit codes, JUnit XML, GitHub Action templates
- 2026-current — checks for Google's March 2026 Spam Update, INP-as-primary-signal, GEO/AEO readiness aligned with Google's AI optimization guide (AI-bot access, snippet eligibility for AI Overviews / AI Mode; llms.txt as a non-scoring optional)
- First-class programmatic SEO — MinHash-based template uniqueness detection (the exact thing Google now penalises)
- Code-first config —
mg-seo.config.ts, type-safe, per project - Zero-config Next.js loop — add the package, run
bun run seo-audit, and the CLI starts localhost when needed
Commands (v0.2)
| Command | Purpose |
|---|---|
| mg-seo init | Scaffold mg-seo.config.ts |
| mg-seo crawl <source> | Spider crawl with sitemap + robots discovery |
| mg-seo audit <source> | Run all rules (on-page, links, schema, content, technical) |
| seo-audit [source] | Local-first audit that auto-starts a dev server when needed |
| mg-seo links <source> | Internal link graph (orphans, broken, redirect chains, depth) |
| mg-seo grade <file-or-url> | Grade ONE document (local HTML draft or URL) against the content / anti-spam / GEO / schema rules |
| mg-seo security <source> | Dedicated security audit (HTTP headers, TLS, cookies, exposed paths, dependency vulns, takeover detection, project-mode static analysis) |
| mg-seo privacy <source> | Dedicated privacy audit (trackers without consent, GA misconfig, plaintext PII, service-worker scope) |
| mg-seo report | Aggregate latest results into a markdown / HTML report |
| mg-seo-mcp | Local MCP server (stdio) exposing audit/keywords/content tools to AI agents |
MCP server (local)
mg-seo ships a local Model Context Protocol server so AI agents (Cursor, Claude) can run audits and build content briefs directly — no need to remember CLI flags. Because it runs locally it keeps the two capabilities a hosted endpoint never could: it can reach http://localhost:3000 and read your project source tree (project mode: source-mapping, deps-audit, supply-chain, vibe-check).
It speaks stdio (stdout is the JSON-RPC channel); all status output is routed to stderr so the protocol stays clean.
Tools
| Tool | What it does |
|---|---|
| seo_audit | Crawl + audit a URL, localhost, or project dir. scope, project, active, autoStart, maxPages, format (summary|json). Returns the LLM-optimised report or a structured payload. |
| seo_keywords | Keyword opportunities (GSC striking-distance/CTR/cannibalisation, discovery, competitor gaps via enrichment JSON). |
| content_brief | Deterministic blog/landing brief: keyword clusters → intent → new-vs-improve mapped against the crawled site. |
| grade_content | Validate ONE document (url, local file, or raw html) against the content / anti-spam / GEO / schema rules — the validation half of the content loop. file grades a draft in place so the HTML never travels through agent context. |
| seo_diff | Diff the latest cached audit vs. the previous snapshot (run seo_audit with writeFiles:true first). |
| list_scopes | List the valid scope values for seo_audit. |
Cursor config
Add to .cursor/mcp.json (or your global Cursor MCP config).
With bun — bunx resolves the default mg-seo bin and the mcp subcommand starts the server:
{
"mcpServers": {
"mg-seo": {
"command": "bunx",
"args": ["--bun", "@jordanmgsoftware/seo-cli", "mcp"]
}
}
}With npm/npx — select the dedicated bin explicitly with -p (plain npx <pkg> mcp is ambiguous because the package ships multiple bins):
{
"mcpServers": {
"mg-seo": {
"command": "npx",
"args": ["-y", "-p", "@jordanmgsoftware/seo-cli", "mg-seo-mcp"]
}
}
}Installed in the project (offline / pinned version) — point straight at the built file:
{
"mcpServers": {
"mg-seo": {
"command": "node",
"args": ["node_modules/@jordanmgsoftware/seo-cli/dist/mcp.mjs"]
}
}
}Requires v0.4.0+ (v0.4.2+ for directory→domain inference in
content_brief/seo_keywords). Use@latestinbunx/npxso you don't get a cached older release.
Content loop (recommended agent workflow)
mg-seo does not auto-write blog posts — the calling agent is the LLM. The loop is:
content_brief→ pick a topic cluster (intent, internal-link sources, competitor evidence).- The agent writes the page.
grade_content→ validate the draft against the anti-spam (spam/entity-swap,spam/publication-velocity, keyword-stuffing) and GEO rules before publishing. Render the draft to disk and passfile(or runmg-seo grade <draft.html>in the terminal) so the HTML never has to travel through agent context.- Fix what it flags, repeat step 3 until clean.
Rule categories
| Category | Coverage |
|---|---|
| on-page | titles, meta description, H1, headings, image alt/dimensions, canonical, hreflang, OG, viewport, lang, noindex, status codes, redirect chains |
| links | orphans, broken internal links, click depth, generic anchor text |
| schema | JSON-LD presence, valid JSON, required properties for Article/Product/FAQ/Organization/Breadcrumb/Recipe/Event/Person/JobPosting and more; video SEO (VideoObject absolute URLs, missing schema for playable videos, thumbnail/content crawlability + CORP, watch-page placement, url/page match) |
| content | thin content (word count), text-to-HTML ratio, identical body content, title keyword cannibalisation (Jaccard ≥ 0.8) |
| technical | robots.txt present, XML sitemap discoverable, sitemap coverage of crawled pages |
| geo | Issue tier: AI-bot access (robots.txt blocking GPTBot/ClaudeBot/PerplexityBot — Google's AI Overviews are unaffected; they use Googlebot), snippet eligibility (geo/snippet-eligibility: nosnippet / max-snippet:0 / near-total data-nosnippet on an indexable page silently opts it out of AI Overviews / AI Mode, which require a page to be indexed and snippet-eligible), and CSR visibility (content invisible to non-JS AI crawlers). See Opportunities below for everything else GEO. |
| security | Only emitted by mg-seo security. HTTP security headers (HSTS, CSP, COOP/COEP, CORP, Permissions-Policy, …), TLS hygiene (cert expiry, weak protocol/cipher, hostname mismatch, OCSP stapling), cookie flags (Secure, HttpOnly, SameSite), HTML/DOM leaks (inline event handlers, javascript: URIs, login over HTTP, sensitive query params, secret-shaped tokens, missing SRI, exposed sourcemaps), exposed paths (.git, .env, wp-admin, debug endpoints, GraphQL introspection), framework probes (WP / Next / Sanity / Strapi), subdomain takeover (DNS + body fingerprint), dependency vulns (npm audit / bun pm audit), project-mode static analysis (dangerouslySetInnerHTML without sanitiser, eval(), JWTs in localStorage, public env vars carrying private-key-shaped values, console.log(process.env…)). |
| privacy | Only emitted by mg-seo privacy. Third-party trackers firing without consent, Google Analytics without IP anonymisation, browser-fingerprinting libraries, mailto: addresses with personal-shaped local parts, plaintext phone numbers in body content, service workers registered with broad scope and no version hash. |
Security audits — mg-seo security
mg-seo audit deliberately stays focused on SEO. The dedicated security command shares the same crawl pipeline + reporters but swaps in a separate rule registry and adds active probes that would be too aggressive for the SEO audit:
mg-seo security https://example.com
mg-seo security http://localhost:3000 --auto-start
mg-seo security ./ # project-only audit (deps + static analysis)
mg-seo security https://example.com --project ./ # crawl + filesystem hygieneEvery security check uses the existing --fail-on, --html, --max-pages, --max-depth, --concurrency flags, plus the following security-specific opt-outs:
| Flag | Effect |
|---|---|
| --no-probe | Skip the active sensitive-paths probe (.git, .env, /wp-admin, …) |
| --probe-paths <file> | Newline-separated file of extra paths to probe |
| --no-deps-audit | Skip npm audit / bun pm audit + project-mode static analysis |
| --no-takeover-check | Skip the subdomain takeover scan (DNS + body fingerprint) |
| --no-tls-probe | Skip the TLS handshake probe |
| --no-framework-probe | Skip framework-specific probes (WP/Next/Sanity/Strapi) |
| --no-error-probe | Skip the verbose-404/500 detection (some servers log these as suspicious traffic) |
What it covers, in one breath:
- HTTP headers — HSTS / CSP (with weak-source detection) / X-Frame-Options / X-Content-Type-Options / Referrer-Policy / Permissions-Policy / COOP+COEP / CORP /
Server-Timingleaks /Server+X-Powered-Byfingerprints. - TLS hygiene — cert expiry, hostname mismatch, weak protocol (≤ TLSv1.1), weak cipher (RC4/3DES/MD5), OCSP stapling, single-handshake probe (no slow rescans).
- Cookies —
Secure/HttpOnly/SameSiteflags evaluated perSet-Cookie. - HTML/DOM — inline event handlers,
javascript:URIs, password inputs on HTTP,<form action>to a different registrable domain,target="_blank"withoutrel="noopener", missing SRI on third-party<script>/<link>,<meta name="generator">version leaks, secret-shaped tokens inside inline<script>. - Exposed paths —
.git/HEAD,.env*, backups, lockfiles,db.sql, default landing pages, directory listings,/.well-known/security.txtpresence, debug/swagger/OpenAPI endpoints, GraphQL introspection. - Framework probes — WordPress
/wp-json/wp/v2/usersenumeration, Next.js_next/required-server-files.json, Sanity/studioand Strapi/adminexposure. - Subdomain takeover — resolves CNAME records on subdomains discovered during the crawl; only flags when the body matches a known platform fingerprint (S3/GitHub Pages/Heroku/Netlify/Vercel/Azure/CloudFront/Fastly/…).
- CORS — wildcard origin + credentials true.
- robots.txt / sitemap — Disallow lines that advertise admin/staging/backup paths, sitemap entries pointing at staging hostnames, cross-domain sitemap entries.
- Project-mode dependency audit —
npm audit(orbun pm audit) when a project directory is supplied, plus filesystem hygiene:.envfiles outside.gitignore, exposed sourcemaps in build output,.gitfolders insidepublic/, multiple co-existing lockfiles, secret-shaped strings in source. - Project-mode static analysis —
dangerouslySetInnerHTMLwithout an obvious sanitiser nearby,eval()/new Function()/ string-formsetTimeout, JWT/auth tokens written tolocalStorage, public env vars (NEXT_PUBLIC_/VITE_/REACT_APP_/PUBLIC_) carrying private-key-shaped values,console.log(process.env…).
Severity is calibrated for production reality:
| Severity | Examples |
|---|---|
| critical | TLS cert expired, hostname mismatch, password input on HTTP, .git reachable, dangling CNAME, .git folder shipping in public/ |
| error | Missing HSTS, weak TLS, mixed content, exposed API debug endpoints, .env not gitignored, public env carrying private-key-shaped value |
| warning | Most header gaps, weak CSP, weak cipher, sensitive query params, exposed sensitive paths, vulnerable dep (high), framework-specific probes |
| info | Missing security.txt, missing CORP, missing OCSP stapling, generator meta leak, sourcemap shipped, low-severity vuln |
Privacy audits — mg-seo privacy
mg-seo privacy https://example.com
mg-seo privacy http://localhost:3000 --auto-startprivacy is intentionally narrow: it focuses on the privacy-adjacent checks that don't belong in either the SEO audit or the security audit.
| Rule | What it flags |
|---|---|
| privacy/tracker-without-consent | Trackers (GA4 / GTM / Meta Pixel / Hotjar / Clarity / UA) detected on a page with no CMP loader (Cookiebot / OneTrust / Iubenda / Usercentrics / …) |
| privacy/ga-without-ip-anonymization | Google Analytics loaded without anonymize_ip / anonymizeIp configuration |
| privacy/fingerprinting-script | FingerprintJS / ClientJS / fp.js style libraries — flagged as personal-data processing under GDPR |
| privacy/plaintext-pii-mailto | mailto: links with admin-shaped local parts (admin@, root@, webmaster@) or personal-shaped local parts (first.last@) |
| privacy/plaintext-phone-number | Phone-shaped strings in main flow text (≥9 digits) |
| privacy/sw-broad-scope | navigator.serviceWorker.register('/sw.js') with root scope and no version hash |
Privacy-specific flags:
| Flag | Effect |
|---|---|
| --consent-cookie <name> | Re-crawl with the named cookie set so the audit sees the post-consent tracker surface (currently only emits a warning — consent re-crawl will land in v0.5) |
Opportunities (a.k.a. suggestions)
Beyond "this is broken, fix it" findings (issues), mg-seo audit also runs a separate suggestion engine that surfaces "this would lift you, consider it" opportunities. Suggestions never trigger --fail-on and heuristic suggestions never affect the audit score; only rules explicitly marked confidence: "deterministic" (provable-from-crawl signals like a missing author entity) feed a small, capped opportunity penalty. Anything Google Search says it ignores (llms.txt, markdown mirrors) or calls a myth (schema density for AI) is deliberately heuristic, so it can never dock points.
The split exists for two reasons:
- Pure scoring — opportunities don't pollute
infopenalties (capped at -3); they're surfaced in their own report section. - CI-friendly —
--fail-on warningwon't go red because someone hasn't added a FAQ section yet.
| Impact | When to act |
|---|---|
| high | Big, well-evidenced lifts (knowledge panel eligibility, missing author entities, breadcrumb/recipe rich results). |
| medium | Worth doing during the next content sprint. |
| low | Polish: do it on pages you're touching anyway. |
Suggestion rules
| Rule | Category | What it flags |
|---|---|---|
| geo/missing-breadcrumb-schema | geo | Deep pages (depth ≥ 2) with no BreadcrumbList JSON-LD |
| geo/add-jsonld-faqpage-on-services | geo | Service/product pages with an FAQ section / accordion but no FAQPage JSON-LD. Low impact — Google retired the FAQ rich result (May 7, 2026); only helps AI Overviews/LLMs now |
| geo/faq-schema-suggestion | geo | Any page with ≥3 question headings or a collapsible Q&A section and no FAQPage JSON-LD. Low impact, advisory — AI Overviews only, no Search rich result |
| geo/faqpage-rich-result-deprecated | geo | Page already has FAQPage/QAPage markup: FYI that the Search rich result is gone for all sites (May 7, 2026). Keep the JSON-LD for AI engines; reconsider the FAQ format |
| geo/add-faq-content | geo | Commercial/purchase-intent pages (services, products, pricing, quote) with no visible Q&A yet. Proactively nudges adding a visible FAQ section for organic long-tail + AI Overviews. Content, not schema — explicitly recommends against FAQPage JSON-LD (Google retired the rich result and bars FAQPage "for advertising purposes") |
| geo/howto-schema-suggestion | geo | Tutorial-titled pages with ordered lists but no HowTo schema (AI engines only — Google retired the HowTo rich result in September 2023) |
| geo/llms-txt-presence | geo | Missing or incomplete /llms.txt. Non-scoring — Google Search ignores llms.txt (official guidance: it neither helps nor hurts); only relevant for non-Google AI tools |
| geo/markdown-mirrors | geo | No markdown mirrors for AI crawlers (per-page .md via <link rel="alternate" type="text/markdown"> or /llms-full.txt). Non-scoring — Google Search ignores these files; targets non-Google AI crawlers only. Per-page .md is the foundation; medium on content-heavy sites, low on small marketing sites where /llms-full.txt is usually overkill |
| geo/organization-completeness | geo | Missing or partial Organization schema on the homepage |
| geo/author-byline | geo | Article schema without E-E-A-T-grade author identifiers |
| geo/freshness | geo | Articles older than 365 days or missing dateModified |
| geo/schema-graph-density | geo | Pages exposing fewer than 4 distinct schema types. Non-scoring — per Google's AI optimization guide, structured data isn't required for generative AI features; framed as optional rich-result/context polish |
| geo/passage-extractability, geo/answer-first-paragraph, geo/question-headings, geo/paragraph-length, geo/citation-readiness, geo/entity-density, geo/llm-readiness | geo | Long-form content structured for LLM passage extraction (readability + non-Google answer engines; Google itself needs no AI-specific writing style or "chunking"). geo/llm-readiness is a rollup and supersedes its component rules on pages where it fires. |
| content/missing-table-of-contents | content | Long-form pages (≥1500 words, ≥6 H2s) without anchor jump-links |
| content/expand-thin-section | content | 1-2 thin (<400 words) outliers in an otherwise-substantial section |
| content/page-type-depth | content | Pages thin for their page type — below the per-type word bar (commercial 500, product 400, location 300, blog 800; tunable via thresholds.contentDepth) or structurally shallow (few H2s / FAQ / lists / internal links / entities) in the borderline band. Bilingual EN/NL by page language; skips utility / contact / login / checkout / watch pages |
| links/under-linked-pages | links | Indexable pages with fewer than 3 inbound internal links |
| links/missing-related-content-cluster | links | Topically-related pages (title Jaccard ≥ 0.4) that don't link to each other |
| links/missing-link-from-parent | links | Child page exists but its parent hub doesn't link to it (and the parent already links to siblings) |
| links/asymmetric-cluster | links | Topically-related page links here but this page doesn't link back |
| links/excessive-internal-links | links | Page with too many contextual internal links, or a wall of near-duplicate query-string variants (over-optimization) |
| on-page/missing-og-image | on-page | Page declares OG tags but no og:image |
| on-page/missing-twitter-card | on-page | Page declares OG tags but no twitter:* meta |
| schema/upgrade-article-to-newsarticle | schema | Generic Article on /blog/* or /news/* could be BlogPosting / NewsArticle |
| schema/video-decorative-only | schema | Page has only decorative background clips (muted/autoplay/loop, no controls) and no VideoObject, intentionally not video-indexable, so a "Video isn't on a watch page" report is expected (not a defect) |
| schema/video-promotional-no-schema | schema | Page has a playable <video controls> on a content-heavy page (homepage infomercial, product teaser) with no VideoObject: correct when you do not want video search results; GSC "not on a watch page" is expected |
| fs/add-generate-metadata | fs | Static metadata export on a route that has a dynamic child ([slug]) — switch to generateMetadata |
| fs/jsonld-component-suggestion | fs | /blog/* or /news/* route file renders no JSON-LD component |
| fs/not-found-metadata | fs | not-found.tsx exists but inherits the parent layout's title |
Measurement & Compliance (analytics/*)
These aren't SEO findings — a page can rank fine without Meta Pixel — so the reporters surface them in a dedicated Measurement & Compliance panel (separate <section> in HTML, separate ## Measurement & Compliance block in Markdown, separate cyan-headed table in the console). Same engine, same --suggestions-only flag, same snapshot diff, same ROI sort.
| Rule | Category | What it flags |
|---|---|---|
| analytics/missing-search-console-verification | analytics | Homepage has no <meta name="google-site-verification"> tag |
| analytics/no-analytics-detected | analytics | No GA4 / GTM / Plausible / Fathom / Umami / Matomo / Hotjar / Clarity anywhere on the site (informational) |
| analytics/duplicate-gtm-containers | analytics | A page loads more than one distinct GTM-XXXX container (double-firing pageviews) |
| analytics/analytics-without-consent | analytics | GA4 / Meta Pixel / Clarity / Hotjar / UA / GTM is present anywhere AND no CMP (Cookiebot, OneTrust, Usercentrics, Iubenda, Complianz, Termly, Didomi, Axeptio) was detected — GDPR / ePrivacy risk |
| analytics/missing-cookie-banner | analytics | No CMP detected anywhere (only fires when analytics-without-consent doesn't, so they never duplicate) |
Disable the whole category with disabledCategories: ["analytics"] if you treat measurement/compliance out-of-band.
ROI sort & deduplication
- Every suggestion carries both an impact (
high/medium/low) and an effort (small/medium/large). The reporters sort by ROI = impact / effort so copy-paste schema fixes float above content rewrites of the same expected lift. - Rules can declare
supersedes: [...]— when a narrower rule (e.g.geo/add-jsonld-faqpage-on-services) fires on a page, it hides the broader rule (geo/faq-schema-suggestion) so the TODO list never carries duplicates. - The Markdown report and console output include a Top pages to act on panel that ranks URLs by
(count × impact)— opening one of these is an N-for-1 fix.
Snapshot / diff for opportunities
Audit snapshots persist suggestionKeys + byImpact alongside the existing issueKeys + bySeverity. The diff section now shows resolved / introduced opportunities and per-impact-tier deltas, so the Opportunities layer becomes a progress tracker, not just a static report.
Configuration
// mg-seo.config.ts
export default defineConfig({
suggestions: {
enabled: true, // turn the whole layer off
minImpact: "low", // "low" | "medium" | "high"
maxPerCategory: 20, // cap per category after ROI-sort
},
// Per-rule disable works the same as issue rules; effort overrides too:
rules: {
"geo/llms-txt-presence": { enabled: false },
"links/under-linked-pages": { options: { impact: "high", effort: "medium" } },
},
});CLI flags & commands
| Flag / command | Effect |
|---|---|
| --no-suggestions | Skip the suggestion engine entirely. Only score-affecting rules run. |
| --suggestions-only | Run only the suggestion engine on mg-seo audit. Score is reported as 100. |
| mg-seo opportunities <source> | First-class entry point for the suggestions-only audit. Prints a condensed table and writes opportunities-latest.md for sharing with the content team. |
<source> accepts:
- A URL (
https://example.com) — production crawl - A localhost URL (
http://localhost:3000) — dev server crawl - A directory (
./dist,./out,./.next/server/pages) — static output crawl (roadmap)
Development
:: One-shot run from source (no build needed)
dev.cmd audit https://example.com --max-pages 20
dev.cmd links http://localhost:3000
:: Full toolchain
bun install
bun run dev -- audit https://example.com
bun run seo-audit
bun run build
bun run typecheckRoadmap
- ✅ v0.1 —
init,crawl,audit,links,report(on-page + link rules) - ✅ v0.2 —
schema,content,technical(robots/sitemap) rule categories - ✅ v0.3 —
geo(AEO/AI Overview readiness),llms.txt, suggestions layer (deterministic opportunities for SEO/GEO growth) - ✅ v0.3.1 — Suggestions v2: snapshot/diff for opportunities, per-URL hotspot rollup,
supersedesdeduplication, ROI sort (impact / effort),fs/*opportunity rules, dedicatedmg-seo opportunitiescommand - ✅ v0.3.2 —
mg-seo security(HTTP headers, TLS, cookies, exposed paths, framework probes, subdomain takeover, dependency vulns, project-mode static analysis) andmg-seo privacy(consent gating, GA misconfig, plaintext PII, service-worker scope) commands - ✅ v0.3.3: Video SEO. VideoObject absolute-URL / missing-schema / thumbnail crawlability (reachability, latency, CORP) checks, plus watch-page detection:
schema/video-not-watch-page(a VideoObject on a content-heavy page that Search Console reports as "Video isn't on a watch page") andschema/video-url-mismatch(a same-originurl/embedUrlpointing at a different page). Genuine watch pages (single video, minimal prose, undervideo.watchPageMaxWords) are exempt from thin-content and text-to-HTML rules, and purely decorative background clips get a non-scoringschema/video-decorative-onlynote - ✅ v0.3.4:
schema/video-missingnow only fires on watch-page candidates (not homepage promos). New non-scoringschema/video-promotional-no-schemadocuments that a playable clip on a content-heavy page without VideoObject is correct when you do not want video indexing.schema/video-not-watch-pagehint updated: remove VideoObject OR move to a watch page - ✅ v0.4.3:
schema/invalid-property— property-on-type validation for JSON-LD. Flags properties that aren't valid for their@type(e.g.inLanguageonLocalBusiness, which belongs onCreativeWork/WebPage/Event), matching SemRush's "property not recognized by Schema.org vocabulary" finding. Uses a curated, inheritance-aware allowlist and only checks modeled types — multi-type nodes are validated against the union of their types; unknown types, JSON-LD keywords (@…) and namespaced/IRI keys are skipped — to stay false-positive-safe. Aggregated site-wide likeschema/required-properties - v0.4 —
pseo+spam(March 2026 policies, MinHash near-dup detection),perf(Lighthouse-Node + PSI optional) - v0.5 — Playwright optional rendering,
watch,diff,ci, HTML reporter, JUnit XML - v1.0 — Bun standalone binary, GitHub Action template
© MG Software B.V.
