=== SummitCite ===
Contributors: pressroost
Tags: aeo, ai, seo, schema, llms-txt
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 8.3
Stable tag: 1.7.3
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

WordPress-native Answer Engine Optimization: emit AI-readable signals, measure AI crawler activity, and feed the results back into your content.

== Description ==

SummitCite treats Answer Engine Optimization as a closed loop rather than a
one-way broadcast: emit the signals AI engines read, measure which AI crawlers
actually arrive, score the content they lift answers from, and show you where
the two disagree.

Everything runs inside your own WordPress. No API key, no account, no
subscription, and nothing sent to PressRoost servers.

**Six things it does**

* **Crawler log.** Records requests from known AI crawlers - training,
  search and assistant bots - and reports them on one screen: which bots
  arrived in the last seven days, how often, when each was last seen, and the
  individual requests newest first. Retention is yours to set, from 1 to 3,650
  days, pruned daily; 90 by default.
* **Robots policy.** Declares, per bot, whether it may read your site - Allow,
  Block, or don't manage - and writes that into the robots.txt WordPress
  already generates, inside one marked block. A separate audit screen fetches
  your live `/robots.txt` and reports, per bot, what you configured against what
  the file actually grants, and names the mismatch when there is one.
* **Schema engine.** Emits one Article or BlogPosting block on every eligible
  published record, plus a FAQPage and a HowTo block when the content qualifies.
  It stands aside entirely when an SEO plugin is already emitting content-level
  structured data, and the Schema screen shows that decision per plugin with the
  evidence it used. A preview shows the exact JSON-LD it would build for any
  record you pick.
* **llms.txt.** Serves a plain-text inventory of your public content at
  `/llms.txt` - a bounded, deterministic document that lists only what you
  choose, or a sensible automatic selection until you do. It stands aside when
  a file already exists in your site root or when Yoast SEO or Rank Math is set
  to provide one.
* **Answer density.** Classifies and scores every post for how well it can
  answer a question: coverage, quality and structure, each scored separately and
  shown with the raw coverage ratio, the per-dimension points and the algorithm
  version that produced them. Scan one post or the whole library, results are
  versioned and re-scanned when content changes, and the list is ordered
  worst-first so the work that pays is at the top.
* **Conflict detector.** One inventory of what else on the site touches
  robots.txt, structured data, llms.txt and answer scoring - five SEO plugin
  families, each with the evidence for who owns what, and a recommendation
  where ownership is contested. Retest on demand.

**One dashboard, one settings screen.** The Dashboard rolls the six modules into
a single operational-evidence score with a colour-coded gauge, four summary
cards, a per-component breakdown and 14 days of crawler activity as a bar chart
with the same numbers in an accessible table underneath. Every module's
controls live on one Settings screen, in one form, saved once.

**What the numbers do not mean**

Stated here because every figure on the screen is bounded by them:

* **A recorded visit is a User-Agent claim, not a verified identity.** Any
  client can send any header, and SummitCite performs no reverse-DNS or IP
  reputation check.
* **The log is a lower bound.** SummitCite measures inside WordPress, so a request
  answered by a CDN, a reverse proxy or a full-page cache never reaches PHP and
  is never counted.
* **A Block rule asks; it does not enforce.** robots.txt is a declaration,
  honoured by the crawlers that choose to honour it.
* **Nothing here guarantees a citation, a ranking or a rich result.** Those are
  decided by the consumer, not by this plugin.

**Three principles**

* **Progressive enhancement.** Every emission feature works on WordPress 6.5
  with no AI involvement at all. The WordPress 7.0 AI Client API enhances the
  AI-driven features where it is present.
* **No API keys.** SummitCite bundles no AI provider. Where AI is used the call
  goes through WordPress core, using the connection you configured yourself.
* **Coexistence first.** SummitCite steps aside rather than emitting duplicate
  output, and decides on evidence rather than on whether a plugin happens to be
  active.

== External services ==

This plugin does not connect to any external service and does not send data
anywhere. There is no account, no API key, no licence check and no usage
telemetry, and nothing about your site leaves your server.

The crawler screens link out to the published bot documentation of the
companies whose crawlers are listed, so you can check which user agent a row
refers to:

* OpenAI - https://developers.openai.com/api/docs/bots
* Anthropic - https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler
* Perplexity - https://docs.perplexity.ai/guides/bots
* Meta - https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/
* Common Crawl - https://commoncrawl.org/ccbot

