=== FindSignal – AI SEO & LLMs.txt ===
Contributors: enterrahost
Tags: llms.txt, ai, markdown, robots.txt, woocommerce
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.4.6
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Author: Enterrahost
Author URI: https://enterrahost.com

Makes a WordPress site readable by AI agents: llms.txt, llms-full.txt, per-page Markdown, and a robots.txt that can declare Content-Signal.

== Description ==

AI assistants and answer engines read the web with a text budget. A WordPress page is 90 KB of theme markup wrapped around 3 KB of words. This plugin gives an agent the words.

It adds four things, all built from your existing content:

* **/llms.txt** — an index of the site: what it is, and where to look. Follows the llmstxt.org convention.
* **/llms-full.txt** — the editorial content of the site in one file, for a reader that wants everything at once.
* **Markdown for every page** — `/your-page.md`, plus `Accept: text/markdown` on the page itself. Generated from the post, not from the theme, so it is identical on every theme.
* **Content-Signal in robots.txt** — an opt-in line declaring how the site permits its content to be used.

No account, no API key, no external service. Everything is generated from your own database.

= robots.txt, both ways =

If WordPress serves robots.txt (the default), the Content-Signal line is added to the generated file on every request. Nothing is written to disk.

If a physical `robots.txt` exists in the site root, the web server serves that file and WordPress never sees the request. The plugin detects this and offers an explicit button to add the block to the file. If PHP cannot write it, WordPress asks for FTP or SSH credentials through its own filesystem form. The original contents are kept in the database, and no backup file is left in the web root.

= Works with what you already run =

* **Any theme.** The Markdown is built from post content and block structure, so a theme change does not change the output.
* **Any host.** Uses WordPress rewrite rules, not server configuration. On a site without pretty permalinks, the same output is available with `?findsignal_md=1`.
* **SEO plugins.** Detects Yoast SEO, Rank Math and All in One SEO and reports which one owns the sitemap rather than competing with it.
* **WooCommerce.** Products get their own section in llms.txt, with price and availability. Large catalogues are kept out of `llms-full.txt` by default.

= Limits =

* **llms-full.txt is capped at 300 pages.** On a larger site the per-page Markdown files cover the rest, and the file ends with a line saying it was truncated. llms.txt is built from the same query, so it covers the same 300 pages in total.
* **The generated files are cached**, for 12 hours, 24 hours or 7 days, whichever you choose. Any edit clears the cache immediately, so the cache lifetime only bounds how long a change made outside the normal save path can go unnoticed.
* **Products are kept out of llms-full.txt by default.** A large catalogue belongs in the per-product `.md` files; there is a setting to include them.

= Bundled font =

The admin screens use **Plus Jakarta Sans**, bundled under the SIL Open Font License 1.1. The licence text is in `assets/fonts/OFL.txt`. The font is loaded only on this plugin's admin pages, never on the public site.

= What it does not do =

* It does not emit JSON-LD. Your SEO plugin and WooCommerce already do, and a second schema graph on the same page is a conflict, not a feature.
* It does not contact us or any third party. Its only HTTP request is to the site's own permalinks, and only when a page's copy is not stored in the post.
* It does not change another plugin's output.

== Installation ==

1. Install and activate the plugin.
2. Visit **AI Visibility** in the admin menu.
3. Choose your post types and whether to add the Content-Signal line.

Permalinks are refreshed on activation. If `/llms.txt` returns a 404, open **Settings → Permalinks** and save once.

== For developers ==

Four filters, for the cases a settings screen would handle badly.

* `findsignal_exclude_post` runs per post. Return true to keep it out of both files.
* `findsignal_is_noindex` overrides the SEO-plugin meta check.
* `findsignal_description` changes the one-line description in `llms.txt`.
* `findsignal_markdown` changes the Markdown for a single post.

Excluding a category, for example:

    add_filter( 'findsignal_exclude_post', function ( $excluded, $post ) {
        return $excluded || has_category( 'internal', $post );
    }, 10, 2 );

== Frequently Asked Questions ==

= Does this replace my sitemap? =

No. It uses the sitemap you already have and shows you which plugin is serving it. WordPress core sitemaps, Yoast, Rank Math and All in One SEO are all detected.

= Does it work with a page builder? =

Block-editor content is fully supported. Elementor pages are read from their stored layout, and Divi or WPBakery layouts are rendered from their shortcodes. A page with copy that lives in a PHP template is read from the rendered page, which works as long as the page is publicly reachable from the server.

