angeo / module-aeo-audit

angeo/module-aeo-audit

Magento 2 AEO (AI Engine Optimization) Audit. v3 covers 15 signals — robots.txt AI bots, llms.txt + llms.jsonl, Product / Organization / FAQ schema, merchant return + shipping policies, sitemap.xml, UCP profile, AI product feed, OG tags, canonical + hreflang, JSON-LD quality, well-known endpoint matrix, Core Web Vitals via CrUX. Score Trend dashboard, Admin UI, cron, dynamic fix commands, dependency-injected extension point for custom checkers.

magento2-module Compatibility: 2.4.7-2.4.9 Code Quality: Fail Tests: Fail Security: Pass MIT

Are you the maintainer of angeo?

Packagento pulls angeo's Composer packages from the public registry so buyers can find them here.

Claim the namespace to take ownership, publish new releases directly, and start charging for premium versions.

Claim this namespace →

Angeo AEO Audit — AI Engine Optimization for Magento 2

CI
Packagist Version
Downloads
PHP
Magento
Mage-OS Directory
License: MIT

One CLI command that tells you exactly why ChatGPT, Gemini, Claude, and Perplexity aren't recommending your store — and how to fix it.


Compatibility

Component Version
Magento Open Source 2.4.6, 2.4.7, 2.4.8
Adobe Commerce 2.4.6, 2.4.7, 2.4.8
Adobe Commerce Cloud All current versions
PHP 8.1 – 8.5
Themes Luma, Hyvä

Tested with: Magento Open Source 2.4.7-p3 + PHP 8.3 + Hyvä 1.3.


What's new in v4.0.0

The evidence layer. Signals 1–15 audit configuration; v4 adds two signals
that audit reality: waf_reality (does the edge actually let the bots in that
robots.txt invites?) and ai_crawler_activity (which AI crawlers demonstrably
visited, from GDPR-safe evidence sources). Plus: purpose-classified bot
grading (blocking GPTBot is a licensing choice, blocking OAI-SearchBot is an
AEO failure), GitHub Actions CI with a Mage-OS installability job, i18n
dictionary, configurable cron schedule, vendor-neutral & switchable fix
hints, and a security fix in the CrUX checker (TLS verification re-enabled,
API key moved from URL to header, encrypted key now actually decrypted).
Full details in CHANGELOG.md.

What's new in v3.0.0

Major release — see CHANGELOG.md for the breaking-change
migration guide if you have custom checkers.

20 signals (up from 9), reflecting the actual AEO landscape of 2026: AI
shopping integrations, merchant policies, agentic commerce, and structured-data
quality.

6 new checkers:

  • merchant_policiesMerchantReturnPolicy + OfferShippingDetails
    required by Google AI Mode and ChatGPT Shopping since Jan 2026
  • organization_schema — brand entity in AI knowledge graphs
  • ucp_profile — Universal Commerce Protocol (/.well-known/ucp), with
    built-in security check that detects leaked JWK private keys
  • jsonld_quality — three-page schema breadth audit (homepage / category /
    product), WebSite+SearchAction, BreadcrumbList, ItemList
  • well_known — discovery matrix for /.well-known/{ucp,ai-plugin.json,security.txt,mcp}
  • core_web_vitals — LCP / INP / CLS via Google CrUX API (free, opt-in
    with API key)

Refactored architecture (this is the BC-break):

  • Shared Service\HttpCache — eliminates duplicate fetches across checkers
    (hundreds of redundant HTTP requests on multi-store audits before, dozens
    now)
  • Service\StoreUrlSampler — single source of truth for product / category /
    CMS URL sampling
  • New --category and --fail-on-severity CLI flags for CI workflows
  • Per-checker exception isolation — slow or failing checkers no longer halt
    the audit run

Note on access-log monitoring: an ai_bot_traffic checker was
prototyped during v3 development and excluded from the release after
security review — it encouraged broad read access on /var/log/nginx/,
didn't work on Cloud/containerised hosting, and was dominated by false
positives behind edge caches. AI-bot traffic is better measured at the
edge (Fastly/Cloudflare Analytics) or via APM (New Relic, Datadog) than
inside a PHP module. See CHANGELOG.md "Considered and rejected" for the
full rationale. The live_signal category remains in CheckerInterface
for third-party modules with secure live-signal sources — notably
angeo/module-aeo-brand-visibility.


What it checks — 18 signals

Two layers since v4.0.0: configuration signals verify the store is set up
for AI engines; the evidence layer verifies AI engines actually reach it.

# Signal Code Weight Category What it validates
1 robots.txt — AI bots robots_txt 1.0 technical 12 AI bots, syntax errors, versioned UAs, conflicting rules
2 llms.txt — content map llms_txt 1.0 technical llmstxt.org v2 structure, store-locale + currency match, cross-host links, empty blockquote, headings before the first H2, HTML links where mirrors exist
3 llms.jsonl — catalog llms_jsonl 0.75 technical JSON Lines validity, required fields, eCommerce fields
4 sitemap.xml sitemap 0.8 technical XML, lastmod, .gz, catalog disproportion
5 Product schema product_schema 1.0 technical JSON-LD on real product, offers, Hyvä detection
6 Merchant policies ★ NEW merchant_policies 0.9 technical hasMerchantReturnPolicy, OfferShippingDetails, priceValidUntil, itemCondition
7 Organization schema ★ NEW organization_schema 0.8 technical Organization / OnlineStore on homepage, sameAs, logo
8 UCP profile ★ NEW ucp_profile 0.9 technical /.well-known/ucp, signing keys, leaked-private-key detection
9 AI product feed ai_product_feed 1.0 feed Feed file, /.well-known/ai-plugin.json, REST endpoint
10 JSON-LD quality ★ NEW jsonld_quality 0.7 technical Breadcrumb, ItemList, WebSite+SearchAction, duplicate schemas
11 Canonical + hreflang canonical 0.7 technical Canonical agrees with og:url + JSON-LD url; hreflang on multi-store
12 Open Graph open_graph 0.7 technical All 5 OG tags, description length
13 FAQ schema faq_schema 0.5 technical FAQPage JSON-LD on homepage or sampled CMS page
14 Well-known matrix ★ NEW well_known 0.5 technical ucp / ai-plugin.json / security.txt / mcp inventory
15 Core Web Vitals core_web_vitals 0.5 external_api LCP / INP / CLS via Google CrUX (API key required)
16 WAF reality check ★★ waf_reality 0.9 technical Probes the edge with real AI crawler UAs — flags bots robots.txt allows but the WAF/CDN blocks (challenge pages detected even behind HTTP 200)
17 AI crawler activity ★★ ai_crawler_activity 0.5 live_signal Which AI crawlers actually visited, by class (search / training / fetch), from GDPR-safe evidence sources
18 A2A Agent Card ★★★ agent_card 0.6 technical /.well-known/agent-card.json — required when the UCP profile declares an a2a transport, informational otherwise; flags cards stranded at the pre-0.3 /.well-known/agent.json path
19 llms.txt v2 link relations ★ NEW link_relations 0.7 technical rel="alternate" type="text/markdown" + rel="describedby" in <head> or Link: header; follows both and fails on a declared-but-broken target; Link: header on the mirror; both page.html.md and page.md forms
20 agents.md ★ NEW agents_md 0.7 technical Delivery / returns / privacy actually reachable, linked policies, cross-reference to llms.txt, text/html shadowing, /sitemap_agentic_discovery.xml

