Changelog
Every release of the pagegraph package, newest first.
pagegraph@0.18.2
Make the Organization contact point optional
organization.contactPoint in createSeo is now optional. When it is absent, the Organization
JSON-LD omits contactPoint instead of requiring a site to publish a support email. Configurations
that set it render the same output as before.
pagegraph@0.18.1
Fix consumer build diagnostics
Generate sourcemaps for authored document source locations so Vite and Rolldown can trace edited modules back to their original source. Preserve the resolved environment in the build-only Cloudflare Markdown prerender Worker so production prerender preview loads its configuration without a missing-environment warning. Consumer configuration stays the same.
pagegraph@0.18.0
Effect 4.1.2 runtime compatibility
Upgrade the bundled CLI's Effect, TypeSafe AI, and platform runtime to 4.1.2. Programmatic build and audit consumers now require Effect peers compatible with 4.1.2; other library entries and the bundled CLI keep these peers optional. Preserve existing decision-cache keys and validate Effect's image-location answers, including the selected image in their cache identity.
pagegraph@0.17.1
Disallow only the directory of a dynamic route
A disallowed route group with a dynamic route now yields a directory prefix: /s/$formKey
produces Disallow: /s/ instead of Disallow: /s. The old prefix also blocked /solutions,
/senders, and /sitemap.xml, because robots.txt rules match by prefix. Static pages such as
/signup and alwaysDisallow entries are unchanged. A dynamic route at the site root
(/$slug) in a disallowed group now fails the build, since its prefix would block every path.
pagegraph check has a new structural rule, robots-blocks-sitemap-url. It fails when the
rendered robots.txt disallows a sitemap page, /sitemap.xml, or /llms.txt for User-agent: *
and names the URL and the matching rule. Sites that currently block their own sitemap URLs will
fail the check until the Disallow is narrowed.
pagegraph@0.17.0
Run the CLI on Effect 4.0.6 and Pi 1.0.4
Breaking: Pi renamed its Azure OpenAI provider from azure-openai-responses to azure. A workflow
model such as azure-openai-responses/gpt-4.1 in workflows.agent.defaultModel,
workflows.agent.models, or --model now fails with Unknown Pi model; write azure/gpt-4.1
instead. AZURE_OPENAI_API_KEY is unchanged.
The CLI now bundles Effect 4.0.6 and Pi 1.0.4. Committed claims answers stay valid, so
pagegraph claims check keeps replaying them without --refresh.
pagegraph@0.16.0
Use OpenRouter for Jev decisions with Pagegraph attribution
Jev now prefers OPENROUTER_API_KEY and falls back to TYPESAFE_API_KEY when no OpenRouter key is set. OpenRouter decisions and workflow agent calls identify the Pagegraph app for rankings and analytics. Set openrouter.referer and openrouter.title in pagegraph.config.ts to override the identity. Existing committed claims answers remain valid when switching providers.
pagegraph@0.15.1
Capture Markdown in dev when Vite pre-bundles dependencies
pagegraph() now keeps pagegraph and pagegraph/react out of dependency pre-bundling, next to
the runtime entries. A pre-bundled copy gave pages their own document context and defineFacts
registry, so every dev capture (/__pagegraph/markdown.json, <page>.md, claims check --dev)
failed with recorded no <Section> or Unknown document fact.
pagegraph@0.15.0
Review improve workflow edits and apply them later
Improve workflows (content, metadata, schema, links) now submit structured edits instead of
writing files. Each edit names the file, the exact oldText it replaces (or none for a new file),
the newText, a reason, and evidence; reviewItems carry changes a human must make, with their
path, reason, and evidence. PageGraph rejects a submission whose edits do not apply to the current
files, so the agent corrects them in the same turn.
Every improve run writes edits.json and a human-readable review.md to its run directory. Jev
decisions that land in review, including link suggestions, appear there as review items instead of
being dropped.
--dry-run records the edits without changing source files. The new pagegraph apply <run> applies
them later, skipping and reporting any edit whose target text no longer matches; --only <ids>
applies a subset and --check reports without writing. Write mode applies the same recorded edits
during the run.
Breaking: the edit_file and write_file agent tools are removed, along with the action result's
files and outcome fields; run.json reports outcome, edits, and reviewItems for improve
runs. Custom presets that mention those tools should describe structured edits instead.
Resolve and check per-page social images
Declare staticData.seo.head.image: { url, width?, height?, alt? } or a collection entry's
image. Existing article.image remains supported. Share an ogImage(node) resolver between
pagegraph() and createSeo to supply images when a page has no declared image. Page images
win over article images, then the resolver supplies the fallback. Resolved images are absolute
URLs in node.head.image and pageHeads, and render Open Graph image metadata and Twitter
large-image cards.
pagegraph check now fails sitemap-eligible pages without an image or whose same-origin image
asset is missing under public/. Configure ogImage.severity (structural, editorial, or
off) and ogImage.publicDirectory in pagegraph.config.ts. Remote images are not fetched.
Consumers own image rendering and must generate local assets before running the check.
Make agent workflow runs recoverable and bounded
Large executor_execute results are saved in JSON under each run's tool-results/ directory, and
truncated previews tell the agent how to read the full result. Agent presets now guide workflows to
return only needed fields and rows and to read large artifacts in chunks.
workflows.repositoryRoot optionally sets the read boundary for repository tools and context.files.
It defaults to the config directory's Git top level, falling back to the config directory. Context
file paths resolve from this read root, so existing app-relative paths may need a repository-relative
prefix. Writes remain bounded to the application root. Run artifacts under runsDirectory are
readable to support recovery.
Workflow commands accept --from <run-dir|run-id> to resume recorded agent session and research
state while skipping recorded completed calls. Resume verifies that repository HEAD and file
contents still match the checkpoint; completed runs return their recorded output without provider
calls. An action that started without recording completion is refused because replay could repeat
edits. Incomplete runs from before progress checkpoints were recorded cannot be resumed. Missing
model-provider credentials, TYPESAFE_API_KEY, or required Executor configuration are reported
before the first paid model turn.
After three rejected submissions, valid items are retained; rejected items and their indices and
validation reasons are recorded in research.json and run.json.
pagegraph@0.14.1
Show Executor tool types for every search hit
executor_search with details no longer fails when a hit is an MCP-backed tool whose schema has no
TypeScript definitions. A hit whose schema cannot be loaded keeps its search result and reports
types unavailable: <reason>, while the other hits still return their input and result types.
pagegraph@0.14.0
Run read-only workflows on Pi
Breaking: Replace workflows.opencode with workflows.agent in pagegraph.config.ts.
Rename configDirectory to presetDirectory; keep defaultModel, optional per-workflow models,
and optional timeoutMs under workflows.agent. Model IDs use provider/id, and Pi reads model
credentials from its provider-specific environment variables. Use --preset-directory in place
of --opencode-config for CLI overrides.
Breaking: Workflow runs, research checkpoints, and failure artifacts use schemaVersion: 2.
Update artifact readers to use agent in place of opencode and inspect agent.runtime:
pi records contain the model, messages, and usage with total cost; opencode records contain
the model, session ID, and transcript.
research.*, analyze.*, and plan.architecture run in-process on Pi 1.0.0. Pi composes the SEO
agent instructions, AGENTS.md, and each workflow's skill and Executor reference from the configured
preset directory. Missing preset files fail with their paths. Repository tools are read-only and
reject paths outside the project root, including symlink escapes. improve.* workflows use OpenCode
with the same preset directory.
Pi submits structured state through submit_result. Acceptance requires valid workflow state,
at least one Executor catalog search, and a completed relevant Executor call. Invalid submissions
receive validation or evidence guidance in the agent loop; three rejected submissions fail the run.
Research declines Executor approval requests and respects the per-run deadline.
Research connects to Executor through @pi-ext/executor: set EXECUTOR_BASE_URL, plus
EXECUTOR_CLIENT_ID[_FILE] and EXECUTOR_CLIENT_SECRET[_FILE] when Cloudflare Access fronts the
server. Only provider calls that succeeded count as evidence.
Run improve workflows on Pi
Breaking: Every workflow now runs on Pi. Improve workflows continue the research conversation
and apply source changes with guarded edit_file and write_file tools. Dry-run actions expose
only read tools and describe intended changes. Writes stay inside the repository and exclude
.git, node_modules, workflow runs, and agent presets. There is no shell tool.
OpenCode, its SDK install, patched client, preset JSON, and permission rules are removed.
Schema-version-2 artifacts now record agent.runtime as the literal pi.
Breaking: pagegraph init writes .pagegraph/agent. To migrate an existing preset, move
.pagegraph/opencode to .pagegraph/agent, delete opencode.jsonc, and set
workflows.agent.presetDirectory: ".pagegraph/agent" in pagegraph.config.ts.
The preset uses executor_search and executor_execute for live tools and submit_result
for structured workflow results.
improve links can read served target-page sentences with fetch_page, restricted to
suggestion-report origins and subject to robots rules and response size limits.
Together with the Pi research runner, these changes make up release 0.14.0.
pagegraph@0.13.0
Capture rendered Markdown through TanStack Start
Declare staticData.markdown: "rendered" to publish Markdown twins from authored React pages,
with optional staticData.llms groups. Collection instances inherit their route's declarations.
The pagegraph() plugin accepts a canonical Markdown origin and a facts module, and exposes
prerenderPages for Start. The non-deployed pagegraph/tanstack-start/prerender-worker captures
pages through the compiled Start server and writes public twins plus private documents and heads.
Use markdownRequest from pagegraph/tanstack-start/markdown before the Start handler for dev
and prerender capture. Private paths return 404 in production. pagegraph/tanstack-start/react
exports a capture-aware Link; llmsTxt and llmsSection on the server entry compose graph-derived
links without rendering pages. Development rendering catches missing route opt-ins.
Author page documents alongside React markup
pagegraph/react adds DocumentProvider, T, Title, Section (including Section.Item
and Section.Item.Link), Fact, Visual, ForAgents, and ForHumans. Normal rendering preserves
layout; a collector records authored messages, sections, linked items, resolved facts, and audience
from completed HTML. CaptureAnchor supplies the anchor boundary for router integrations, and
messageText extracts static text for head metadata without executing components.
pagegraph adds plain document types, fact definitions, section kinds, markdown writers, pure
llms.txt string builders, and createMarkdownLock. Content and document hashes preserve the
original authored-document format. Both runtime entries remain Effect-free and Node-free.
Augment the root Register interface with facts: typeof facts to check Fact ids.
pagegraph/oxlint adds the pagegraph plugin's no-bare-text and t-children rules for imports
from pagegraph/react. It is a build-only entry with throwing Worker and browser stubs.
Gate claims and inspect captured Markdown
Breaking: Rename seo.config.ts to pagegraph.config.ts (also .js and .mjs).
The CLI rejects the old filename with migration guidance. Keep CLI policies and the graph loader
in this file; configure site identity, robots, Markdown, and claims through the Vite plugin.
Remove top-level origin, disallow, contentSignal, and directives from the CLI config.
tanstackStartGraph supplies the plugin's site; a generic viteGraphLoader requires a site
option, and custom graph loaders return site: { origin, indexable, robots } with the graph.
Add claims: { rules, model, cutoff?, context?, excludeHeads? } to pagegraph() with probability
rules built against claimsInput from pagegraph/claims. Run pagegraph claims check to ask for
missing answers, review them, and commit .pagegraph/decisions/claims/. --refresh replaces answers;
--dev <origin> checks live captured documents. Builds and pagegraph check replay committed
answers without model calls and fail on missing answers or violations.
Use pagegraph markdown show <path> to print a twin and pagegraph markdown find <text> to search
pages and sections. pagegraph markdown lock writes .pagegraph/markdown.lock.json;
--check verifies it without writing.
pagegraph@0.12.0
Build the graph inside a TanStack Start app with pagegraph()
pagegraph/tanstack-start adds a Vite plugin that builds the SEO graph in the app's own Vite
pipeline — aliases, defines, and content plugins such as Fumadocs apply — and ships it to the
server runtime as data. robots.txt and sitemap.xml are one line each:
// vite.config.ts
pagegraph({ origin, indexable, robots: { contentSignal }, collections: "src/lib/seo/collections.ts", routeConfig });
// src/routes/robots[.]txt.ts
import { robotsTxt } from "pagegraph/tanstack-start/server";
export const Route = createFileRoute("/robots.txt")({ server: { handlers: { GET: robotsTxt } } });
The plugin evaluates each route file that declares staticData, never the generated route tree, so
a route that imports server-only modules no longer needs a stub; an app-only route that cannot
evaluate in Node fails with its file name and goes in exclude. routeConfig takes the
seoRouteConfig options and adds the derived robots exclusions. tanstackStartGraph({ root })
gives seo.config.ts and loadPageHeads the same graph, and contentCollection maps any pages
with a url (Fumadocs getPages()) to a collection.
Declare a page's head once
A static route declares its head in staticData.seo.head and renders it with head: seo.head; the
graph, pageHeads, and pagegraph check read the same title, description, and FAQs. A route's
titleTemplate ("%s | Example Blog") applies to the title seoHead renders and to the route's
collection pages. createSeo({ transformHead }) post-processes every rendered head.
Fail loud when a runtime bundle imports build code
pagegraph/config, pagegraph/vite, pagegraph/audit, and pagegraph/tanstack-start resolve to a
stub under the workerd, worker, and browser export conditions that throws at import with the
entry's name, instead of crashing on a native binding when a request first runs. The README opens
with what runs where.
Keep every workflow failure
Workflows drop null from model JSON before decoding, give the repair turn every schema issue at
once, and write failure.json with the stage, OpenCode session id, transcript, and cause chain for
every error after a run starts. The error message ends with the artifact path and a next step.
Breaking changes
selectPageHeadNodes,pageHeads,PageHead, andPageHeadsOptionsare exported frompagegraph, notpagegraph/config.loadPageHeadsstays inpagegraph/config.pageHeadsreadsnode.head(a declaredstaticData.seo.head, or a collection page through its route'stitleTemplate) instead ofnode.instance.PageSeoInstanceisSeoPageHeadandFAQItemisSeoFaq.@tanstack/router-generatoris an optional peer: install it withpagegraph/viteorpagegraph/tanstack-start.research-failure.jsonis replaced byfailure.json.seo.config.tsdisallowis optional; withtanstackStartGraphthe robots policy comes from the plugin and must not be repeated.
Migrating a TanStack Start app: replace the hand-written graph module and viteGraphLoader with
pagegraph() plus a collections module, move seoRouteConfig options to routeConfig, serve
robots and sitemap with robotsTxt/sitemapXml, move static heads into staticData.seo.head with
head: seo.head, and move hand-appended title suffixes into titleTemplate. The
TanStack Start cookbook
has before/after snippets for each step.
pagegraph@0.11.0
Load page heads from pagegraph/config
Graph loaders created by viteGraphLoader accept an input that is passed to the entry export.
pagegraph/config exports selectPageHeadNodes, pageHeads, and loadPageHeads, which return
{ path, title, description } for indexable pages, honor caller excludes, fail with the page path
when a selected page lacks a title or description, and dispose the loader for the caller.
Run the OpenCode host from an isolated install
improve and research workflows load the pinned OpenCode host from their own verified Bun
install, so a consumer's Effect or Drizzle overrides no longer change its runtime. Failures keep the
HTTP operation, status, and body, research keeps its transcript, and a discovery-only run gets one
recovery turn.
Require stable Effect 4
The Effect peer dependencies are ^4.0.0.
pagegraph@0.10.1
Accept angle-bracket placeholders in FAQ answers
faq-not-visible no longer reports a plain-text JSON-LD answer that contains
placeholders such as samva-<id>._domainkey when the page renders them escaped.
Answers with real HTML markup are still compared by their text.
pagegraph@0.10.0
Check rendered content and page freshness
pagegraph check --site <url> fetches every declared page from a running
server and checks its server-rendered HTML. Indexable pages need exactly one
H1, a self-referencing canonical, and robots meta that matches the declaration.
FAQ answers and offer prices in JSON-LD must also appear in the visible text.
Skipped heading levels, an H1 that shares no term with the title, and pages
under a configured content.minWords floor are reported as warnings.
pagegraph audit now reports the heading and structured-data findings for every
HTML page it audits.
Routes can declare modifiedAt, which becomes the sitemap's <lastmod> just as
content dates already do. Dates must now be ISO 8601 (2026-09-29, or a
date-time with an offset): check fails on any other form, and the sitemap
refuses to render one instead of guessing its day. Set
freshness: { maxAgeDays } to report stale pages in check, and run
pagegraph stale to list pages to refresh, oldest first.
pagegraph@0.9.0
Find better internal linking opportunities
PageGraph now finds sitemap pages that links alone miss and suggests useful
contextual links backed by exact page text. Use links candidates --site to
review ranked suggestions, then pass them to improve links --suggestions to
approve edits.
pagegraph@0.8.4
Preserve workflow evidence and diagnose interrupted research
Selected pages retain their surrounding link relationships, and Executor evidence requires actual completed provider invocations. Research instructions inspect current tool schemas and respect the requested scope. OpenCode completions support configurable deadlines, bounded interruption, and activity diagnostics. Validated research is saved before decision inference so downstream failures retain useful evidence. Git mutation checks handle nested project roots and changed submodules.
pagegraph@0.8.3
Attribute Executor evidence from structured search results
PageGraph now recognizes tool identifiers returned in Executor search-result path fields, including
JSON-encoded output, so valid provider calls satisfy the workflow evidence gate. Discovery stays
limited to those structured fields, preventing unrelated dotted metadata from authorizing a call.
pagegraph@0.8.2
Recover incomplete workflow results
When an OpenCode turn collects Executor evidence but omits its final workflow JSON, PageGraph now runs one tool-free repair turn within the original deadline. Repair restores the session's prior permissions, and deadline failures report the configured timeout instead of an opaque transport error.
pagegraph@0.8.1
Wait for Executor activation
Workflow commands now wait for the configured Executor plugin to appear in OpenCode's plugin registry before starting a run. Registry failures still propagate immediately, and a bounded timeout keeps missing or misconfigured Executor installations fail-closed.
pagegraph@0.8.0
Run page-backed SEO workflows
Combine PageGraph's deterministic route graph with an embedded OpenCode agent, project-owned configuration, live Executor tools, and Jev decisions. The new workflow-oriented CLI covers keyword, competitor, and authority research; SERP, content, and AI-search analysis; architecture planning; and content, metadata, schema, and internal-link improvements.
pagegraph initscaffolds a customizable.pagegraph/opencodepreset with one SEO agent, workflow-specific skills, and editable Executor starter recipes.research,analyze, andplanworkflows collect evidence without changing project files.improveworkflows require a clean Git tree, support--dry-run, leave edits uncommitted, and record the resulting file changes.- Every run persists its graph, project context, OpenCode session, live-tool evidence, Jev answers,
Git provenance, and model provenance under
.pagegraph/runs.
This release replaces the low-level public decision commands and input-oriented exports with the smaller workflow surface. OpenCode owns model authentication and configuration; an Executor plugin provides live SEO integrations discovered at runtime.
pagegraph@0.7.0
Answer SEO decisions with Jev
Add a reusable decision runner and five typed decision families, answered in one TypeSafe System One call per input. Confident answers produce a plan; answers inside the confidence band go to a review queue. Nothing is applied automatically.
pagegraph decide serp— classify the SERP format and score our page's fit, intent, and title (aligned | mismatch | review).pagegraph decide content— score the content rubric and place page value on thin | adequate | strong (pass | flag | review).pagegraph decide fit— choose the best existing page for a query, or flag cannibalization or a gap (map | cannibalized | gap | review).pagegraph decide meta— rank supplied title/description candidates (choose:| review). pagegraph decide authority— judge a link target's legitimacy, spam, and outreach worth, plus its fit (accept | spam | review).pagegraph decide links(and the retainedlinks decide) — real reason, anchor present, direction (A→B / B→A / both), and relevance, with top-K per-source budget selection.
Shared flags: --model, --threshold, --concurrency, --cache, --review-out,
and --budget for links. Every record carries the model id and an input hash.
pagegraph@0.6.1
Assert contextual coverage on the rendered graph
pagegraph links verify now closes the loop between declared and served links:
links verify <url> --emit-rendered <path>writes the crawl's raw edge set as a versionedlinks-renderedartifact that preserves each anchor'sregion(bodyvsnav/footer/header) and the provenance a gate needs —origin,seed, the rendered page paths, and the crawl'slimit,truncated,bodyTruncated, andfailures. It is also a drop-in input forlinks candidates --rendered.links verify --rendered <path>replays a saved artifact instead of crawling, so one crawl can be asserted repeatedly without re-fetching.links verify --assert-coverageevaluates theseo.config.tscoveragerules against the anchors a crawler actually receives. Only same-origin body-region anchors count, matchingcheck'srelated-only semantics; the rule universe stays the declared graph's sitemap-eligible pages. An unmet rule exits 1, and the command refuses to assert — rather than reporting a false pass — on a truncated crawl, a page that failed to fetch, a truncated body, a missingcoveragepolicy, or an origin mismatch.
The default --json summary is unchanged; the coverage block appears only under
--assert-coverage.
pagegraph@0.6.0
Verify and plan contextual links
Add pagegraph links, three commands over the link graph:
links verify <url>crawls served HTML and reports homepage depth, rendered orphans, and the declared-vs-rendered gap. Anchor extraction honors<base href>, and the crawl dedupes redirect targets and surfaces truncated bodies.links candidatesproposes reviewable, section-clustered contextual links from the declared graph, excluding anchors already rendered, with an optional--decidepass.links decideanswers real-reason and anchor-present probabilities for a candidate set through TypeSafe System One (Jev), routing below-threshold answers to human review.
pagegraph check also gains --require-inbound (with a configurable policy) so a
sitemap-eligible page must have at least N incoming contextual edges.
pagegraph@0.5.1
Bundle Effect into the CLI
The published pagegraph binary now bundles effect and @effect/platform-bun. It no longer asks
consumers to install optional Effect peers, and no longer breaks when the consumer resolves a
different Effect RC — the effect/unstable/cli constructors rename between RCs (Flag.boolean →
Flag.Boolean at rc.113). The library entries remain Effect-free.
pagegraph@0.5.0
Compare timestamped SEO audits
Add pagegraph diff <before.json> <after.json> with deterministic human and JSON
output, schema validation, and regression-aware exit behavior. Structural
findings, scanner failures, and lost coverage fail the command, while editorial
changes and volatile scanner evidence remain non-blocking.
pagegraph@0.4.1
Default optional CLI switches
Treat omitted boolean switches as false so pagegraph audit and the shared --json option work without requiring unrelated flags.
pagegraph@0.4.0
Audit any website from the CLI
Add a framework-independent pagegraph audit command and public audit toolkit with composable Effect scanners, pure SEO rules, hardened network validation, Lighthouse evidence, and versioned human or JSON reports.
pagegraph@0.3.0
Compose any schema.org entity
Add custom site and page JSON-LD with typed helpers, stable entity references, graph composition, and an optional transform for extending, replacing, suppressing, or expanding generated schemas. Existing JSON-LD generators continue to work unchanged.
pagegraph@0.2.1
Add organization legal identity fields
Organization JSON-LD can now include a legal name and structured postal address, making business identity details easier for search engines and agents to understand.
pagegraph@0.2.0
Add Content-Signal to robots.txt
renderRobots now takes contentSignal (the preference list), extra directives, and a transform for a last-mile override. The same fields live on seo.config.ts so pagegraph robots prints the same file as the route. Preview hosts still omit the origin-wide group lines. No default signal.
pagegraph@0.1.0
Initial release
Route-declared SEO for TanStack Router: declare once on the route, then derive the sitemap, robots.txt, breadcrumbs, JSON-LD, cross-link graph, Vite coverage gate, and CLI from that graph.