These are plain hyperlinks. They open in a new tab when clicked and are never
requested by the plugin itself, so no request, referrer or site data is sent to
those services. Each site is governed by its own terms of service and privacy
policy.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/summitcite`, or install the
   ZIP through Plugins > Add New.
2. Activate the plugin through the Plugins screen.

Activation creates three custom database tables, stores the default settings in
a single `aive_settings` option and schedules two maintenance events. Everything
is on from the moment you activate; every module can be switched off under
**SummitCite > Settings**.

Crawler logging records from the first matching request. Find the results under
**SummitCite > Crawler Log**. The screens need the `manage_options` capability, so
an administrator sees them and no one else does.

The robots policy writes into the robots.txt WordPress generates, inside one
marked block, and only when nothing else owns robots.txt - no file in the site
root, no SEO plugin with its own robots editor switched on, and search engines
not discouraged under Settings > Reading. Edit the rules under **SummitCite >
Settings > Robots**, and see what the live file grants each bot under
**SummitCite > Robots Audit**.

The schema engine emits on every eligible published post, page or public custom
post type, unless evidence shows Yoast SEO, Rank Math, SEOPress, All in One SEO
or The SEO Framework is already emitting content-level structured data, in which
case SummitCite emits nothing. **SummitCite > Schema** lists the generation rules,
names who owns the graph and why, and previews the JSON-LD it would build.

llms.txt needs a permalink structure other than Plain, because the route is a
WordPress rewrite rule. Visit `/llms.txt` at your site root to see it. By default
it lists up to 20 pages and your 20 most recent posts; choose exactly what you
want under **SummitCite > Settings > llms.txt** instead.

Answer density scans on a six-hourly schedule and on demand under **SummitCite >
Answer Density**. A scan reads stored content only - it never renders a page,
runs a shortcode or calls a provider - so a large library takes a while on the
first pass and little afterwards.

Nothing arrives instantly. A crawler row appears the next time an AI crawler
requests a published page, which is outside your control and can take days on a
new or low-traffic site. An empty screen after activation is the expected state,
not a fault.

Deactivating the plugin clears its scheduled events but keeps your data and
settings. Only deleting the plugin removes them.

== Frequently Asked Questions ==

= Does this need an AI API key? =

No. SummitCite ships no AI provider. Features that use AI go through the
WordPress 7.0 AI Client API and the connection you configure in WordPress
itself. Every other feature works with no AI at all.

= Will it conflict with my SEO plugin? =

No, by design - and for every feature SummitCite decides on evidence rather than
on whether a plugin happens to be installed.

For structured data, SummitCite checks Yoast SEO, Rank Math, SEOPress, All in One
SEO and The SEO Framework against a stated version profile. If one of them is
emitting content-level structured data, SummitCite emits none of its own. If one
is active and SummitCite cannot establish what it emits, SummitCite still stands
down: a missing graph is recoverable, a duplicated one confuses every consumer
that reads it. **SummitCite > Schema** shows that reasoning per plugin, including
which adapters have been exercised against a running copy of the plugin and
which were written from its documented settings. An SEO plugin outside those
five is not evaluated and does not stop SummitCite; the `aive_schema_conflict`
filter exists for sites that need one accounted for.

For robots.txt, before writing anything, SummitCite checks for a robots.txt file
in the site root and for evidence that Rank Math, All in One SEO or SEOPress Pro
has its own robots editor switched on. In any of those cases it writes nothing
and the Robots Audit screen tells you which system owns the file and where to
set the rules instead. Being active is never enough on its own: all three ship
with robots management off, and SummitCite keeps managing robots.txt on a site
that runs one of them for other features.

For llms.txt, SummitCite checks for an llms.txt file in the site root, for Yoast
SEO's own llms.txt setting, and for Rank Math's llms.txt module. Any one of
those and SummitCite generates nothing. Yoast's feature writes a real file into
your site root, so that case is caught by the file check whatever the setting
says.

SummitCite never claims that another owner is successfully serving a valid file: a
file in your web root is served before WordPress runs, and a cache or CDN can sit
in front of any plugin's output, so neither is something a plugin can see from
inside WordPress. **SummitCite > Conflicts** is the single screen that reports all
of it in one place.

= Does the answer density score decide whether my content ranks? =

No. It measures how well a post is shaped to answer a question - whether the
claims have support, whether the structure is scannable, whether the coverage is
there - and it reports each part separately so you can see which one is weak. It
is a writing aid with its arithmetic shown, not a prediction. The algorithm
version and every raw and per-dimension value that produced a score are on the
screen, so a score can be traced rather than trusted.

= Why is there no FAQ or HowTo block on my page? =

Because the content did not meet the rules, or something else owns the graph.
SummitCite never invents a question or a step. Without markers it needs at least
two distinct question headings, or a procedure heading followed immediately by
an ordered list. Wrap the region in `<!-- aive:faq:start -->` and
`<!-- aive:faq:end -->` - or the `aive:howto` pair - and every heading inside it
counts whatever its wording and whatever language it is in. **SummitCite > Schema**
previews the result for any record and names what was missing.

= Does the schema preview prove what my visitors receive? =

No, and it is labelled so. The preview is what SummitCite's generator produces for
that record right now. A theme, another plugin, a later hook or a page cache all
sit between the generator and a browser.

= Why does my robots.txt have a SummitCite block in it? =

Because SummitCite owns the file on your site and the robots policy is on.
Everything it writes sits between `# BEGIN SummitCite` and `# END SummitCite`, and
nothing outside those lines is touched. Edit the rules under SummitCite > Settings
> Robots, or switch Robots policy off there to remove the block on the next
request.