★ = added in v3.0.0 · ★★ = added in v4.0.0 · ★★★ = added in v4.1.0.

The agent card is conditional on purpose

Almost no Magento store has opted into A2A, and scoring them all down for a
protocol they never adopted would make the number less useful, not more. So
agent_card asks the UCP profile first. If the store advertises an a2a
transport, the card is part of the contract and its absence is a FAIL. If
it does not, the signal passes with applicable: false in the details.

One case is flagged regardless: a card served only at /.well-known/agent.json,
the path A2A used before 0.3. A spec-compliant 1.0.0 client never looks there,
so the operator believes they have published a card that, to the protocol, does
not exist.

v4 grading philosophy: bots are judged by purpose

The bot catalog (BotRegistry) classifies every
AI agent as training (collects pages to train models), search
(indexes for answer engines — this traffic earns citations), or fetcher
(real-time page access for a user asking right now):

  • Blocking a search crawler (OAI-SearchBot, PerplexityBot,
    Claude-SearchBot) fails the audit — the store disappears from AI
    answers.
  • Blocking a training crawler (GPTBot, ClaudeBot, CCBot, Bytespider) is a
    respected licensing choice — reported, never punished. Opting out of
    model training does not remove a store from AI search.
  • Opt-out tokens (Google-Extended, Applebot-Extended) never crawl anything
    themselves and are reported as conscious-choice notices.

The evidence layer

waf_reality and ai_crawler_activity corroborate each other:

waf_reality ai_crawler_activity Diagnosis
edge blocks probe zero hits Real block — allow-list AI crawlers in your WAF/CDN bot management
edge blocks probe healthy hits Verified-bot program correctly rejecting UA spoofers — no action needed
consistent zero hits Store likely not yet discovered — work on feeds, llms.txt, citations
consistent search hits Fully AI-visible ✔

Evidence sources for ai_crawler_activity (pluggable via
Angeo\AeoAudit\Api\BotHitSourceInterface):

  1. Built-in instrumentation (default, zero-config): a frontend plugin
    counts AI bot requests at the PHP layer on every hosting model. Limitation
    reported honestly: requests served entirely from full-page cache never
    reach PHP and are not counted.

  2. Webserver access log (opt-in): set an absolute path under Stores →
    Configuration → Angeo AEO → Live Signal
    . Only the trailing 8 MB is read;
    combined and JSON-lines formats are auto-detected. Never chmod the
    live log
    — grant targeted read access instead:

    # point-grant read to the PHP user (survives permission audits):
    setfacl -m u:www-data:r /var/log/nginx/access.log
    
    # …or copy bot-filtered lines on rotation (logrotate postrotate):
    grep -Ei 'GPTBot|OAI-SearchBot|ClaudeBot|Claude-SearchBot|PerplexityBot|Amazonbot|Applebot|meta-externalagent|CCBot|Bytespider' \
      /var/log/nginx/access.log.1 > /var/log/ai-bots/access.log
    

    Behind a CDN the origin log under-counts (cache hits never reach the
    origin) — the report says so explicitly.

  3. Your own adapter (CDN analytics APIs are the highest-fidelity source —
    they see cache hits too). Implement BotHitSourceInterface, register via
    di.xml on the checker's sources argument.

Privacy: only aggregates — bot code, class, store, date, count — are ever
persisted (angeo_aeo_bot_hit). No IPs, no URLs, no raw user agents, no log
lines. Retention (default 90 days) is enforced by cron. Absence of any
source is an informational note, never a score penalty.


Installation

composer require angeo/module-aeo-audit
bin/magento setup:upgrade
bin/magento cache:flush

For full coverage, install the companion modules:

composer require \
  angeo/module-llms-txt \
  angeo/module-rich-data \
  angeo/module-openai-product-feed \
  angeo/module-openai-product-feed-api \
  angeo/module-ucp \
  angeo/module-aeo-brand-visibility

CLI usage

# Audit all stores
bin/magento angeo:aeo:audit

# Specific store
bin/magento angeo:aeo:audit --store=en_us

# JSON output (for dashboards / CI)
bin/magento angeo:aeo:audit --format=json

# Markdown report to file
bin/magento angeo:aeo:audit --format=markdown --output=/var/www/html/aeo-report.md

# Fast technical-only checks (skip external APIs)
bin/magento angeo:aeo:audit --category=technical

# Run only external-API checks (Core Web Vitals + any third-party live signals)
bin/magento angeo:aeo:audit --category=external_api,live_signal

# Fail build if score below threshold
bin/magento angeo:aeo:audit --fail-on=80

# Fail build if any critical-severity check fails
bin/magento angeo:aeo:audit --fail-on-severity=critical

# Run without saving to DB (CI / read-only environments)
bin/magento angeo:aeo:audit --no-save

