Is your website understandable, citable, and truthful — to an AI?
semantic-integrityaudits how well a page is represented to LLM-powered search and answer engines, and tells you exactly what to fix.
The web is shifting from search → links → human decides to question → AI synthesis → recommendation. When an LLM answers on a user's behalf, your page no longer speaks to a human first. It speaks to a model that extracts, summarizes, and decides whether to cite you — or whether to trust you at all.
That shift created a new optimization layer on top of SEO: AEO / GEO / LLMO (Answer Engine / Generative Engine / LLM Optimization). Most of the early energy there points the wrong way — toward gaming recommendations and tricking models.
semantic-integrity takes the opposite stance:
This is not "how to trick ChatGPT." This is "how to be represented correctly by AI systems."
It rewards factual clarity, structural soundness, provenance, and consistency
between what your page shows and what it claims — never manipulation.
See docs/vision.md.
One command, an honest score, and a list of concrete fixes:
LLM Readiness Score: 72/100
URL: https://example.com/products/tote
Metadata (20/25)
✓ Page title present — Taurillon Leather Tote
✓ Canonical URL present
⚠ No llms.txt
Structured Data (30/30)
✓ Valid JSON-LD found (3 node(s))
✓ Product schema found
Content (12/25)
⚠ No visible FAQ structure
✗ Factual density (heuristic): 2/10 — facts=0, fluff=9, words=140
Integrity (10/20)
✓ Product name is consistent (structured ↔ visible)
✗ Weak provenance: no organization, author, or publisher
Requires Node.js >= 18.17 (uses the global fetch).
npm install
npm run build
# audit a live page
npx semantic-integrity https://example.com
# machine-readable output (pipe into jq, store in CI artifacts, etc.)
semantic-integrity https://example.com --json
# CI gate: exit 1 if the score is below a threshold
semantic-integrity https://example.com --min-score 70| Category | Points | Signals |
|---|---|---|
| Metadata | 22 | title, description, canonical, indexability, llms.txt¹ |
| Structured Data | 30 | valid JSON-LD, Product schema, supporting entities |
| Content | 25 | headings, visible FAQ, factual density (heuristic) |
| Integrity | 20 | structured ↔ visible consistency, provenance, offer data |
¹ llms.txt is a real, forward-looking proposal (llmstxt.org),
not yet a formal standard or honored by major LLMs — so it's checked but weighted
low (2 pts). An auditor about truthfulness should be honest about its own metrics.
Scoring rationale and weights live in docs/architecture.md.
flowchart TD
A[Target URL] --> B[validateUrl]
B --> C[fetchPage + fetchLlmsTxt]
C --> D[parsePage cheerio]
D --> E1[analyzeMetadata]
D --> E2[analyzeStructuredData]
D --> E3[analyzeContent]
D --> E4[analyzeIntegrity]
E1 --> F[computeScore]
E2 --> F
E3 --> F
E4 --> F
F --> G[renderText / renderJson]
The codebase is organized around an explicit domain model, not around the mechanics of fetching and parsing. Data flows in one direction through a small set of typed, immutable shapes:
flowchart LR
TargetUrl --> PageSnapshot --> ParsedPage --> AnalyzerReport --> LLMReadinessScore
Why this matters in practice:
- A pure analysis core. Everything from parsing onward is a pure function of
PageSnapshot.auditSnapshot(snapshot)touches no network and is fully deterministic, so the entire scoring logic is unit-tested from HTML fixtures with zero mocking.audit(url)is only a thin network wrapper around it. - Independent analyzers. Each check is a pure
(ParsedPage) => AnalyzerReportthat owns a fixed point budget. Adding a signal means adding one analyzer — the scorer never changes, and nothing shares mutable state. - A swappable parser seam. Because the model boundary (
PageSnapshot → ParsedPage) is explicit, the HTML parser can be replaced (e.g. a faster Rust/WASM parser at crawl scale) without touching a single analyzer. The architecture absorbs that change instead of fighting it. - Explainable scores. No opaque model. Every point earned or lost maps to a
named
Findingwith a stable id, so a72/100is always traceable to a list of specific, fixable observations.
Design rationale is recorded as ADRs in docs/adr/, and the full
type model in docs/domain-model.md.
npm run typecheck # tsc --noEmit (strict, NodeNext ESM)
npm test # vitest — no network, fixtures only
npm run build # emit dist/Code-quality rules the project holds itself to: readable over clever, small functions, every core concept typed, no hidden global state, minimal abstraction. AI-generated code must stay readable to humans.
MVP. Product-centric today (target domain: luxury e-commerce). Known
limitations — SPA/client-rendered pages, product-only integrity checks — and
the mitigation paths are documented honestly in
docs/architecture.md.
Contributions welcome: new analyzers are intentionally easy to add. Open an issue to discuss a signal before sending a PR.
MIT © RAGfish Labs