= Does it add JavaScript to my site's front end? =

No. All output is generated on the server, and no script or stylesheet is loaded
on the front end - the admin screens are built from WordPress's own markup, and
the one script the plugin ships is loaded on its own admin screens only. The
JSON-LD blocks are `<script type="application/ld+json">` elements, which hold
data rather than executable code and run nothing in a browser.

= Does a Block rule stop an AI company reading my site? =

No. robots.txt is a declaration, honoured by the crawlers that choose to honour
it. A Block rule asks; it does not enforce. Anything that must be enforced needs
server or application access control instead.

= What happens to my data if I delete the plugin? =

Deleting removes everything SummitCite created: its three tables, its options, its
cached transients and its scheduled events. Deactivating alone removes nothing
but the schedule.

= Does the crawler log prove a particular AI company visited my site? =

No, and it is worth being clear about why. Identification matches the request's
User-Agent header against a registry of publisher-documented crawler tokens, and
any client can put any text in that header. A row means a request arrived
*claiming* to be that crawler. SummitCite runs no reverse-DNS or IP reputation
check, so treat the data as traffic measurement, not as verified identity.

= Why does the log show fewer visits than my server or CDN logs? =

Because SummitCite measures inside WordPress. A request answered by a CDN, a
reverse proxy or a full-page cache never reaches PHP, so SummitCite never sees it.
The log is a lower bound on AI crawler activity, and on a heavily cached site it
can be far below the true figure. Any plugin measuring at this level has the same
limit.

= Why is there no Bytespider in my log? =

Five registry entries - Google-Extended, Applebot-Extended, anthropic-ai,
cohere-ai and Bytespider - carry a robots.txt rule only. Their publishers
document no crawler, or no AI purpose, that SummitCite is willing to attribute
traffic to, so they are deliberately unmatchable and no header can produce a row
for them. Bytespider is a high-volume crawler, so its absence is noticeable; it
is a decision, not a gap.

= Does logging slow down my site? =

A logged request adds one INSERT. Nothing is added to pages served to humans: the
check rejects a non-crawler request before any query runs, and the aggregates are
computed only on the admin screen. If you would still rather not have it, switch
it off in Settings and the hook stops recording.

= Which IP addresses are stored? =

Whatever `REMOTE_ADDR` reports, in a 45-character column, deleted with the row
when retention prunes it. Forwarding headers such as `X-Forwarded-For` are
ignored, because on a site that is not behind a proxy that rewrites them, a
visitor sets their own value. If your site *is* behind a proxy, expect to see the
proxy's address rather than the crawler's.

= Does llms.txt work without pretty permalinks? =

No. The route is a WordPress rewrite rule, so a Plain permalink structure has no
route to match and `/llms.txt` is not served. Every other feature works
regardless.

= Do I need WordPress 7.0? =

No. WordPress 6.5 is enough for everything shipping today. The 7.0 AI Client API
is used where it exists and is absent without error where it does not.

== Screenshots ==

1. The Dashboard: an operational-evidence score with a colour-coded gauge, four summary cards and a per-component breakdown.
2. Fourteen days of AI crawler activity as a bar chart, with the same values in an accessible table underneath.
3. Crawler Log: visits by bot over seven days, and every request newest first, filterable by crawler.
4. Robots Audit: what you configured for each bot against what the live `/robots.txt` actually grants.
5. Schema: who owns the structured data graph, per-plugin evidence, and a preview of the exact JSON-LD SummitCite would emit.
6. Answer Density: coverage, quality and structure scored separately, worst-first, with the raw values behind each score.
7. Conflict Detector: one inventory of every plugin and file that touches robots.txt, structured data and llms.txt.
8. Settings: every module's controls on one screen, saved once.

== Upgrade Notice ==

= 1.7.2 =
Renamed to SummitCite, and the first release submitted to the WordPress.org
directory. Upgrade in place. Your data is untouched: the database tables, option
keys and cron events keep their `aive_` names, so crawler history, scores and
every setting survive. The robots.txt block markers change to `# BEGIN SummitCite`
/ `# END SummitCite`; a block written under the old markers is replaced in place on
the next request, and text outside the block is still never modified. No
migration, no re-scan, no settings reset.

