.md * - Whole site: /llms-full.txt (every page's markdown concatenated) * * The markdown is built from the captured analysis (llm_context_block, * summaries, content_sections, approved entities with sameAs, FAQs) — a view * purpose-built for LLM retrieval, not a naive HTML→MD dump. Falls back to the * raw post content when a page hasn't been analysed. * * Also advertises each page's .md via . */ class Semantic_Markdown { public static function init() { add_action( 'init', [ __CLASS__, 'rewrite_rules' ] ); add_filter( 'query_vars', [ __CLASS__, 'query_vars' ] ); add_action( 'template_redirect', [ __CLASS__, 'serve' ] ); add_action( 'wp_head', [ __CLASS__, 'alternate_link' ], 6 ); } public static function rewrite_rules() { // .md → markdown for that page add_rewrite_rule( '^(.+)\.md$', 'index.php?semantic_md_path=$matches[1]', 'top' ); // /llms-full.txt → full-corpus markdown add_rewrite_rule( '^llms-full\.txt$', 'index.php?semantic_llms_full=1', 'top' ); } public static function query_vars( $vars ) { $vars[] = 'semantic_md_path'; $vars[] = 'semantic_llms_full'; return $vars; } public static function serve() { if ( ! Semantic_Settings::markdown_enabled() ) return; // Whole-site corpus if ( get_query_var( 'semantic_llms_full' ) ) { header( 'Content-Type: text/markdown; charset=utf-8' ); header( 'X-Robots-Tag: noindex' ); header( 'X-Content-Type-Options: nosniff' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain-text markdown response (Content-Type: text/markdown sent above); HTML escaping would corrupt the output. echo self::full_corpus(); exit; } // Per-page markdown $path = get_query_var( 'semantic_md_path' ); if ( $path === '' || $path === null ) return; $path = trim( $path, '/' ); $post_id = url_to_postid( home_url( '/' . $path . '/' ) ); if ( ! $post_id ) $post_id = url_to_postid( home_url( '/' . $path ) ); if ( ! $post_id ) { status_header( 404 ); header( 'Content-Type: text/markdown; charset=utf-8' ); echo "# Not found\n\nNo page matches this URL.\n"; exit; } $post = get_post( $post_id ); // Same public-content gate as every other public surface: published, public, // password-free, non-excluded, publicly-queryable type. Anything else 404s. if ( ! $post || ! Semantic_Settings::is_publicly_servable( $post ) ) { status_header( 404 ); header( 'Content-Type: text/markdown; charset=utf-8' ); echo "# Not found\n"; exit; } header( 'Content-Type: text/markdown; charset=utf-8' ); header( 'X-Robots-Tag: noindex' ); header( 'X-Content-Type-Options: nosniff' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain-text markdown response (Content-Type: text/markdown sent above); HTML escaping would corrupt the output. echo self::render_post( $post ); exit; } /** Advertise the .md alternate on singular pages so agents can discover it. */ public static function alternate_link() { if ( ! Semantic_Settings::markdown_enabled() ) return; if ( ! is_singular() ) return; $post = get_queried_object(); if ( ! $post instanceof WP_Post ) return; // Only advertise .md where we'd actually serve it (skips password-protected, // excluded and non-public posts, matching serve()). if ( ! Semantic_Settings::is_publicly_servable( $post ) ) return; $md = self::md_url( $post ); if ( $md ) { echo '' . "\n"; } } /** Build the .md URL for a post from its permalink. */ public static function md_url( WP_Post $post ) { $url = get_permalink( $post ); if ( ! $url ) return ''; // Insert .md before any trailing slash / query. $parts = wp_parse_url( $url ); $path = isset( $parts['path'] ) ? rtrim( $parts['path'], '/' ) : ''; if ( $path === '' ) return ''; $base = ( $parts['scheme'] ?? 'https' ) . '://' . ( $parts['host'] ?? '' ); return $base . $path . '.md'; } // ------------------------------------------------------------------------- // Rendering // ------------------------------------------------------------------------- /** Render one post as AI-optimised markdown. */ public static function render_post( WP_Post $post ) { global $wpdb; $url = get_permalink( $post ); $analysis = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}semantic_analyses WHERE post_id = %d AND status = 'completed' ORDER BY finished_at DESC LIMIT 1", $post->ID ), ARRAY_A ); $lines = []; $lines[] = '# ' . wp_strip_all_tags( get_the_title( $post ) ); $lines[] = ''; $desc = $analysis['llm_description'] ?? ''; if ( $desc ) { $lines[] = '> ' . $desc; $lines[] = ''; } $lines[] = '**Source:** ' . $url; $lines[] = '**Updated:** ' . get_post_modified_time( 'Y-m-d', true, $post ); $lines[] = ''; // No analysis yet → fall back to a clean text rendering of the content. if ( ! $analysis ) { $lines[] = self::content_to_markdown( $post ); return implode( "\n", $lines ) . "\n"; } // Summary $summary = $analysis['summary'] ?: ''; if ( $summary ) { $lines[] = '## Summary'; $lines[] = ''; $lines[] = $summary; $lines[] = ''; } // LLM context block — the self-contained retrieval paragraph. if ( ! empty( $analysis['llm_context_block'] ) ) { $lines[] = '## Overview'; $lines[] = ''; $lines[] = $analysis['llm_context_block']; $lines[] = ''; } // Key entities (approved, with grounding links) $entities = $wpdb->get_results( $wpdb->prepare( "SELECT name, type, definition, canonical_entity_id, wikidata_qid, same_as FROM {$wpdb->prefix}semantic_suggested_entities WHERE analysis_id = %d AND approval_status = 'approved' ORDER BY relevance_score DESC LIMIT 25", $analysis['id'] ), ARRAY_A ); if ( $entities ) { $lines[] = '## Key entities'; $lines[] = ''; foreach ( $entities as $e ) { $same = json_decode( $e['same_as'] ?? '[]', true ) ?: []; $link = $same[0] ?? ( $e['canonical_entity_id'] ? home_url( '/entities/' . $e['canonical_entity_id'] ) : '' ); $name = $link ? '[' . $e['name'] . '](' . $link . ')' : $e['name']; $row = '- ' . $name; if ( ! empty( $e['type'] ) ) $row .= ' _(' . str_replace( '_', ' ', $e['type'] ) . ')_'; if ( ! empty( $e['definition'] ) ) $row .= ' — ' . $e['definition']; if ( ! empty( $e['wikidata_qid'] ) ) $row .= ' (Wikidata: ' . $e['wikidata_qid'] . ')'; $lines[] = $row; } $lines[] = ''; } // Section outline $sections = json_decode( $analysis['content_sections'] ?? '[]', true ) ?: []; if ( $sections ) { $lines[] = '## Contents'; $lines[] = ''; foreach ( $sections as $sec ) { if ( empty( $sec['heading'] ) ) continue; $lines[] = '### ' . wp_strip_all_tags( $sec['heading'] ); if ( ! empty( $sec['summary'] ) ) { $lines[] = ''; $lines[] = $sec['summary']; } $lines[] = ''; } } // FAQs $faqs = $wpdb->get_results( $wpdb->prepare( "SELECT question, answer FROM {$wpdb->prefix}semantic_suggested_faqs WHERE analysis_id = %d AND approval_status = 'approved' ORDER BY ai_confidence DESC", $analysis['id'] ), ARRAY_A ); if ( $faqs ) { $lines[] = '## FAQ'; $lines[] = ''; foreach ( $faqs as $f ) { $lines[] = '**' . wp_strip_all_tags( $f['question'] ) . '**'; $lines[] = ''; $lines[] = wp_strip_all_tags( $f['answer'] ); $lines[] = ''; } } // Citable snippet if ( ! empty( $analysis['citable_snippet'] ) ) { $lines[] = '---'; $lines[] = ''; $lines[] = $analysis['citable_snippet']; $lines[] = ''; } return implode( "\n", $lines ) . "\n"; } /** Whole-site corpus: every published page's markdown, concatenated. */ public static function full_corpus() { $name = Semantic_Settings::org_name(); $out = [ '# ' . $name . ' — Full Content Corpus', '' ]; $desc = Semantic_Settings::org_description(); if ( $desc ) { $out[] = '> ' . $desc; $out[] = ''; } $types = Semantic_Settings::public_post_types(); $posts = empty( $types ) ? [] : get_posts( [ 'post_type' => $types, 'post_status' => 'publish', 'has_password' => false, 'posts_per_page' => 500, 'orderby' => 'menu_order title', 'order' => 'ASC', 'meta_query' => [ [ 'key' => Semantic_Settings::EXCLUDE_META, 'compare' => 'NOT EXISTS' ] ], ] ); foreach ( $posts as $post ) { $out[] = self::render_post( $post ); $out[] = "\n---\n"; } return implode( "\n", $out ) . "\n"; } /** Lightweight HTML→Markdown for un-analysed pages (headings, links, lists). */ private static function content_to_markdown( WP_Post $post ) { $html = apply_filters( 'the_content', $post->post_content ); $html = preg_replace( '/]*>(.*?)<\/h\1>/is', "\n" . '### $2' . "\n", $html ); $html = preg_replace( '/]*>(.*?)<\/li>/is', '- $1' . "\n", $html ); $html = preg_replace( '/]*href="([^"]*)"[^>]*>(.*?)<\/a>/is', '[$2]($1)', $html ); $text = wp_strip_all_tags( $html ); $text = preg_replace( "/\n{3,}/", "\n\n", $text ); return trim( $text ); } }