Sample output:

  AEO Score: [████████████████░░░░] 81% — Good
  ✓ Pass: 12  ⚠ Warn: 3  ✗ Fail: 1

  Critical fixes needed:
  → Install angeo/module-openai-product-feed and register at chatgpt.com/merchants

  💡 Fix with angeo modules:
     composer require angeo/module-openai-product-feed angeo/module-openai-product-feed-api
     composer require angeo/module-ucp

Configuration

Some checkers need configuration. All are accessed via:
Stores → Configuration → Angeo AEO.

Setting Purpose
CrUX API Key Required by core_web_vitals checker. Free key from console.cloud.google.com — enable the Chrome UX Report API. Stored encrypted.

Admin UI

  • Marketing → Angeo AEO → AEO Audit Results — full history grid
  • Marketing → Angeo AEO → Score Trend — line chart of AEO score over time
  • ▶ Run Audit Now button (on the Audit Results grid, Score Trend, and result
    view pages) — triggers an on-demand audit. Since 3.1.0 this is a POST action
    protected by the admin form key and the dedicated
    Angeo_AeoAudit::run_audit ACL permission; the old GET menu entry was removed.

Score interpretation

Score Label Typical situation
0–25% Critical Default Magento install. AI crawlers blocked. No schema.
26–50% Needs Improvement Some fixes applied. Feed or merchant policies missing.
51–75% Needs Improvement Core signals in place. UCP, ai-plugin.json, or hreflang missing.
76–90% Good Strong foundation. Minor gaps in well-known or CWV.
91–100% Excellent Full 2026 AEO compliance.

Cron

Scheduled audit — default every Monday at 03:00 server time, configurable
under Stores → Configuration → Angeo AEO → Scheduled Audit since v4.0.0.
Results saved to DB (last 50 per store); expired bot-hit counters pruned per
the retention setting.

bin/magento cron:run --group=default

For fast daily checks (without external APIs or log scans), schedule an
additional cron job calling the audit with --category=technical.


Extending with custom checks

Implement Angeo\AeoAudit\Api\CheckerInterface (or extend
Angeo\AeoAudit\Model\Checker\AbstractChecker, which provides HTTP cache,
URL sampling and JSON-LD parsing), and register via di.xml:

<type name="Angeo\AeoAudit\Model\AuditRunner">
    <arguments>
        <argument name="checkers" xsi:type="array">
            <item name="my_check" xsi:type="object">Vendor\Module\Model\Checker\MyChecker</item>
        </argument>
    </arguments>
</type>

v3 interface:

public function getName(): string;       // "My Custom Check"
public function getCode(): string;       // "my_check"
public function getWeight(): float;      // 0.0–1.0
public function getCategory(): string;   // CheckerInterface::CATEGORY_*
public function getSeverity(): string;   // CheckerInterface::SEVERITY_*
public function getFixCommand(): string; // "composer require vendor/fix-module" or ""
public function check(\Magento\Store\Api\Data\StoreInterface $store): CheckResult;

Migrating from v2? See CHANGELOG.md for the migration guide.


Running tests

vendor/bin/phpunit -c app/code/Angeo/AeoAudit/phpunit.xml

v3 ships with unit tests covering all 15 checkers, both services
(HttpCache, StoreUrlSampler), the AuditRunner, and the report value
objects.


Code quality

# Magento Coding Standard
vendor/bin/phpcs --standard=Magento2 \
    --extensions=php,phtml --severity=10 \
    app/code/Angeo/AeoAudit/

# PHPStan static analysis
vendor/bin/phpstan analyse -l 5 app/code/Angeo/AeoAudit/

The Angeo AI Visibility Suite

Module Signal Purpose
angeo/module-aeo-audit This module — audit all 18 signals
angeo/module-robots-txt-aeo #1 Inject AI bot rules into robots.txt
angeo/module-llms-txt #2, #3 Generate llms.txt and llms.jsonl
angeo/module-rich-data #5, #6, #7, #13 Product, Organization, FAQ JSON-LD + merchant policies
angeo/module-openai-product-feed #9 ACP product feed for ChatGPT Shopping
angeo/module-openai-product-feed-api #9 REST API — 6 ACP endpoints
angeo/module-openai-instant-checkout Agentic Commerce Protocol — instant checkout from ChatGPT
angeo/module-ucp #8 Universal Commerce Protocol — /.well-known/ucp
angeo/module-aeo-brand-visibility (extends) Live AI visibility across ChatGPT, Claude, Perplexity, Gemini, Groq

Contributing

Issues and PRs welcome at github.com/angeo-dev/module-aeo-audit.

Before opening a PR:

  1. Run vendor/bin/phpunit -c phpunit.xml — all tests must pass
  2. Run vendor/bin/phpcs --standard=Magento2 — no MCS violations
  3. Add tests for any new checker

License

MIT — see LICENSE


Made with care by Ievgenii Gryshkun — open-source
contributions to the Magento + AI commerce ecosystem.

Changelog

All notable changes to angeo/module-aeo-audit will be documented in this file.

The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

[4.2.3] — 2026-09-16

Patch release. Build, packaging and code-quality fixes found while
preparing the module for the Mage-OS Extension Directory. No change to
signals, weights or scoring.

Fixed

  • Audit result page could fatal. AuditResult\View called
    setAuditResult() with the nullsafe operator, but getBlock() returns
    false, not null, when the block is missing. The block type is now checked.
  • Unescaped output in admin templates (Magento coding standard errors):
    score_trend.phtml and auditresult/view.phtml. Values were already
    numbers or fixed colours; they now go through the escaper or an (int) cast.
  • CMS page sampling passes the store ID to addStoreFilter() instead of the
    store object.
  • Collection filters use ['eq' => …]. Same SQL.
  • HttpCache: removed a method_exists() check that was always true.

Changed

  • PHP 8.1–8.5 (was 8.2–8.4). The code uses no PHP 8.2+ syntax or
    functions; CI now runs on 8.1 too.
  • Dev tooling updated to PHPUnit 10.5, PHPStan 2, bitexpert/phpstan-magento
    and magento/magento-coding-standard ^40 || ^41. phpunit.xml uses the
    10.5 <source> element.
  • phpstan.neon rewritten to the suite-wide config (level 5 with
    bitexpert/phpstan-magento). The old config ignored every undefined Magento
    method and every invalid Magento type. The extension generates Magento
    factory classes during analysis, so no stub files are needed.
  • CI (.github/workflows/ci.yml) now matches the rest of the suite: one job
    per PHP version 8.1–8.5 running composer validate, lint, Magento coding
    standard and PHPUnit, plus one PHPStan job on PHP 8.2. The Mage-OS installability job is kept.