= 1.7.1 =
Shows the values behind an Answer Density score. No schema change and no re-scan
needed; existing results are re-rendered with the new fields on the next screen
load. Equal scores now sort by the site's own language instead of the database's
default collation.

= 1.7.0 =
Adds the Dashboard and consolidates every module's controls onto one Settings
screen. No table or option changes; the Dashboard reads what the modules already
store, so an existing site shows its real history immediately.

= 1.6.0 =
Adds the Conflict Detector. No table or option changes and nothing else changes
behaviour; the module reports on what the other modules already decided.

= 1.5.0 =
Adds the Answer Density module and its scans. No table changes beyond the ones
activation already created; the six-hourly sweep is scheduled on activation and
existing content is scanned on the first pass.

= 1.4.0 =
Adds llms.txt, its three settings and its route. Upgrade in place; no table or
column changes and no settings are reset. llms.txt is enabled after the upgrade,
so a site with a permalink structure, search engines not discouraged, no
llms.txt in its site root and no Yoast or Rank Math llms.txt feature switched on
will start serving a generated document at /llms.txt. Until you choose content
under SummitCite > Settings > llms.txt it lists up to 20 pages and your 20
most recent posts. Switch llms.txt off there if that is not wanted. Upgrading by
overwriting files fires no activation hook, so the route is repaired on the first
admin request after the upgrade rather than immediately; re-saving Settings >
Permalinks has the same effect.

= 1.3.0 =
Adds the schema engine, its admin screen and its enable setting. Upgrade in
place; no table or column changes and no settings are reset. The schema engine
is enabled after the upgrade, so a site where no evaluated SEO plugin is
emitting content-level structured data will start serving Article JSON-LD on
eligible published content from the next request. Switch Schema Engine off under
SummitCite > Settings if that is not wanted. If one of the five evaluated SEO
plugins is active, SummitCite emits nothing and SummitCite > Schema says why.

= 1.2.0 =
Adds the per-bot robots policy, the Robots Audit screen and robots conflict
detection. Upgrade in place; no table or column changes and no settings are
reset. The robots policy is enabled after the upgrade, so a site where SummitCite owns
robots.txt will start serving a marked AI bot block on the next request for
/robots.txt. Switch Robots policy off under SummitCite > Settings if that is
not wanted.

= 1.1.0 =
Adds the AI crawler log, its admin screen, and the two settings that control it.
Upgrade in place; the schedule and settings are reused as-is. The crawler log
table gains a canonical UTC timestamp column, and any crawler log rows written
before it are deleted during the upgrade because their original instants cannot
be reconstructed. No other data is affected.

= 1.0.0 =
First release. Plugin foundation only: no admin screens and no front-end output yet.

== Changelog ==

= 1.7.3 =
* First WordPress.org release, published as SummitCite.
* No behavioural change and no database migration: tables, the `aive_settings`
  option, transients and cron hooks keep their `aive_` prefix, so nothing is
  re-scanned or reset on update.
* robots.txt markers are `# BEGIN SummitCite` / `# END SummitCite`. A block
  written under the previous markers is recognised and replaced in place on the
  next request; a pair that does not match still stops the module with an admin
  notice instead of being edited on a guess.
* The plugin description spells out "WordPress" instead of "WP", and the readme
  gains an External services section stating that the plugin contacts no
  external service and that the crawler documentation links are plain
  hyperlinks the plugin never requests.

= 1.7.1 =
* Answer Density now shows the values its score is built from. The admin
  listing's AJAX payload and the rendered table both carry the algorithm
  version, the raw coverage ratio and the per-dimension points - coverage,
  quality and structure - so a score can be traced to its arithmetic instead of
  being taken on trust.
* Tie-break ordering for equal scores is locale-aware. Previously an equal-score
  pair was ordered by the database's default collation, which orders non-English
  titles by byte value rather than by the site's language.
* No schema change; existing rows are re-rendered with the new fields on the next
  screen load. No re-scan required.

= 1.7.0 =
* New Dashboard, the parent screen for the plugin: an operational-evidence score
  with a colour-coded gauge (good at 70 and above, fair 40 to 69, poor below 40),
  four linked summary cards, a six-component breakdown that states why a
  component is unavailable rather than showing a zero, and quick links into each
  module.
* Fourteen days of AI crawler activity as a bar chart drawn in pure CSS - no
  chart library, no canvas, no image - bounded to whole UTC days, with every
  plotted value repeated in a semantic table underneath and in each bar's
  accessible label.
* Settings consolidated onto one screen: crawler log, robots policy, schema,
  llms.txt, answer density and conflict detector sections in a single form,
  saved once, with each module's state and ownership shown next to its controls.
* Existing screens and slugs are unchanged, so every bookmark and every link
  into a specific module keeps working.
* No new tables, options or migrations. The Dashboard reads what the modules
  already store and keeps no state of its own.

= 1.6.0 =
* New Conflict Detector module and screen: a closed inventory of five SEO plugin
  families - Yoast SEO, Rank Math, SEOPress, All in One SEO and The SEO
  Framework - with the ownership each one delegates for robots.txt, structured
  data and llms.txt, the evidence used to decide it, and a recommendation
  wherever ownership is contested.
* Delegated rows defer to the owning feature's own decision instead of
  re-deriving it, so the two screens can never disagree about who owns a file.
* One-hour reporting cache with an explicit Retest action; the cache is discarded
  when modules, settings or active plugins change, and a failed probe is reported
  as a named failure rather than an inferred status.
* Aggregate statistics feed the Dashboard. No new tables, no background work and
  no front-end output.

= 1.5.0 =
* New Answer Density module: deterministic per-post classification and
  transparent scoring across coverage, quality and structure, with each
  dimension scored separately rather than collapsed into one opaque number.
* Versioned results stored per post: a result records the algorithm version that
  produced it, so a score from before a change is never presented as though it
  came from after one.
* Single and bulk scans on demand, plus a six-hourly scheduled sweep. A scan
  reads stored content only - it never renders a page, executes a shortcode or
  calls a provider - and is bounded in size and time.
* Worst-first administration, so the content that most needs work is at the top
  of the list, with the supporting signals shown next to the score.
* Disabling the module preserves stored rows and stops scanning; removing a post
  type hides its rows without deleting them; restoring either resumes from the
  existing data rather than starting over.
* No front-end output and no new user-facing surface beyond its own admin screen.

= 1.4.0 =
* Module 4, llms.txt: serves a plain-text inventory of eligible public content
  at the site root, from one rewrite rule matching `/llms.txt` with one optional
  trailing slash and nothing else. Handled on `template_redirect` at priority 1,
  before canonical redirection and any template, and it ends the request.
* The response is exactly HTTP 200 with `Content-Type: text/plain; charset=UTF-8`
  and `X-Content-Type-Options: nosniff`. No theme output, no redirect and no
  frontend JavaScript on any request.
* SummitCite serves only when the module is on, site visibility is on, no
  `ABSPATH/llms.txt` exists, and no supported plugin is positively detected as
  providing the feature. Otherwise the route yields WordPress's ordinary 404 -
  never an empty 200, a redirect, a second document or a public explanation of
  who owns it.
* Fixed ownership precedence: site visibility off, a physical file, Yoast SEO's
  `enable_llms_txt` setting, Rank Math's `llms-txt` module, then SummitCite. A plugin
  being active is never evidence on its own, and no request input reaches the
  decision. An absent, unreadable or malformed signal, or an unsupported plugin,
  is reported as not established rather than as a known owner, and does not stop
  SummitCite - the physical-file rule already catches the case that produces a
  duplicate, including Yoast's, whose feature writes a real file into the site
  root.
* Eligibility is published, public, non-attachment, non-revision,
  non-password-protected content with a canonical HTTP(S) permalink on this
  site. Private, draft, pending, scheduled, trashed, protected, attachment and
  non-public content never appears, and neither does any archive, feed, search
  or taxonomy view.
* Two selection modes. An explicit, ordered selection lists exactly those of its
  items that are still eligible and appends nothing; an item that becomes
  ineligible is omitted without the stored selection being rewritten. With no
  valid selection, automatic selection lists at most 20 pages by menu order,
  title then id, and at most 20 standard posts by date then id. No custom post
  type is selected automatically. An empty section is absent.
* Deterministic output: the same effective state produces byte-identical,
  LF-terminated UTF-8. Every site name, description and title is stripped of
  markup, decoded, cleared of control, line-break, bidirectional-override and
  zero-width characters, bounded by characters rather than bytes, and
  backslash-escaped, so no title can inject a heading, a list item or its own
  link destination. Permalinks are validated to this site's host and
  percent-encoded where a Markdown destination would otherwise break.
* The response is capped at 64 KiB by omitting whole items in deterministic
  order, never by truncating: no half-written list item, no heading introducing
  nothing and no split UTF-8 character.
* A twelve-hour cache keyed on the format version, the plugin build, site
  identity and visibility, the effective llms settings including the ordered
  selection, the ownership signature, and each item's id, title, permalink, menu
  order, date and modification instant. Ownership and visibility are determined
  before the cache is consulted, so a new conflict or a newly private site never
  serves a cached document. A corrupt entry reads as a miss and regenerates.
