Skip to content

Trace citation metadata through Yoast SEO and NLWeb - #253

Merged
dknauss merged 5 commits into
mainfrom
claude/project-thread-6zozsp
Nov 6, 2026
Merged

dknauss merged 5 commits into
mainfrom
claude/project-thread-6zozsp

Conversation

@dknauss

@dknauss dknauss commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Requested by Dan · project thread

Summary

  • What changed? This adds tools/yoast-nlweb-trace/, a reproducible trace of Borges' citation metadata through Yoast SEO's schema graph and NLWeb on the way to an AI agent. setup.sh builds a local site: WordPress 7.1.4 on SQLite, this checkout of Borges, Yoast SEO 28.8 with schema aggregation on, and NLWeb at b423f15 (both its WordPress plugin and its Python ingest code). The fixture post is the markup-value corpus: 12 works, 8 DOIs, 2 ISBNs, and 78 field values. capture.sh saves every stage, measure.py scores each field at each stage, and probe.py asks questions that only the fields lost along the way could answer. Results are in results/2026-11-04/, and the README explains the method and the findings. The only change outside tools/ is one line in .prettierignore.
  • Why was this change needed? Andrey asked how much of the metadata survives Yoast Schema Aggregation and NLWeb, which is a better question than whether JSON-LD is on the page. This follows the markup-value study (Add tools/markup-value: measure what Borges markup gives machines #203, markup-value: add GPT-6.1 (Codex) to the language-model results #233).

Findings

Labeled field values that survive, out of 78:

Stage Borges today With #252 Prototype: records in citation Prototype: records as graph nodes
Page HTML, all JSON-LD 78 78 78 78
Yoast's graph only 12 12 78 78
Yoast aggregator feed 12 12 78 78
NLWeb index (crawl / feed) 12 / 12 12 / 12 78 / 78 71 / 71
NLWeb WordPress plugin, MCP answer 0 12 78 12
  • Today an agent gets the citation links but not the records. It gets titles and types, plus DOIs inside @id URLs. It gets no authors, years, journals, pages, ISBNs, or publishers. Those stay in Borges' ItemList, and nothing past Yoast reads that list.
  • NLWeb's trim_schema_json drops every ItemList. It also drops WebPage nodes and every top-level publisher. So crawling the page gets no more than Yoast's feed does.
  • NLWeb's WordPress plugin got no citations at all. That's a Borges bug, fixed in Read the post from Yoast's context when adding citations #252 (draft).
  • Full records can get through. Either prototype carries every field through Yoast and its aggregator. Embedding the records in citation is the only option that also reaches the plugin's MCP answer. The cost: Yoast's script on this page goes from 7.6 KB to about 28 KB.
  • No model was run. The ranking LLM is a stub that records its prompt, so the tables show what an agent is given, not what it answers. The study makes no claims about ranking or AI citation.

The prototypes live in trace-variants.php, a test-site mu-plugin. Whether Borges should adopt one is Dan's call, and it would be its own PR.

Validation

  • Built the site twice from scratch with setup.sh, and the numbers matched
  • npm run format:check passes
  • No plugin code changes, so the JS/PHP lint, test, and build items don't apply. tools/ is in .distignore.
  • CI

Checklist

  • No linked issue. It answers Andrey's question about the markup-value study.
  • No sensitive details disclosed publicly

Generated by Claude Code

Adds tools/yoast-nlweb-trace: a local WordPress 7.1 + Borges + Yoast SEO
28.8 + NLWeb site, built by setup.sh, and scripts that capture the
fixture post (the markup-value corpus) at each stage on the way to an
agent: the page's JSON-LD, Yoast's graph, Yoast's schema aggregator feed,
NLWeb's index (its own ingest code, from a crawl and from the feed), and
NLWeb's WordPress plugin answering an MCP `ask`. measure.py scores 78
field values per stage; probe.py checks which questions each stage can
still answer.

Today only titles (and DOIs inside @id URLs) get past Yoast: the full
records stay in Borges' ItemList, which NLWeb's trim drops. NLWeb's
WordPress plugin got no citations at all (fixed in #252). Two prototypes
show what putting full records into Yoast's graph would carry, and at
what page weight. No model was run: the ranking LLM is a recording stub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPtgA2F1J2N3yCPk3cUX1P
@dknauss dknauss self-assigned this Nov 4, 2026
The trace site's setup scripts set WordPress and plugin options on the
throwaway site they build. tools/ is not in the release package, so the
no-persistence audit now skips it, as the metrics doc says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPtgA2F1J2N3yCPk3cUX1P
@codecov

codecov Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.68%. Comparing base (44217c1) to head (a19c004).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##               main     #253   +/-   ##
=========================================
  Coverage     88.68%   88.68%           
  Complexity       67       67           
=========================================
  Files           102      102           
  Lines         10989    10989           
  Branches        962      962           
=========================================
  Hits           9746     9746           
  Misses          445      445           
  Partials        798      798           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…-builder into yoast-trace

# Conflicts:
#	docs/current-metrics.md

dknauss commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T17:51:09.319809Z 6b7b5e7 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67d9edf084

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/yoast-nlweb-trace/measure.py Resolved
Comment thread tools/yoast-nlweb-trace/README.md Resolved
measure.py read only 1-page.html, which the committed runs replace with
1-page-json-ld.json, so the published tables could not be recomputed
without the stack. It now falls back to the extracted JSON-LD and picks
Yoast's graph as the block with an @graph. Recomputing all four committed
runs reproduces their survival.json byte for byte.

The README now says the pr-252 rows need #252's context fix (78947a5 or
later) checked out, and keeps the main baseline run separate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPtgA2F1J2N3yCPk3cUX1P
@dknauss
dknauss marked this pull request as ready for review November 6, 2026 17:48

dknauss commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

@codex review


Generated by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 6b7b5e775d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

dknauss added a commit that referenced this pull request Nov 6, 2026
* Read the post from Yoast's context when adding citations

Borges added the cited works to Yoast SEO's Article (or WebPage) piece
using the main query's post, so a graph Yoast built for another caller
had no citations. NLWeb's WordPress plugin is one: it calls
YoastSEO()->meta->for_post() to answer agents over MCP, and its answers
lost the citation edge. Found while tracing citation metadata through
Yoast and NLWeb on a local site.

The Yoast filters now take the post from the context Yoast passes, and
bibliography_builder_json_ld_page_url() accepts that post, passing it
to its filter as a second argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPtgA2F1J2N3yCPk3cUX1P

* Satisfy Psalm and update the PHP line count

get_permalink() takes a WP_Post or an ID, so pass the context post's ID
when it is some other object, and declare the context post as an object.
docs/current-metrics.md follows the new first-party PHP line count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPtgA2F1J2N3yCPk3cUX1P

* Type the post get_cited_work_refs() accepts as any object

It already reads any object with post_content; Psalm now accepts the
post from Yoast's context there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPtgA2F1J2N3yCPk3cUX1P

* Put full citation records into Yoast SEO's graph

Yoast's schema aggregator and NLWeb (crawler and WordPress plugin MCP
answers) read only Yoast's graph, where Borges put {@type, @id, name}
stubs, so 12 of 78 traced field values reached them. The Yoast Article
and WebPage filters now embed each cited work's full record, the same
one the ItemList carries, deduplicated by @id and without @context.

The bibliography_builder_yoast_citation_records filter returns to the
short references. Rank Math and the no-SEO WebPage node keep them.

Verified with tools/yoast-nlweb-trace (PR #253): 78/78 at the Yoast
graph, aggregator feed, NLWeb index, and NLWeb MCP answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPtgA2F1J2N3yCPk3cUX1P

---------

Co-authored-by: Claude <noreply@anthropic.com>
…-builder into yoast-trace

# Conflicts:
#	docs/current-metrics.md
@dknauss
dknauss merged commit baecb72 into main Nov 6, 2026
20 checks passed
@dknauss
dknauss deleted the claude/project-thread-6zozsp branch November 6, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants