=== One-V LLM Serve ===
Contributors: onevteam, vslnk
Donate link: https://one-v.co.il
Tags: markdown, llm, ai, seo, geo
Requires at least: 6.0
Tested up to: 6.9
Requires PHP: 8.0
Stable tag: 1.0.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Serve clean Markdown versions of every public page at the same URL with a `.md` extension for AI crawlers, LLMs, and GEO optimization.

== Description ==

**One-V LLM Serve** makes every public page on your WordPress site available as clean Markdown at the same URL with a `.md` extension — zero configuration required.

`https://example.com/about/      ← HTML page for humans`
`https://example.com/about.md    ← clean Markdown for AI`

AI systems — ChatGPT, Perplexity, ClaudeBot, Google AI Overviews, and most RAG pipelines — parse Markdown far more efficiently than HTML. When these systems encounter an HTML page, they must strip navigation, headers, footers, sidebars, scripts, and tracking pixels before they can read the actual content. This noise introduces errors, increases token cost, and leads to lower-quality outputs.

The Markdown file contains a configurable YAML frontmatter block followed by the page title, headings in correct hierarchy, and the body text. Nothing else.

= Core features =

* **Zero-config Markdown endpoint** for every public post, page, and custom post type
* **YAML frontmatter** with configurable fields (`title`, `date`, `modified`, `url`, `description`, `image`, `tags`, `categories`, `lang`, `type`)
* **`/llms.txt` discovery file** at the site root following the [llmstxt.org](https://llmstxt.org) convention
* **Taxonomy archives** as Markdown — `/category/news.md`, `/tag/foo.md`, custom taxonomies
* **`?format=markdown` query parameter** as an alternative to the `.md` URL on any singular page
* **Per-post exclude** via a sidebar checkbox on the post editor
* **Works with Classic Editor and Gutenberg** via the `the_content` filter
* **ACF integration** — opt-in per-post: pick which text, textarea, WYSIWYG, URL, email, or link fields to append below the body

= Discoverability =

* **`Link: rel="alternate"; type="text/markdown"`** HTTP header on every HTML page
* **`<link rel="alternate">`** tag in `<head>` for HTML-based discovery
* **`Allow: /*.md$`** directive in `robots.txt`
* **`X-Robots-Tag: index, follow`** on `.md` responses
* **CORS `Access-Control-Allow-Origin: *`** on `.md` and `/llms.txt` so browser-based AI clients can fetch them

= Operations =

* **Transient caching** with automatic invalidation on `save_post`, on ACF field value saves, on any ACF field group change, and on plugin settings save
* **"Clear cache" button** in the settings page
* **Admin notice** on fallback HTTP fetch failures
* **"Settings" link** next to the plugin row in Plugins screen
* **"View .md" row action** in the Posts and Pages list tables

= Developer hooks =

* **`ovls_markdown`** filter for the final Markdown output
* **`ovls_frontmatter`** filter for adding, removing, or modifying frontmatter fields
* **`ovls_content_queries`** filter for the HTML extraction XPath cascade

= How it works =

Each request to `/about.md` is captured by a WordPress rewrite rule and routed through the plugin's content generator. The generator runs the post through `apply_filters( 'the_content', ... )` — the same pipeline WordPress uses on the front end — so Classic Editor, Gutenberg, and shortcodes all work without separate code paths. The rendered HTML is converted to Markdown via `league/html-to-markdown`, then cached in a WordPress transient.

The cache is invalidated automatically on `save_post`, on ACF field/group changes, and whenever plugin settings are saved. A manual **Clear cache** button is also available on the settings page.

= Access methods =

There are three equivalent ways to request the Markdown version of a page:

* `.md` extension — `https://example.com/about.md`
* `?format=markdown` query — `https://example.com/about/?format=markdown`
* `Link: rel="alternate"` header — returned by every HTML page

The `.md` URL is the recommended canonical form.

= ACF integration =

When [Advanced Custom Fields](https://www.advancedcustomfields.com/) is active, ACF field rendering is opt-in at two levels:

1. **Site defaults per post type** — at **Settings → One-V LLM Serve → ACF Defaults**, tick fields that should be appended to every post of a given post type.
2. **Per-post override** — the **One-V LLM Serve** metabox on each post editor lists every supported ACF field applicable to that post. Tick fields to replace the site defaults for that one post.

Supported ACF types: `text`, `textarea`, `wysiwyg`, `url`, `email`, `link`. Each selected field is rendered under a `## Field Label` heading. Empty fields are skipped.

== Installation ==

1. Upload the `one-v-llm-serve` folder to `/wp-content/plugins/`, or install via **Plugins → Add New → Upload Plugin**.
2. Activate the plugin through the **Plugins** screen in WordPress.
3. Visit **Settings → One-V LLM Serve** to configure post types, frontmatter fields, and ACF defaults.

Rewrite rules are flushed automatically on activation. If `.md` URLs return 404 immediately after activation, go to **Settings → Permalinks** and click **Save Changes**.

== Frequently Asked Questions ==

= Does activating the plugin change my existing pages? =

No. The plugin only responds to `.md` URLs, `/llms.txt`, and the `?format=markdown` query parameter. All existing HTML URLs are unaffected.

= Will the `.md` URLs hurt my SEO? =

No. Responses include `X-Robots-Tag: index, follow`, and the `Link: rel="alternate"` header signals the relationship correctly. Search engines treat them as supplementary content.

= Does it work with password-protected posts? =

No. Password-protected and private posts return 404 on the `.md` URL. Only published posts are served.

= What Markdown flavour is used? =

CommonMark-compatible Markdown via `league/html-to-markdown`. ATX-style headings (`#`), inline links (`[text](url)`), and fenced code blocks.

= Where is the Markdown cached? =

In WordPress transients (database by default, or your object cache). No expiry — entries persist until the post is saved or you click **Clear cache**.

= The `.md` URL returns 404 after activation. =

Go to **Settings → Permalinks** and click **Save Changes** to flush rewrite rules.

= Can I disable Markdown for specific posts? =

Yes. Two ways:

1. Check **Exclude from Markdown** in the One-V LLM Serve metabox on the post editor.
2. Return `''` from an `ovls_markdown` filter callback.

= Does it work with page builders like Elementor or Divi? =

Yes. Any builder that hooks into `the_content` is supported (Elementor, Divi, WPBakery, Beaver Builder). For builders that bypass `the_content`, the plugin falls back to fetching the rendered frontend HTML and extracting the main content area.

= Is it compatible with caching plugins? =

Yes. Markdown is stored in WordPress transients. Object caches (Redis, Memcached) work transparently. Full-page caching layers (WP Rocket, W3 Total Cache, LiteSpeed Cache) serve fresh Markdown on the next request after a save.

= /llms.txt returns 404 on WPEngine / Kinsta / managed nginx hosts =

Some managed WordPress hosts configure their nginx to serve static file extensions (`.txt`, `.xml`, …) directly from disk without passing the request to WordPress. When the file is generated dynamically by a plugin, that produces a 404 because nothing exists on disk.

Fix: enable **Settings → One-V LLM Serve → Write llms.txt to disk**. The plugin then maintains a real `/llms.txt` file at the site root, regenerating it on every post save, ACF change, or settings update. The file carries a marker comment on the first line; the plugin refuses to overwrite a `/llms.txt` it did not create. On plugin deletion the managed file is removed via `uninstall.php`.

= Does disk-mode work on WordPress installed in a subdirectory or on multisite? =

Not in v1.0.0. The disk-mode writer assumes WordPress is installed at the site root (`ABSPATH` is the public root). Subdirectory installs (`/wp/`) and multisite are not supported by disk-mode yet — for those, the dynamic rewrite-rule path is still available on hosts where nginx forwards `.txt` requests to PHP (most hosts other than WPEngine/Kinsta).

== Screenshots ==

1. Settings page — master toggle, llms.txt and taxonomy toggles, frontmatter field picker, ACF Defaults, and post-type selection.
2. Rendered Markdown output with YAML frontmatter served at the `.md` URL.
3. Another rendered example showing a different page as Markdown.

== Disclaimer ==

This plugin is provided "as is", without warranty of any kind, express or implied, in accordance with the GNU General Public License v2 or later. The authors and contributors are not liable for any direct, indirect, incidental, special, or consequential damages — including but not limited to data loss, lost profits, business interruption, search-ranking changes, or third-party claims — arising from the use of, or inability to use, this software, even if advised of the possibility of such damages.

By installing and activating the plugin you acknowledge that:

* You are responsible for testing the plugin in a staging environment before deploying to production.
* You are responsible for the content this plugin exposes as Markdown — `.md` URLs and `/llms.txt` serve the same content as their HTML counterparts and are intended to be crawled and consumed by AI systems and third-party LLMs.
* The plugin does not transmit data to any external service. All Markdown generation, caching, and file writes happen on your own server.

Nothing in this disclaimer is intended to exclude or limit liability for matters that cannot lawfully be excluded under the consumer-protection laws of your jurisdiction. For the full legal terms see the GPLv2 license at [https://www.gnu.org/licenses/gpl-2.0.html](https://www.gnu.org/licenses/gpl-2.0.html).

== Changelog ==

= 1.0.2 =
* Added: rel="canonical" Link HTTP header on .md responses pointing to the HTML permalink — consolidates SEO signals and avoids duplicate-content indexing. Index.md points at the homepage; taxonomy term .md points at the term archive.
* Added: Disclaimer section in readme covering warranty, liability, and data-transmission stance per GPLv2.

= 1.0.1 =
* Fixed: disk-mode for `/llms.txt` now detects multisite and "WordPress in a subdirectory" installs and refuses to write to `ABSPATH` when it does not map to the public docroot. Settings page surfaces a clear "unsupported install layout" state instead of silently writing the file to the wrong location. The dynamic rewrite-rule path keeps working on all install layouts.
* Fixed: uninstall script applies the same layout check before attempting to delete the managed `/llms.txt`.

= 1.0.0 =
* Initial release.