Build

  • Unit tests run against real Magento classes: Store mocks use
    Magento\Store\Model\Store (for getBaseUrl()), generated factories are
    declared by Test/Unit/bootstrap.php, and the agents.md "missing policies"
    fixture is long enough not to count as a placeholder.

Documentation

  • README: one badge row across the suite — CI, Packagist version and
    downloads, PHP 8.1 – 8.5, supported Magento range, Mage-OS Extension
    Directory, license.

Quality

  • Magento2 coding standard: 0 errors. PHPStan level 5: no errors.

[4.2.0] — 2026-09-08

Minor release. Brings the llms.txt signal up to llmstxt.org v2
(10 August 2026) and adds two signals for the discovery layer that v2 and
Shopify's rollout turned into an expectation. No breaking changes; upgrading
from 4.1.0 is drop-in (composer update, then bin/magento setup:upgrade && bin/magento setup:di:compile).

Added

  • link_relations signal — llms.txt v2 link relations (weight 0.7).
    v1 said what an llms.txt file should contain; v2 added how an agent finds
    the markdown version of a page and the llms.txt covering it without guessing
    URLs. The checker samples a product page and looks for
    rel="alternate" type="text/markdown" and rel="describedby" — in the HTML
    <head> or in the HTTP Link: header, both of which the spec accepts.

    It then follows them. A relation that points at a 404 fails the check:
    declaring a markdown twin that does not exist is worse than declaring
    nothing, because the agent follows it and fails. It also checks the Link:
    header on the mirror itself — markdown has no <head> to carry the
    relation — and probes both URL forms v2 permits (page.html.md and
    page.md), reporting a missing second form as a warning, since serving one
    is compliant.

  • agents_md signal — /agents.md (weight 0.7). Distinct from the
    agent_card signal: /.well-known/agent-card.json is a machine-readable
    A2A capability descriptor, agents.md is prose an LLM reads to learn how the
    shop expects to be dealt with. Shopify rolled it out across its stores and
    then made it canonical, with llms.txt pointing at it.

    The check is about consequence, not length: whether an agent about to
    recommend or transact can find the delivery, returns and privacy terms it
    must quote. A file with none of them warns — an agent left to summarise
    policy from memory is how wrong refund windows reach shoppers. Also flags a
    file served as text/html (usually a CMS page shadowing the real file) and
    a missing /sitemap_agentic_discovery.xml.

Changed

  • llms_txt signal aligned with v2.
    • A bare > is now an issue. An empty blockquote reads to a parser as the
      spec's summary and carries nothing. Previously it was counted as a valid
      description.
    • A heading between the summary and the first H2 is now an issue. v2 allows
      markdown of any type in that gap except headings.
    • Warns when links point at HTML pages while the store demonstrably serves
      markdown mirrors — v2 asks that llms.txt links lead to LLM-friendly
      content. Only raised where a mirror actually resolves, so a store that has
      not adopted them is never nagged.
    • The over-size message no longer claims llms-full.txt is required by the
      spec. It is a Mintlify convention. The spec's own answer to a large file is
      fewer links with more detail behind them.
    • ## Optional is neither required nor rewarded: v2 removed its mechanical
      meaning, so it no longer tells any tool what to drop.

Notes

  • Both new signals are module-independent. A store that hand-writes the tags
    and serves markdown from nginx passes identically to one running
    angeo/module-llms-txt; the fix command is a suggestion shown on failure,
    not a precondition for passing.
  • Both are technical category, so they run under
    bin/magento angeo:aeo:audit -c technical and add no external API cost.

Minor release. Adds an A2A Agent Card signal, refreshes the ACP framing of
the product-feed signal, and teaches the UCP checker the difference between a
superseded revision and an unknown one. No breaking changes; upgrading from
4.0.0 is drop-in (composer update, then bin/magento setup:upgrade && bin/magento setup:di:compile).

Added

  • agent_card signal — A2A Agent Card (weight 0.6). Checks
    /.well-known/agent-card.json. A2A reached 1.0.0 under Linux Foundation
    governance, and UCP 2026-04-08 lists a2a alongside rest, mcp and
    embedded as a transport a business may advertise — pointing at exactly this
    path.

    The signal is conditional by design, so it does not penalise the large
    majority of stores that have never opted into A2A:

    • UCP profile declares an a2a transport → the card is required. Missing, or
      served only at the legacy path → FAIL.
    • No a2a transport declared → the card is optional and its absence is a
      PASS carrying applicable: false.

    The defect it exists to catch is the path migration. Before A2A 0.3 the card
    lived at /.well-known/agent.json, and a large share of published cards are
    still there — where a spec-compliant 1.0.0 client never looks. A card at the
    legacy path only is flagged whether or not a2a is declared, because it is a
    live misconfiguration either way. Cards served at both paths get a WARN:
    two copies drift apart.

    Structural validation covers the fields A2A requires (name, url,
    version) and the ones that make a card useful rather than merely
    discoverable (description, capabilities, skills). The UCP transport
    lookup walks the manifest structurally rather than assuming one shape, since
    the services block nests differently across UCP revisions.

Changed

  • Product-feed signal re-framed around ACP as a discovery surface. ACP
    shipped in September 2025 built around Instant Checkout; OpenAI retired that
    flow on 24 March 2026 and refocused the protocol on product discovery, with
    merchants pushing catalog feeds and promotions over ACP while keeping
    checkout on their own store. The 2026-04-17 revision formalised the shift,
    adding cart, feed, orders, authentication and MCP transport. The checker's
    logic is unchanged — the feed was always what it measured — but its
    documentation no longer describes a checkout integration that no longer
    exists.
  • UCP checker distinguishes superseded revisions from unknown ones.
    2026-01-11 and 2026-01-23 are published UCP revisions. A manifest on one
    of them previously produced "not in known set — may be newer than this audit
    knows", which was the opposite of true. Those now warn that the profile is
    behind 2026-04-08 and name what that revision added; a genuinely
    unrecognised version string still warns that it may be newer than this
    release.
  • Audit user agent bumped to AngeoAeoAudit/4.1.