* Three bounded controls in the existing `aive_settings` option on the existing
  Settings screen: the enable toggle, a 500-character description, and a
  checkbox list of up to 200 pages and 50 recent posts plus anything already
  selected outside them. Saved through the ordinary `options.php` capability and
  nonce path; invalid, duplicate, unknown and ineligible ids are discarded, and
  unrelated settings keys survive a save untouched. No new admin page, REST
  endpoint, custom AJAX surface, dashboard, script or stylesheet.
* The section reports who owns the route and why, with one neutral notice per
  state and a per-plugin evidence table that distinguishes a known owner from
  one SummitCite could not establish - and states that SummitCite does not verify another
  owner is serving a valid file.
* Activation and deactivation maintain the route and clear the cached document
  only. The module never writes, edits, deletes or claims ownership of a
  physical llms.txt, and uninstall removes its cache, its route marker and its
  settings through the existing prefix sweep.
* The route needs a permalink structure other than Plain. Activation flushes the
  rewrite rules, and one bounded soft flush per build repairs the stored rules
  on a site upgraded by overwriting plugin files, which fires no activation hook.
* No AI provider call, external request, crawler-registry change, robots or
  schema change and no unrelated setting is introduced by this release.

= 1.3.0 =
* Module 3, Schema Engine: emits one Article or BlogPosting block for every
  eligible published post, page or public custom post type viewed on its own
  page, plus one FAQPage and one HowTo block when the content qualifies. A core
  post is described as BlogPosting; a page or custom post type as Article.
* Nothing is emitted in wp-admin, AJAX, REST, feed, cron, CLI, login, embed or
  preview contexts, on a 404, search, blog index or archive, or for a revision,
  autosave, attachment, non-public post type, unpublished status or
  password-protected record. A record appearing in the preview selector does not
  override any of those.
* Required Article fields are all-or-nothing: headline, publication and
  modification instants from the stored GMT dates, the canonical permalink and
  an Organization publisher. If one cannot be formed, no Article is emitted
  rather than a partial or guessed one. Author, description, image, logo and
  language are optional and simply absent when unavailable.
* Explicit marker grammar for both derived types:
  `<!-- aive:faq:start -->` / `<!-- aive:faq:end -->` and the `aive:howto`
  pair. Inside a FAQ region every heading and every details/summary is a
  question whatever its wording, which is what makes the feature usable in
  languages the automatic rules do not cover. Markers are HTML comments, so they
  survive the Classic editor, the Code editor and a Custom HTML block.
* Automatic detection is bounded rather than open-ended: h2/h3 headings and
  details/summary that end in `?`, `？` or `؟` or begin with a listed English,
  Chinese, Japanese or German interrogative, with at least two distinct pairs
  required; and a procedure heading beginning with a listed phrase whose next
  meaningful sibling is an ordered list of two or more items. No other ordered
  list is read as a procedure.
* Overlap precedence is fixed and deterministic: explicit HowTo, explicit FAQ,
  automatic HowTo, then automatic FAQ. A procedure heading and its list are
  never also published as a question and an answer.
* Bounds on everything parsed: at most 200 KiB of stored content, 20 FAQ pairs,
  20 steps, 200 characters per question or step name, 2,000 per answer or step,
  and 40,000 across one generated object. FAQ questions are deduplicated
  case-insensitively and a duplicate cannot satisfy the automatic threshold.
* The generator reads stored content with shortcodes removed. It never calls
  `the_content`, renders a block, executes a shortcode or fetches a URL, and
  libxml runs with networking off and its diagnostics collected. A documented
  `aive_schema_source_content` filter can replace the bounded source; output
  that is not a string within the bound is ignored.
* Serialisation goes through `wp_json_encode()` with `JSON_HEX_TAG`,
  `JSON_HEX_AMP`, `JSON_HEX_APOS` and `JSON_HEX_QUOT`, so no author text can
  close the script element it sits inside. Every emitted URL is validated to an
  absolute HTTP(S) URL; an invalid optional URL is dropped and an invalid
  required one suppresses its object. No frontend script or stylesheet is
  enqueued.
* Schema conflict ownership, evidenced per plugin rather than by presence. Yoast
  SEO, Rank Math, SEOPress, All in One SEO and The SEO Framework each get an
  adapter with a stated version profile, and each decision carries the detected
  version, the setting or hook consulted and the profile used. An active plugin
  whose schema output cannot be established is reported as unknown and SummitCite
  emits nothing, which is the deliberate conservative choice. An SEO plugin
  outside the five is not evaluated and does not block SummitCite.