= Does the plugin modify files? =

Only `robots.txt` in the site root, only when you press the button, and only to add or remove its own marked block. Nothing is written on activation. Every other file this plugin serves is generated in memory.

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

Nothing is kept unless you tick "Keep settings when the plugin is deleted". By default, deleting removes the option and the cached files.

== External services ==

None. Nothing is sent to the plugin author or to any third party.

The plugin makes one kind of HTTP request, and it is to the site itself. When a
page's copy is not in the post, which is normal on a theme that renders it in a
PHP template or on a page builder that stores layout in its own meta, the plugin
fetches that page's own URL to read it. That request never leaves the site.

The dashboard links to four free tools on enterrahost.com. Those are ordinary links that open in a new tab; nothing is sent to them unless you click one.

== Screenshots ==

1. The dashboard: score, live files, and the environment the plugin detected.
2. The preview: the Markdown an agent receives for a real page.

== Changelog ==

= 0.4.6 =
* llms.txt and llms-full.txt are not cached by a page cache, so every agent request reaches PHP and is counted.

= 0.4.5 =
* Counts robots.txt requests when WordPress serves the file.
* The activity card explains what Other means.

= 0.4.4 =
* Clears the update cache when the stored version changes, so a manual upload cannot leave a self-offer behind.

= 0.4.3 =
* Clears the update cache after its own update, so the version just installed is not offered again.

= 0.4.2 =
* The updater removes a stale "up to date" entry when it offers a newer version.

= 0.4.1 =
* The activity table is full width, filterable by agent and paginated over the last 500 requests.
* The crawler table no longer wraps agent names or paths.

= 0.4.0 =
* Agent activity: which AI agents request the files, counted by name, with nothing else stored.
* A crawler access table that reads robots.txt and shows what each known agent may fetch.
* ETag and 304 responses on llms.txt, llms-full.txt and the Markdown endpoints.
* A self-hosted update check, so the plugin updates without WordPress.org.

= 0.3.3 =
* Markdown responses are private and uncacheable, so a page cache that ignores Vary cannot serve Markdown to browsers.

= 0.3.2 =
* Option to declare the sitemap in robots.txt, for SEO plugins and physical files, without duplicating the line core or a plugin already writes.

= 0.3.1 =
* FindSignal 0.3.1. Same feature set as 0.3.0, under the new name, with the sitemap link using the live sitemap.

= 0.3.0 =
* Pages whose copy is not in the post are read from the rendered page, so theme templates and page builders produce real Markdown.
* Elementor layouts are read from _elementor_data instead of guessing from the page, and Divi or WPBakery layouts are rendered from their shortcodes.
* A read-only status route reports file sizes, page counts and rewrite health.
* The dashboard names the pages that produced no text.
* Classic posts convert their HTML to Markdown instead of plain text.
* Rendered pages are cached per post and per modification, and a rebuild caps how many it fetches.
* New settings: read pages whose copy is not in the post, and the fetch cap.

= 0.2.0 =
* Scheduled rebuild, off by default, hourly or daily.
* Last run and next run on the dashboard, with a warning when the schedule cannot fire.
* A Rebuild now button, and the WP-CLI rebuild records the run.
* The WP-Cron warning only appears when a schedule actually exists.
* A cron heartbeat records every cron run, so a site with WP-Cron switched off and a system cron running wp-cron.php is reported as working, not broken.
* The Markdown response carries a canonical Link header pointing at the HTML page.
* The Markdown response advertises the sitemap with a Link header, and a physical robots.txt block now declares it too.
* The sitemap score counts discovery, meaning it exists, robots names it and a header advertises it, rather than existence alone.
* The settings screen offers to update an older robots.txt block that has no sitemap line.
* The two files are served by request path as well as by rewrite rule, so another plugin's rule cannot take them over.
* The request-path fallback is root only. A nested /anything/llms.txt is a 404, not the index.
* The dashboard reports how many pages produced text and warns when the files are mostly titles.
* Password-protected posts are excluded from both files and from the Markdown endpoint.
* The filters are documented in the readme.

= 0.1.0 =
* First release: llms.txt, llms-full.txt, per-page Markdown, Content-Signal, dashboard and settings.

== Upgrade Notice ==

= 0.1.0 =
First release.