Notes on scoring

The new signal changes composite scores for stores that declare an a2a
transport without serving a card. Stores that do not use A2A are unaffected:
the signal passes for them.


[4.0.0] — 2026-07-02

Major release: the audit grows an evidence layer. Every previous signal
answered "is the door configured to be open?"; v4 adds two signals that
answer "did anyone actually walk through it?" — an edge/WAF reality probe
and observed AI crawler activity from pluggable, GDPR-safe evidence
sources. Grading is rewritten around bot purpose (training vs search vs
fetch), CI ships in the repository, and three shipped-broken test classes
plus a security bug in the CrUX checker are fixed. BC breaks listed below.

Added

  • waf_reality signal — Edge vs robots.txt consistency (weight 0.9).
    Fetches the homepage presenting real AI crawler user agents
    (OAI-SearchBot, PerplexityBot, GPTBot, ClaudeBot) and compares the edge's
    behaviour against what robots.txt declares. WAF/CDN rules execute before
    robots.txt is ever read, so a managed challenge rule silently overrides
    every Allow a merchant writes — the classic "why does no AI engine ever
    cite us?" failure. Block-page fingerprints (Cloudflare, Imperva,
    PerimeterX, Akamai) are detected even behind HTTP 200. Mismatches WARN
    rather than FAIL, with an explicit verified-bot caveat: edges that validate
    crawler source IPs may correctly reject the spoofed probe while admitting
    genuine bots — cross-reference the activity signal before acting.
  • ai_crawler_activity signal — observed AI crawler traffic (live_signal,
    weight 0.5, severity informational).
    Merges evidence from pluggable
    BotHitSourceInterface adapters and grades by bot class: search/fetch
    crawlers seen → PASS; only training crawlers → WARN ("you feed models but
    earn no citations"); silence → WARN with cross-reference to waf_reality.
    Never FAILs and never fails a CI build by design — evidence coverage is
    inherently partial.
  • Evidence sources (Angeo\AeoAudit\Api\BotHitSourceInterface):
    • Built-in instrumentation (default ON): a frontend-area plugin counts
      AI bot requests at the PHP layer. Zero configuration, zero filesystem
      access, works on every hosting model. Honest limitation surfaced in
      every report: full-page-cache hits never reach PHP and are not counted.
    • Webserver access log (opt-in): tail-reads the last 8 MB of a
      configured nginx/apache log (combined or JSON lines, auto-detected).
      Documentation recommends targeted ACLs (setfacl) or logrotate copy
      hooks — never chmod on the live log.
    • Third-party adapters (CDN analytics, Fastly, Cloudflare) register via
      di.xml on the checker's sources argument.
  • Privacy by architecture: only aggregates (bot code, class, store, date,
    count) are ever persisted to the new angeo_aeo_bot_hit table — no IPs, no
    URLs, no raw user agents, no log lines. Retention (default 90 days) is
    enforced by the scheduled cron.
  • Angeo\AeoAudit\Service\BotRegistry — single purpose-classified
    catalog of 17 AI agents (training / search / fetcher / opt-out token) with
    UA and robots.txt tokens, shared by all bot-aware components so no bot is
    ever classified two different ways in two places.
  • GitHub Actions CI (.github/workflows/ci.yml): coding standard,
    PHPStan, unit tests on PHP 8.2/8.3/8.4, and an installability job against
    the Mage-OS composer mirror — every green build doubles as a Mage-OS
    compatibility proof.
  • i18n/en_US.csv — base translation dictionary for all admin strings.
  • Configurable cron schedulecrontab.xml now reads
    angeo_aeo/cron/schedule (Stores → Configuration → Angeo AEO → Scheduled
    Audit); default unchanged (0 3 * * 1).
  • Vendor-neutral fix hints — suggestions are rephrased ("any module
    providing the signal works — e.g.") and can be disabled entirely via
    General → Show fix suggestions, a hard requirement for
    distribution/bundling contexts.
  • HttpCache::getAs($url, $userAgent) — UA-aware cached GET; the cache key
    includes the UA because the same URL may legitimately answer differently
    per agent.
  • Angeo\AeoAudit\Service\AuditResultPersister — single owner of the
    populate → save → prune flow.

Changed

  • robots_txt grading rewritten around bot purpose (BC in behaviour).
    Blocking a search-class crawler (OAI-SearchBot, PerplexityBot,
    Claude-SearchBot) FAILs — it removes the store from AI answers. Blocking a
    training-class crawler (GPTBot, ClaudeBot, CCBot, …) is now a respected
    licensing choice: reported in details, never punished. Previously GPTBot
    was graded "critical" and its block FAILed the audit — that conflated
    training opt-out with search invisibility.
  • robots.txt parsing extracted to the shared
    Angeo\AeoAudit\Service\RobotsTxtParser so robots_txt and
    waf_reality resolve "is bot X invited?" with identical logic.
  • CLI, cron and admin RunNow now persist through AuditResultPersister
    (removes 3× duplicated logic).
  • Audit User-Agent bumped to AngeoAeoAudit/4.0.
  • composer.json support URLs now point at the actual repository.

Fixed

  • Security — CrUX checker (core_web_vitals): the 3.1.0 implementation
    instantiated a raw Curl outside DI with TLS verification disabled
    (CURLOPT_SSL_VERIFYPEER/HOST = false), sent the API key in the URL
    query string
    (leaks into proxy/access logs), and never decrypted the
    stored value even though the config backend is Backend\Encrypted — so a
    saved key was sent as ciphertext and every call failed. Now: key is
    decrypted (undecryptable ⇒ treated as unconfigured), sent via the
    X-Goog-Api-Key header, and the request goes through HttpCache::post
    with TLS verification on. The existing unit tests already demanded exactly
    this behaviour — they simply never ran (see below).
  • Four test classes shipped broken in 3.1.0 (AuditRunnerTest,
    SitemapXmlCheckerTest — pre-Config constructor signatures;
    MerchantPoliciesCheckerTest, SitemapXmlCheckerTest::disproportion
    assertions against removed v3.0 behaviour). They never ran because no CI
    existed; all fixed, full suite green (142 tests).

BC breaks

  • RobotsTxtChecker::__construct() now requires BotRegistry and
    RobotsTxtParser (DI resolves this automatically; only manual
    instantiation is affected).
  • CoreWebVitalsChecker::__construct() now requires EncryptorInterface.
  • AeoAuditCommand, AuditCron, RunNow constructor signatures changed
    (persister-based). All DI-resolved.
  • robots_txt scoring changed as described above — stores that block GPTBot
    will see their score rise; stores that block OAI-SearchBot will see it
    fall. Both are corrections, not regressions.
  • New DB table angeo_aeo_bot_hit (created by setup:upgrade).

[3.1.0] — 2026-06-10

Minor release. Adds per-signal enable/disable configuration, configurable
sitemap placeholder-slug handling, and fixes two false-signal bugs in the
sitemap checker. Fully backward compatible — no interface or DB changes.

Added

  • Per-signal configuration. Every one of the 15 signals can now be enabled
    or disabled individually under Stores → Configuration → Angeo AEO → AEO
    Audit → Signals (Checks)
    . All signals are enabled by default, so a
    fresh install behaves exactly as before. Disabled signals are skipped during
    the audit and excluded from the score entirely — they neither raise nor lower
    it (removed from both numerator and denominator). Settings are store-scoped.
  • Configurable sitemap placeholder-slug handling. New group Angeo AEO →
    AEO Audit → Sitemap Checker
    :
    • Placeholder slug handlingAffect score (default) or Ignore
      (report-only, never changes status/score).
    • Placeholder slug threshold — how many placeholder slugs are tolerated
      before the score is affected (default 1).
  • New Angeo\AeoAudit\Model\Config — a single typed reader for all module
    settings, so checkers no longer touch ScopeConfig directly.
  • New Angeo\AeoAudit\Model\Config\Source\SlugMode option source.
  • Unit tests: disabled-checker skipping in AuditRunner; sitemap foreign-element
    FAIL; placeholder-slug score/ignore modes; disproportion-false-positive
    regression.

Fixed

  • Sitemap: false "disproportion" warning. The v3 check compared sitemap URL
    count against active products only, but a sitemap also lists the homepage,
    CMS pages and categories — so healthy stores were frequently warned. URL count
    is now compared against the full indexable surface (products + categories +
    CMS pages) and reported as informational context only (coverage_ratio);
    it never changes the result status.
  • Sitemap: false "stale" warning. Staleness was computed from the first
    <lastmod> in the file, so a single old entry (often the homepage or a CMS
    page) flagged the whole sitemap as stale. A legitimately unchanged product
    should keep an old <lastmod> — that is honest metadata, not a defect. The
    check now inspects the newest <lastmod> across the file and only warns
    if nothing at all has changed in 180 days (a sign of a broken generation
    cron). Individual old entries are informational only.

Added — sitemap structural integrity

  • Sitemap: foreign-element detection. Non-sitemap elements injected directly
    into <urlset> (e.g. a stray <script> from a theme or module) are now
    detected and reported as a FAIL. libxml parses such markup without error, so
    the previous XML-validity check missed it.
  • Sitemap: placeholder-slug detection. Slugs that carry no meaning for an AI
    engine (test2.html, product-name.html, bare numbers, etc.) are surfaced so
    they can be renamed. Behaviour is controlled by the new configuration above.

[3.0.0] — 2026-05-22

Major release. Adds 6 new checkers, refactors the checker architecture, and
requires changes in third-party modules that implement CheckerInterface.

⚠️ Breaking changes

  • CheckerInterface::check() signature changed from
    check(string $baseUrl): CheckResult to
    check(\Magento\Store\Api\Data\StoreInterface $store): CheckResult.
    Custom checkers must be updated. The base URL is available via
    $store->getBaseUrl() or StoreUrlSampler::getBaseUrl($store).
  • CheckerInterface adds two required methods: getCategory(): string and
    getSeverity(): string. Subclassing AbstractChecker provides sensible
    defaults (technical / weight-derived severity). Custom checkers extending
    the interface directly need to implement both.
  • AbstractChecker constructor signature changed. Now requires
    HttpCache and StoreUrlSampler instead of Curl. DI handles this
    automatically for checkers that don't override the constructor.

Added — 6 new checkers (now 15 total signals)

  • MerchantPoliciesChecker — validates hasMerchantReturnPolicy +
    OfferShippingDetails + priceValidUntil + itemCondition on a sampled
    product. Required by Google AI Mode and ChatGPT Shopping since Jan 2026.
    Weight 0.9.
  • OrganizationSchemaChecker — validates Organization / OnlineStore
    JSON-LD on the homepage. Establishes brand entity in AI knowledge graphs.
    Weight 0.8.
  • UcpProfileChecker — validates /.well-known/ucp (Universal Commerce
    Protocol, integration with angeo/module-ucp). HTTPS-only, JWK validation
    including leaked-private-key detection (CRITICAL security check).
    Weight 0.9.
  • JsonLdQualityChecker — three-page scan (home + product + category) with
    @context validation, duplicate-schema detection, BreadcrumbList /
    ItemList / WebSite+SearchAction presence. Weight 0.7.
  • WellKnownAggregateChecker — inventory matrix for /.well-known/ucp,
    ai-plugin.json, security.txt, mcp. Weight 0.5.
  • CoreWebVitalsChecker — LCP / INP / CLS via Google CrUX API
    (requires API key under Stores → Configuration → Angeo AEO).
    Category external_api. Weight 0.5.

Added — architecture

  • Service\HttpCache — request-scoped HTTP cache. Eliminates duplicate
    fetches across checkers (a single runAll() for 10 stores went from
    hundreds of HTTP requests to a few dozen).
  • Service\StoreUrlSampler — centralized product / category / CMS URL
    sampling, memoized per store. Replaces ad-hoc sampling logic inside
    individual checkers.
  • --category CLI flag — filter checkers by category
    (technical|live_signal|external_api|feed). Useful for fast cron checks.
  • --fail-on-severity CLI flag — fail the build on critical / important
    / info severity. Complements --fail-on=<score> for CI.
  • Per-checker timeout logging — slow checkers (>30s) emit warning to log;
    checker exceptions no longer halt the audit run.
  • Test/Unit/Model/Checker/CheckerTestHelper trait — shared test scaffolding
    for checker unit tests.

Enhanced — existing checkers

  • RobotsTxtChecker: detects versioned UAs (GPTBot/1.0), Crawl-delay
    on bots that ignore it, HTTP sitemap directives, conflicting Allow: /
    Disallow: rules. AI bot list expanded to 12.
  • SitemapXmlChecker: detects sitemap.xml.gz, compares URL count to
    active catalog product count (warns on >30% delta).
  • LlmsTxtChecker: validates store-locale + currency match metadata;
    flags cross-host links on subdomain stores.
  • CanonicalChecker: now cross-checks canonical against og:url and
    Product JSON-LD url; verifies HTTPS; checks hreflang presence on
    multi-store setups.

Considered and rejected — ai_bot_traffic checker

An access-log-based AI-bot traffic checker was prototyped during the v3
development cycle and excluded from the release after a security and
usefulness review. The summary, recorded so the trade-off is documented:

  • Encouraged poor permissions hygiene. The natural way to make
    /var/log/nginx/access.log readable to PHP-FPM is usermod -aG adm www-data or chmod 644, both of which expose unrelated sensitive logs
    (auth.log, syslog) to any future LFI/RCE in the application. The
    bundled ACL guidance helped, but a module whose presence creates the
    incentive at all violates "secure by default".
  • Unusable on managed platforms. On Adobe Commerce Cloud, Magento
    Cloud, and any containerised hosting, nginx logs go to stdout and
    centralised collection (Fastly/New Relic/Splunk). PHP-FPM cannot read
    them at all. The check returns WARN on these platforms 100% of the
    time, contributing only noise.
  • Dominated by false positives. Even on self-hosted setups, sites
    behind Cloudflare/Fastly with edge caching never see the AI bots reach
    origin — the bots are served from edge. WARN again.
  • Better-served externally. Edge analytics (Fastly, Cloudflare
    Analytics), APM platforms (New Relic, Datadog), and dedicated log
    analyzers (GoAccess, Matomo) measure AI-bot traffic without coupling
    it to PHP application permissions.

This means 15 built-in signals, not 16. The live_signal category
remains in CheckerInterface for third-party modules that have their own
secure live-signal source — notably angeo/module-aeo-brand-visibility,
which queries AI provider APIs rather than parsing host logs.

Configuration

  • New encrypted config field: angeo_aeo/crux/api_key
    (Stores → Configuration → Angeo AEO → CrUX API Key).

Suggested

  • New suggest entry: angeo/module-ucp — companion module for UCP profile.
  • New suggest entry: angeo/module-aeo-brand-visibility — live AI
    visibility checker (adds a brand_visibility signal via DI injection).

Migration guide for v2 → v3

For most users (using only built-in checkers): composer update. No code
changes needed.

For custom checkers extending AbstractChecker: update the check() signature:

- public function check(string $baseUrl): CheckResult
+ public function check(\Magento\Store\Api\Data\StoreInterface $store): CheckResult
  {
-     [$status, $html] = $this->fetch($baseUrl . '/path');
+     $base = $this->urlSampler->getBaseUrl($store);
+     [$status, $html] = $this->fetch($base . '/path');
  }

For custom checkers implementing CheckerInterface directly: also add
getCategory() and getSeverity(). Sensible defaults:

public function getCategory(): string { return CheckerInterface::CATEGORY_TECHNICAL; }
public function getSeverity(): string { return CheckerInterface::SEVERITY_IMPORTANT; }

[2.1.2] — 2026-05-01

Fixed

  • Recursive @graph parsing in JSON-LD extraction — handles nested @graph and top-level array roots correctly
  • Bug-report URL in fallback error path now points to the correct repository
  • Composer constraint accuracy: explicit ^ ranges for Magento dependencies instead of *
  • Test/ directory excluded from production classmap

Added

  • Unit tests for ProductSchemaChecker, FaqSchemaChecker, ProductFeedChecker, LlmsJsonlChecker (9 of 9 checkers now have tests)
  • CHANGELOG.md and CONTRIBUTING.md
  • GitHub Actions CI workflow (PHPUnit + PHPStan + MCS)
  • Magento Coding Standard as a require-dev dependency

Changed

  • README updated with explicit Magento version compatibility (2.4.6, 2.4.7, 2.4.8)
  • README mentions tested PHP versions (8.2, 8.3, 8.4)
  • Removed hardcoded version field from composer.json — Packagist resolves from git tags

[2.1.1] — 2026-04-24

Added

  • getFixCommand() method on CheckerInterface for dynamic CLI fix suggestions
  • LlmsJsonlChecker for /llms.jsonl validation
  • Score Trend dashboard in admin UI

[2.1.0] — 2026-04-15

Added

  • Score Trend dashboard
  • Dynamic fix commands in CLI output
  • Deeper llms.txt validation (12 checks)

[2.0.0] — 2026-03-20

Added

  • Deep robots.txt parser with first-match semantics
  • Product schema validation including offers.availability
  • Hyvä theme detection
  • Admin UI with results grid
  • Cron scheduling (weekly Monday 03:00)
  • Extensible architecture via CheckerInterface + di.xml

Changed

  • Weighted scoring: critical signals weight 1.0, informational lower

[1.0.0] — 2026-02-10

Added

  • Initial release with 6 AEO signal checks
  • CLI command bin/magento angeo:aeo:audit
  • Table, JSON, and Markdown output formats
Versions
Version Stability QA Status Compatibility Released
4.2.3 stable Fail Magento 2.4.7-2.4.9 Details 2026-09-16 20:24:50
4.2.2 stable Fail Magento 2.4.7-2.4.8 Details 2026-09-11 16:38:02
4.2.1 stable Not tested Not yet tested Details 2026-09-11 16:35:49
4.2.0 stable Not tested Not yet tested Details 2026-09-11 16:32:51
4.0.0 stable Not tested Not yet tested Details 2026-09-11 16:30:47
3.2.0 stable Fail Magento 2.4.7-2.4.9 Details 2026-07-15 18:30:36
3.1.0 stable Fail Magento 2.4.7-2.4.8 Details 2026-06-14 18:50:44
3.0.0 stable Not tested Not yet tested Details 2026-05-28 18:54:28
2.1.2 stable Not tested Not yet tested Details 2026-05-15 10:02:00
2.1.1 stable Not tested Not yet tested Details 2026-04-24 20:27:14
2.1.0 stable Not tested Not yet tested Details 2026-04-24 19:37:35
2.0.1 stable Not tested Not yet tested Details 2026-04-19 18:21:18
2.0.0 stable Not tested Not yet tested Details 2026-04-16 19:27:52
1.0.0 stable Not tested Not yet tested Details 2026-04-02 18:37:34

Requires 8

Package Constraint
magento/framework ^103.0
magento/module-backend ^102.0
magento/module-catalog ^104.0
magento/module-cms ^104.0
magento/module-config ^101.2
magento/module-store ^101.1
magento/module-ui ^101.2
php ~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0

Requires-dev 4

Package Constraint
bitexpert/phpstan-magento ^0.43
magento/magento-coding-standard ^40.0 || ^41.0
phpstan/phpstan ^2.0
phpunit/phpunit ^10.5

Suggests 7

Package Reason
angeo/module-aeo-brand-visibility Live AI visibility check across ChatGPT, Claude, Perplexity, Gemini
angeo/module-llms-txt Generate llms.txt and llms.jsonl
angeo/module-openai-product-feed ACP product feed for ChatGPT Shopping
angeo/module-openai-product-feed-api REST API endpoints for ACP feed
angeo/module-rich-data Product, Organization, FAQ JSON-LD and merchant policies
angeo/module-robots-txt-aeo Fix robots.txt AI bot access
angeo/module-ucp Universal Commerce Protocol profile (/.well-known/ucp) for Google AI Mode / Gemini

Compatibility

Each Magento release line is installed on its supported PHP versions, then the module is built (DI compilation + static-content deploy) and its unit and integration suites are run. The matrix shows the lines and PHP versions the module is confirmed to install and run on. Code-quality results further down (phpstan, phpcs, …) are reported separately and never affect compatibility.

Compatibility matrix (Magento × PHP)
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Pass Pass
2.4.8 Pass Pass
2.4.9 Pass Pass

Code Quality

Advisory checks against the module's source. Static analysis runs once across the whole module; PHPStan re-runs per Magento + PHP version because resolvable symbols differ between releases. These NEVER affect the Compatibility badge. A phpcs finding can't make a module incompatible.

Static analysis

Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.

Static analysis results
Tool Status Findings Summary
PHPCS Warning 76 76 warnings (ruleset: Magento2), 18 auto-fixable with phpcbf
PHPMD Warning 68 68 rule violations (NPathComplexity:19, CyclomaticComplexity:17, MissingImport:12, ExcessiveMethodLength:11, UnusedFormalParameter:5)
Cpd Pass 0
Composer validate Info 1 valid; 1 advisory note (composer validate --strict)

PHPStan

Type-checks the module's PHP against a real Magento install at the configured gate level. Re-runs per Magento and PHP version because resolvable symbols differ between releases.

PHPStan results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 4 4
2.4.8 4 4
2.4.9 4 4

Tests

Unit and integration suites, run for each applicable Magento and PHP version. A test failure speaks to the module's behaviour, not its compatibility with a Magento line, so it is reported here separately and never reddens the compatibility matrix.

Unit tests

Unit tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Pass Pass
2.4.8 Pass Pass
2.4.9 1 1

Integration tests

Integration tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 N/A N/A
2.4.8 N/A N/A
2.4.9 N/A N/A

Security

Security checks run directly against the module: an audit of its declared dependencies for known vulnerabilities (composer audit) and a scan of its source for malware and web-shell signatures. Each runs once. A malware detection fails the version outright.

Security results
Tool Status Findings Summary
Composer audit Pass 0
Malware scan Pass 0
License
MIT
Homepage
https://angeo.dev
Authors

More from angeo

View vendor
angeo/module-llms-txt Free
magento2-module

Magento 2 module for AI Engine Optimization (AEO). Generates spec-compliant llms.txt and llms-full.txt per llmstxt.org standard, plus streaming JSONL for vector indexing. Multi-store, multi-website, CLI, cron, async admin UI, Page Builder-aware sanitization, customer-group pricing, atomic writes, ETag/Cache-Control, .md mirrors.

v4.3.3 5d ago
0
angeo/module-robots-txt-aeo Free
magento2-module

Magento 2 module for AI Engine Optimization (AEO). Injects AI crawler rules (OAI-SearchBot, GPTBot, ChatGPT-User, PerplexityBot, Perplexity-User, Google-Extended, ClaudeBot, anthropic-ai, Claude-User, Applebot, cohere-ai, Amazonbot, Meta-ExternalAgent) into robots.txt — without overwriting your existing configuration. Supports per-bot Allow/Disallow lists, Crawl-delay, Sitemap directives, multi-store, and a public Api\RobotsStatusInterface for cross-module integration with angeo/module-aeo-audit.

v4.0.0 15d ago
0
angeo/module-aeo-brand-visibility Free
magento2-module

Live AI brand visibility audit for Magento 2. Queries ChatGPT, Claude, Perplexity, Gemini and Groq with brand-probing prompts and scores real-world AI recall, citation rate and recommendation presence. Extends angeo/module-aeo-audit v3 via CheckerInterface as the 16th signal, alongside the 15 built-in technical checks.

v4.0.1 4d ago
0
angeo/module-ucp Free
magento2-module

Spec-compliant Universal Commerce Protocol (UCP) profile generator for Magento 2. Generates /.well-known/ucp at protocol version 2026-04-08 with ECDSA P-256 signing keys, declared capabilities, and proper cache headers. v0.1.x is profile-only — catalog, cart, checkout endpoints land in later releases.

v2.1.1 3d ago
0
Make it pay

Turn an existing module into recurring revenue.

If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.