* The SEO Framework's contradictory legacy requirement is resolved as an
  evidence-scoped decision: coexistence only for a configuration profile
  confirmed to emit site-level and breadcrumb schema alone. No such profile is
  confirmed in this release, so an active install is treated as unknown and SummitCite
  stands down.
* `aive_schema_conflict` filters the complete ownership result. It may select
  only a recognised state and owner, cannot force output for a disabled module
  or an ineligible record, is recorded as an override and is shown to
  administrators. Malformed filter output falls back to SummitCite's own decision.
* Generated payloads are cached for at most twelve hours under a key carrying
  post identity and modification time, status, type, a content hash, the
  settings hash, the site's locale, home URL, name, logo and icon, the active
  theme, the featured image, the author identity used and the ownership
  signature - so a stale payload is unreachable rather than merely scheduled for
  deletion. Only normalised payloads are stored, never raw or rendered content,
  and the relevant entries are also deleted on edit, status change, featured
  image, author, site identity, theme, settings and SEO-plugin lifecycle events.
* New Schema screen, `manage_options` only: the generation rules per type in
  plain language, who owns the graph with per-plugin evidence, and a preview of
  the generated candidate for any published eligible record. The preview is
  labelled "Generated candidate - not proof of frontend emission", prints JSON
  as escaped text in a labelled region rather than as an executable script,
  carries an accessible status message and a visible empty state, and reports
  disabled, ineligible, yielded, unknown, no-qualifying-content and encoding
  outcomes by name.
* The record selector is a read-only GET with strict integer validation. It
  lists published, unprotected, eligible records only, and a record supplied by
  id that is not eligible is reported by reason alone, without its title.
* Settings screen gains the Schema section with the Schema Engine toggle, stored
  in the existing `aive_settings[enable_schema]` key. Settings the form does not
  render still survive a save. Switching the module off stops output, makes the
  preview unavailable and clears the schema cache.
* No database schema or table change. Schema cache artifacts carry the `aive_`
  prefix, so uninstall removes them with every other SummitCite artifact and no
  third-party data is touched. Crawler, robots, registry, database and the
  accepted timestamp behaviour are unchanged.

= 1.2.0 =
* Module 2, Robots policy: declares an Allow, Block or Don't manage rule for
  every token in the crawler registry, written into the robots.txt WordPress
  generates. Allow contributes `Allow: /` with `Disallow: /wp-admin/`; Block
  contributes `Disallow: /`; Don't manage contributes nothing.
* All SummitCite output sits inside one `# BEGIN SummitCite` /
  `# END SummitCite` block. Text outside the markers is never
  modified, the previous block is replaced rather than appended to, and marker
  text that is not one matching pair stops the module entirely with an admin
  notice rather than being edited on a guess.
* Robots conflict ownership. SummitCite writes nothing when WordPress is set to
  discourage search engines, when a robots.txt file exists in the site root,
  when Rank Math, All in One SEO or SEOPress Pro has its own robots management
  switched on by evidence of that plugin's own setting, or when such a plugin is
  active and its setting cannot be read. An active plugin alone is never treated
  as owning robots.txt.
* A bot that an existing non-SummitCite group already names is left alone: no second
  group is emitted for it, and the audit reports which bots are in that state.
* New Robots Audit screen. Reports the source of the audited response, when it
  was last checked and whether that is still current, who owns robots.txt, and
  per bot the configured rule, the measured status at the site root, the reason
  for it, and any mismatch. Empty and failure states say what to do next instead
  of showing a zero.
* The audit makes one bounded request to this site's own `/robots.txt`: no URL
  field, HTTP(S) only, home host and port, no redirect followed, a ten-second
  timeout and at most 128 KiB read. A redirect, a non-2xx status, a non-text
  response, an oversized body or a transport error each produce a named failure
  and no inferred bot status. Nothing fetched is stored.
* The audit runs only on an explicit nonce-checked Re-run Audit by a
  `manage_options` user. Opening the screen reads a one-hour cache and never
  fetches; the cache is discarded when the rules, the site visibility or the
  active plugins change.
* Audit limits are stated rather than implied: it reports access to the site
  root only, does not evaluate wildcards or anchors, and is not evidence that
  any crawler obeys robots.txt.
* Settings screen gains the Robots section: the module toggle and the per-bot
  rule table. A submitted rule is kept only for a currently registered token
  with a recognised value; anything else is discarded and a missing value
  becomes Don't manage. Settings the form does not render still survive a save.
* A registry name that cannot be written safely into robots.txt is excluded from
  the rules and the output, and reported in an admin-only notice rather than
  dropped silently.
* No new table, column or schema migration. Rules live in the existing
  `aive_settings` option and the audit in an `aive_*` transient, so the existing
  uninstall cleanup removes both.

= 1.1.0 =
* Module 1, Crawler Log: records visits from known AI crawlers and reports them
  on a new admin screen.
* Logs an eligible visit on `template_redirect` when the User-Agent matches the
  crawler registry. Eligible means a GET request for a single published post,
  page or public custom post type. Humans, unmatched User-Agents, non-GET
  methods, admin, AJAX, REST, cron, feed, robots.txt, sitemap and XML requests,
  and non-singular or non-public content are all ignored. A configured posts
  page records nothing, because the blog index lists other content rather than
  being a visit to that page; a static front page is a real page view and is
  recorded against its own ID.
* Stores the matched bot name and type, resolved post ID, request URI path,
  User-Agent and `REMOTE_ADDR`, each truncated to its column width. The query
  string is dropped and forwarding headers are deliberately not read.
* New admin menu, SummitCite, holding Crawler Log and Settings. Both screens
  require `manage_options`, re-checked in the page callback rather than relying
  on the menu entry alone.
* Crawler Log screen reports Visits by Bot and Recent Visits over the last seven
  days, filterable by bot and sortable by time, with its own empty state and a
  distinct message when the table cannot be read.
* Settings screen renders Module 1's two controls: the enable toggle and the
  retention period. Saves go through `register_setting` with a nonce and a
  capability check, and the sanitiser preserves the settings of modules that do
  not exist yet instead of dropping them.
* Retention: the daily `aive_cleanup_crawler_logs` event deletes rows older than
  `log_retention_days` x 24 hours of elapsed time. Bounded to 1-3650 days, 90 by
  default, and clamped on read as well as on save so a value written
  programmatically cannot widen the window. Both this window and the seven-day
  reporting window measure absolute elapsed time, so neither shifts by an hour
  across a daylight-saving transition in site timezones that observe one.
* Seven-day totals and distinct bot counts published through the
  `aive_dashboard_stats` filter as `crawler_total_visits_7d` and
  `crawler_unique_bots_7d`, ready for the Module 7 dashboard.
* Every SQL statement against the crawler log table lives in one store class,
  with prepared bindings and an ORDER BY allow-list. A database error degrades
  to an admin message and never affects a front-end response.
* No assets of any kind: the admin screens are built from core's own classes,
  and the front end loads no SummitCite stylesheet or JavaScript.
* Requirements 2, 6 and 9 of the acceptance suite now execute, joined by new
  integration coverage for retention boundaries, the reporting window, admin
  authorisation, escaping and the empty and failure states.
* Timekeeping: the crawler log gains a `visited_at_utc` column and an index on
  it, and that column alone decides retention, the seven-day window,
  newest-first ordering and each bot's last visit. The existing `visited_at`
  stays as a site-local rendering of the same instant for compatibility and is
  no longer compared against. A column with no offset cannot tell apart the two
  instants an hour apart that share a wall-clock reading when the clocks go
  back, which made retention keep an expired row and the seven-day count
  include one. Timestamps are converted for display, so changing the site
  timezone changes what the screen shows and nothing about which rows a window
  contains.
* Upgrading deletes crawler log rows written before that column existed. Their
  original instants cannot be reconstructed - a repeated-hour reading maps to
  two, and the site's timezone at the time of the visit is not recorded - so
  they are removed rather than given an invented offset. Crawler measurements
  only: posts, settings, registry data and the plugin's other tables are
  untouched, and this is not uninstall. Until the migration completes the module
  records nothing, runs no cleanup and reports zero, rather than writing rows
  with no canonical instant.
* The cleanup event and the settings keys were created by 1.0.0 and are reused
  unchanged.

= 1.0.0 =
* Initial release: plugin foundation.
* Plugin header, constants and Composer PSR-4 autoloading, with a missing
  autoloader degrading to an admin notice instead of a fatal error.
* Activation creates the crawler log, answer density and citations tables via
  dbDelta(), seeds the default settings without overwriting an existing
  configuration, schedules the daily log cleanup and six-hourly density scan,
  and flushes rewrite rules.
* Deactivation clears the scheduled events and flushes rewrite rules, keeping
  all data.
* Uninstall removes every aive_ prefixed table, option, transient and scheduled
  event, after verifying that WordPress is uninstalling this plugin.
* Schema versioning, so a plugin updated by overwriting files still migrates.
* AI crawler registry covering 18 publisher-verified search, assistant and
  training crawler tokens, 13 of them detectable from the User-Agent header and
  5 carried for their robots.txt rule alone. Extensible through the
  aive_crawler_list filter.
* Shared utilities: settings access, AI Client API detection, crawler matching,
  multilingual question detection for English, Chinese, Japanese and German,
  HTML fragment parsing and debug logging.
