pagegraph 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +325 -66
  2. package/dist/anthropic-messages-TqKpKmfW.js +1067 -0
  3. package/dist/anthropic-messages-TqKpKmfW.js.map +1 -0
  4. package/dist/audit.d.ts +4 -2
  5. package/dist/audit.js +5 -5
  6. package/dist/audit.js.map +1 -1
  7. package/dist/azure-openai-responses-DJjBLwoI.js +231 -0
  8. package/dist/azure-openai-responses-DJjBLwoI.js.map +1 -0
  9. package/dist/build-only/audit.js +1 -1
  10. package/dist/build-only/claims.d.ts +1 -0
  11. package/dist/build-only/claims.js +6 -0
  12. package/dist/build-only/claims.js.map +1 -0
  13. package/dist/build-only/config.js +1 -1
  14. package/dist/build-only/oxlint.d.ts +1 -0
  15. package/dist/build-only/oxlint.js +6 -0
  16. package/dist/build-only/oxlint.js.map +1 -0
  17. package/dist/build-only/tanstack-start.js +1 -1
  18. package/dist/build-only/vite.js +1 -1
  19. package/dist/{checks-uZk5Em9A.js → checks-BS-aDXB_.js} +2 -2
  20. package/dist/checks-BS-aDXB_.js.map +1 -0
  21. package/dist/claims-Bch7Lxlo.d.ts +92 -0
  22. package/dist/claims-Bv7voeEV.js +431 -0
  23. package/dist/claims-Bv7voeEV.js.map +1 -0
  24. package/dist/claims.d.ts +94 -0
  25. package/dist/claims.js +2 -0
  26. package/dist/cli.js +17607 -43387
  27. package/dist/cli.js.map +1 -1
  28. package/dist/cloudflare-workers-ai-system-one-f3ZrvNYZ.js +41 -0
  29. package/dist/cloudflare-workers-ai-system-one-f3ZrvNYZ.js.map +1 -0
  30. package/dist/config.d.ts +9 -29
  31. package/dist/config.js +2 -2
  32. package/dist/config.js.map +1 -1
  33. package/dist/{content-kcEnQdfi.js → content-CHd0X4s6.js} +2 -2
  34. package/dist/{content-kcEnQdfi.js.map → content-CHd0X4s6.js.map} +1 -1
  35. package/dist/{content-B1cPtL8b.d.ts → content-DgNr7apS.d.ts} +4 -5
  36. package/dist/declare-D5o7hwmv.js.map +1 -1
  37. package/dist/diagnostics-Wt5faNDM.js +108 -0
  38. package/dist/diagnostics-Wt5faNDM.js.map +1 -0
  39. package/dist/dist-B6zg_Z3Z.js +4794 -0
  40. package/dist/dist-B6zg_Z3Z.js.map +1 -0
  41. package/dist/document-CztEu3Vd.d.ts +109 -0
  42. package/dist/document-Dvemcv7V.js +379 -0
  43. package/dist/document-Dvemcv7V.js.map +1 -0
  44. package/dist/env-api-keys-C-OA0yM-.js +124 -0
  45. package/dist/env-api-keys-C-OA0yM-.js.map +1 -0
  46. package/dist/error-body-CyiejW4b.js +103 -0
  47. package/dist/error-body-CyiejW4b.js.map +1 -0
  48. package/dist/facts-1OKclPOk.js +115 -0
  49. package/dist/facts-1OKclPOk.js.map +1 -0
  50. package/dist/github-copilot-headers-CEpMOpQF.js +24 -0
  51. package/dist/github-copilot-headers-CEpMOpQF.js.map +1 -0
  52. package/dist/google-generative-ai-BxABKIKO.js +330 -0
  53. package/dist/google-generative-ai-BxABKIKO.js.map +1 -0
  54. package/dist/google-shared-0gpM2jFi.js +19471 -0
  55. package/dist/google-shared-0gpM2jFi.js.map +1 -0
  56. package/dist/google-vertex-D8Eb5cbu.js +376 -0
  57. package/dist/google-vertex-D8Eb5cbu.js.map +1 -0
  58. package/dist/{declare-DVgdEdOL.d.ts → graph-D1PIndsC.d.ts} +117 -6
  59. package/dist/{graph-Bcsourlv.js → graph-DMNOLXLu.js} +23 -13
  60. package/dist/graph-DMNOLXLu.js.map +1 -0
  61. package/dist/hash-Dp7EzZUo.js +18 -0
  62. package/dist/hash-Dp7EzZUo.js.map +1 -0
  63. package/dist/headers-BRNMVpzu.js +19 -0
  64. package/dist/headers-BRNMVpzu.js.map +1 -0
  65. package/dist/index-C5w9ripz.d.ts +204 -0
  66. package/dist/index.d.ts +6 -160
  67. package/dist/index.js +37 -5
  68. package/dist/index.js.map +1 -1
  69. package/dist/inline-BbS93GAT.js +682 -0
  70. package/dist/inline-BbS93GAT.js.map +1 -0
  71. package/dist/json-parse-C10znB7l.js +383 -0
  72. package/dist/json-parse-C10znB7l.js.map +1 -0
  73. package/dist/llms-Dhr_5u2O.js +19 -0
  74. package/dist/llms-Dhr_5u2O.js.map +1 -0
  75. package/dist/markdown-C6dRE9zD.js +168 -0
  76. package/dist/markdown-C6dRE9zD.js.map +1 -0
  77. package/dist/markdown-path-lJB9BAVZ.js +6 -0
  78. package/dist/markdown-path-lJB9BAVZ.js.map +1 -0
  79. package/dist/{message-DT_CjUdt.js → message-CuBjgZ74.js} +2 -2
  80. package/dist/{message-DT_CjUdt.js.map → message-CuBjgZ74.js.map} +1 -1
  81. package/dist/mistral-conversations-CvaqBWjR.js +690 -0
  82. package/dist/mistral-conversations-CvaqBWjR.js.map +1 -0
  83. package/dist/models-DcT5qOEL.js +1203 -0
  84. package/dist/models-DcT5qOEL.js.map +1 -0
  85. package/dist/node-Ccvru9GE.js +26 -0
  86. package/dist/node-Ccvru9GE.js.map +1 -0
  87. package/dist/node-MEosBOF2.js +2403 -0
  88. package/dist/node-MEosBOF2.js.map +1 -0
  89. package/dist/openai-BCBEd2eO.js +17350 -0
  90. package/dist/openai-BCBEd2eO.js.map +1 -0
  91. package/dist/openai-codex-responses-vJqxeeRC.js +1268 -0
  92. package/dist/openai-codex-responses-vJqxeeRC.js.map +1 -0
  93. package/dist/openai-completions-Cb_fRAbI.js +989 -0
  94. package/dist/openai-completions-Cb_fRAbI.js.map +1 -0
  95. package/dist/openai-prompt-cache-Bc6qNWt0.js +10 -0
  96. package/dist/openai-prompt-cache-Bc6qNWt0.js.map +1 -0
  97. package/dist/openai-responses-sDuh82G5.js +268 -0
  98. package/dist/openai-responses-sDuh82G5.js.map +1 -0
  99. package/dist/openai-responses-shared-mhuo1Jov.js +599 -0
  100. package/dist/openai-responses-shared-mhuo1Jov.js.map +1 -0
  101. package/dist/openrouter-images-CRmBkzpX.js +127 -0
  102. package/dist/openrouter-images-CRmBkzpX.js.map +1 -0
  103. package/dist/oxlint.d.ts +58 -0
  104. package/dist/oxlint.js +202 -0
  105. package/dist/oxlint.js.map +1 -0
  106. package/dist/page-heads-BfjWQvau.d.ts +108 -0
  107. package/dist/{page-heads-6y-CARRQ.js → page-heads-BgbaIjj9.js} +3 -3
  108. package/dist/page-heads-BgbaIjj9.js.map +1 -0
  109. package/dist/pi-Bvw6J0gh.js +56795 -0
  110. package/dist/pi-Bvw6J0gh.js.map +1 -0
  111. package/dist/pi-messages-CejiVYvO.js +308 -0
  112. package/dist/pi-messages-CejiVYvO.js.map +1 -0
  113. package/dist/{projections-DeEFe7XT.d.ts → projections-Bk-qOOsR.d.ts} +2 -2
  114. package/dist/provider-env-t38GK5FF.js +39 -0
  115. package/dist/provider-env-t38GK5FF.js.map +1 -0
  116. package/dist/provider-retry-DzKoh1X7.js +80 -0
  117. package/dist/provider-retry-DzKoh1X7.js.map +1 -0
  118. package/dist/react.d.ts +3 -2
  119. package/dist/react.js +2 -1
  120. package/dist/react.js.map +1 -1
  121. package/dist/rolldown-runtime-B-lAHAz2.js +37 -0
  122. package/dist/sanitize-unicode-Ds14l9Om.js +28 -0
  123. package/dist/sanitize-unicode-Ds14l9Om.js.map +1 -0
  124. package/dist/sdk-C9_wiO6w.js +12503 -0
  125. package/dist/sdk-C9_wiO6w.js.map +1 -0
  126. package/dist/state-BuJP_3Lr.js +28102 -0
  127. package/dist/state-BuJP_3Lr.js.map +1 -0
  128. package/dist/system-one-shared-Dl10ZF7Z.js +167 -0
  129. package/dist/system-one-shared-Dl10ZF7Z.js.map +1 -0
  130. package/dist/tanstack-start/index.d.ts +29 -13
  131. package/dist/tanstack-start/index.js +391 -42
  132. package/dist/tanstack-start/index.js.map +1 -1
  133. package/dist/tanstack-start/markdown.d.ts +12 -0
  134. package/dist/tanstack-start/markdown.js +149 -0
  135. package/dist/tanstack-start/markdown.js.map +1 -0
  136. package/dist/tanstack-start/prerender-worker.d.ts +2 -0
  137. package/dist/tanstack-start/prerender-worker.js +7 -0
  138. package/dist/tanstack-start/prerender-worker.js.map +1 -0
  139. package/dist/tanstack-start/react.d.ts +7 -0
  140. package/dist/tanstack-start/react.js +12 -0
  141. package/dist/tanstack-start/react.js.map +1 -0
  142. package/dist/tanstack-start/server.d.ts +9 -3
  143. package/dist/tanstack-start/server.js +26 -2
  144. package/dist/tanstack-start/server.js.map +1 -1
  145. package/dist/transform-messages-BLJ8EfK6.js +468 -0
  146. package/dist/transform-messages-BLJ8EfK6.js.map +1 -0
  147. package/dist/typesafe-system-one-CwEWMV8P.js +25 -0
  148. package/dist/typesafe-system-one-CwEWMV8P.js.map +1 -0
  149. package/dist/{vite-graph-loader-01p1WxEq.js → vite-graph-loader-CsGiYNWE.js} +6 -5
  150. package/dist/vite-graph-loader-CsGiYNWE.js.map +1 -0
  151. package/dist/vite-graph-loader-DZCwejuJ.d.ts +133 -0
  152. package/package.json +47 -10
  153. package/dist/checks-uZk5Em9A.js.map +0 -1
  154. package/dist/graph-Bcsourlv.js.map +0 -1
  155. package/dist/graph-BxCY90vq.d.ts +0 -104
  156. package/dist/graph-CBIY71w6.d.ts +0 -57
  157. package/dist/page-heads-6y-CARRQ.js.map +0 -1
  158. package/dist/page-heads-DNkjMUEN.d.ts +0 -21
  159. package/dist/vite-graph-loader-01p1WxEq.js.map +0 -1
  160. package/dist/vite-graph-loader-Btgjz5RH.d.ts +0 -56
package/README.md CHANGED
@@ -26,20 +26,194 @@ bun add pagegraph
26
26
  bun add -D lighthouse # only for `pagegraph audit` performance evidence
27
27
  ```
28
28
 
29
+ ## Authored pages
30
+
31
+ Keep authored text beside its React layout. `DocumentProvider` records only its page body when
32
+ supplied a collector; ordinary rendering preserves the layout. `Title` assigns a heading to the
33
+ nearest section or item, `Fact` resolves shared values, and `Visual` describes content for the
34
+ markdown document without rendering its children during capture.
35
+
36
+ ```tsx
37
+ import { renderToStaticMarkup } from "react-dom/server";
38
+ import { defineFacts, fact, documentMarkdown } from "pagegraph";
39
+ import {
40
+ DocumentProvider, T, Title, Section, Fact, createCollector, finishDocument,
41
+ } from "pagegraph/react";
42
+
43
+ const facts = defineFacts({ attempts: fact.number(8) });
44
+ declare module "pagegraph" {
45
+ interface Register { facts: typeof facts }
46
+ }
47
+
48
+ const collector = createCollector({ path: "/email", site: "https://example.com", facts });
49
+ const page = (
50
+ <DocumentProvider collector={collector}>
51
+ <Section kind="prose" id="intro">
52
+ <h1><Title>Email delivery</Title></h1>
53
+ <p><T>Retry up to <Fact id="attempts" /> times.</T></p>
54
+ </Section>
55
+ </DocumentProvider>
56
+ );
57
+ // Your SSR renderer supplies completed HTML, whose markers determine document order.
58
+ const html = renderToStaticMarkup(page);
59
+ const document = finishDocument(collector, {
60
+ title: "Email delivery", description: "Reliable delivery with retries.",
61
+ }, html);
62
+ const markdown = documentMarkdown(document, "https://example.com");
63
+ ```
64
+
65
+ Use `defineSectionKind` for custom formatting; its markdown writer provides `text`, `heading`,
66
+ and `table`:
67
+
68
+ ```ts
69
+ import { defineSectionKind } from "pagegraph";
70
+ const comparison = defineSectionKind({
71
+ kind: "comparison",
72
+ markdown: (section, md) => md.heading(2, section.title)
73
+ + md.table(section.items.map((item) => item.cells)),
74
+ }); // pass kind={comparison} to Section
75
+ ```
76
+
77
+ `Section.Item.Link` records a card's destination, while `CaptureAnchor` is the
78
+ public anchor boundary for router `createLink` integrations. `ForAgents` and `ForHumans` mark
79
+ audience-specific text; markdown excludes human-only content. `messageText` extracts text or
80
+ inline markdown for head metadata. The root entry also provides `hashDocument`,
81
+ `createMarkdownLock`, and pure `llmsMarkdown`/`llmsSection` string builders.
82
+
83
+ ## Rendered markdown with TanStack Start
84
+
85
+ Declare rendered pages on the route that owns `DocumentProvider`. Collection instances inherit
86
+ these fields from their parameter route; the parameter template itself is never captured.
87
+
88
+ ```tsx
89
+ import { createFileRoute } from "@tanstack/react-router";
90
+ import { DocumentProvider, Section, T, Title } from "pagegraph/react";
91
+ import { Link } from "pagegraph/tanstack-start/react";
92
+
93
+ export const Route = createFileRoute("/pricing")({
94
+ staticData: {
95
+ seo: { kind: "page", head: { title: "Pricing", description: "Plans for your team." } },
96
+ markdown: "rendered",
97
+ llms: "Product",
98
+ },
99
+ head: () => ({ meta: [{ title: "Pricing" }, { name: "description", content: "Plans for your team." }] }),
100
+ component: () => (
101
+ <DocumentProvider>
102
+ <Section kind="prose" id="plans">
103
+ <h1><Title>Plans for your team</Title></h1>
104
+ <Link to="/"><T>Back home</T></Link>
105
+ </Section>
106
+ </DocumentProvider>
107
+ ),
108
+ });
109
+ ```
110
+
111
+ `markdown: "source"` identifies pages whose Markdown is supplied by the application; pagegraph
112
+ captures only `"rendered"` pages. `llms` sets an optional llms.txt group. Use the capture-aware
113
+ `Link` for authored links: it uses TanStack's ordinary Link outside a capture and records resolved
114
+ anchor destinations during capture. The plugin adds `source=` locations to authored JSX imported
115
+ from `pagegraph/react`. In development, `DocumentProvider` rejects a current route lacking
116
+ `staticData.markdown: "rendered"`.
117
+
118
+ Configure the plugin and pass its fixed capture page to Start:
119
+
120
+ ```ts
121
+ import { pagegraph } from "pagegraph/tanstack-start";
122
+
123
+ const graph = pagegraph({
124
+ origin: "https://preview.example.com", // this deployment's SEO origin
125
+ collections: "src/collections.ts",
126
+ markdown: {
127
+ origin: "https://example.com", // canonical document and twin links
128
+ serverEntry: "dist/server/index.js", // default; relative to the Vite root
129
+ },
130
+ facts: "src/facts.ts", // exports `facts`; evaluated in the pagegraph environment
131
+ });
132
+
133
+ // In your Vite plugins:
134
+ // tanstackStart({
135
+ // prerender: { enabled: true, autoStaticPathsDiscovery: false, crawlLinks: false, failOnError: true },
136
+ // pages: [...graph.prerenderPages],
137
+ // }), graph
138
+ ```
139
+
140
+ For Cloudflare, wire the non-deployed prerender Worker to the compiled Start server. Add this to
141
+ `cloudflare()` alongside the production Worker's configuration:
142
+
143
+ ```ts
144
+ experimental: {
145
+ prerenderWorker: {
146
+ config: {
147
+ name: "app-markdown-prerender",
148
+ main: "pagegraph/tanstack-start/prerender-worker",
149
+ compatibility_date: "2026-09-20",
150
+ compatibility_flags: ["nodejs_compat"],
151
+ vars: { TSS_PRERENDERING: "true" },
152
+ assets: { binding: "ASSETS", run_worker_first: true },
153
+ },
154
+ },
155
+ },
156
+ ```
157
+
158
+ Call the capture adapter before your normal Start handler, forwarding the same request options:
159
+
160
+ ```ts
161
+ import handler from "@tanstack/react-start/server-entry";
162
+ import { markdownRequest } from "pagegraph/tanstack-start/markdown";
163
+
164
+ export default {
165
+ async fetch(request: Request) {
166
+ return await markdownRequest(request) ?? handler.fetch(request);
167
+ },
168
+ };
169
+ ```
170
+
171
+ Only dev and the prerender Worker capture documents. `GET` and `HEAD` on
172
+ `/__pagegraph/markdown.json` capture all rendered pages; `/pricing.md` and
173
+ `/pricing.document.json` capture one page in dev. Ordinary requests return `null`. In production,
174
+ private capture paths return 404 and Markdown twins fall through to your static asset server.
175
+ `markdownPagePaths()` exposes the concrete rendered page paths.
176
+
177
+ After prerender, the plugin writes `dist/client/pricing.md` (`/` becomes `index.md`),
178
+ `.pagegraph/documents/pricing.json`, and `.pagegraph/heads.json`, and removes the emitted private
179
+ capture response. Stale valid documents at their derived paths are pruned; unrelated files are
180
+ preserved. Ignore the private generated documents and heads in Git. Claims replay is reserved for
181
+ a separate integration.
182
+
183
+ Serve graph-derived llms.txt without rendering pages:
184
+
185
+ ```ts
186
+ import { createFileRoute } from "@tanstack/react-router";
187
+ import { llmsTxt } from "pagegraph/tanstack-start/server";
188
+
189
+ export const Route = createFileRoute("/llms.txt")({
190
+ server: { handlers: { GET: llmsTxt({ sections: ["## Docs\n\nRead our documentation."] }) } },
191
+ });
192
+ ```
193
+
194
+ `llmsSection()` returns the generated section for custom composition. It includes rendered twins
195
+ and explicitly grouped pages, using graph titles and descriptions and `markdown.origin`.
196
+
29
197
  ## What runs where
30
198
 
31
- Every entry belongs to one place. Runtime entries are safe in a Worker, an SSR server, or a
32
- browser; build entries load your app through Vite, parse routes natively, or drive Node I/O.
199
+ Every entry belongs to one place. Runtime entries use portable graph and document code with
200
+ the framework peers listed below; the capture adapter runs on a Start server. Build entries
201
+ load your app through Vite, parse routes natively, or drive Node I/O.
33
202
 
34
203
  | Entry | Runs in | Exports | Peers |
35
204
  | ------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------- |
36
- | `pagegraph` | runtime (Worker, SSR, browser) | `buildSeoGraph`, `contentCollection`, `renderSitemap`, `renderRobots`, `pageHeads`, `graphToJson`/`graphFromJson`, checks, `inspectHtml` | — |
37
- | `pagegraph/react` | runtime | `createSeo` → `seo.head`, `seoHead`, `Breadcrumbs`, JSON-LD generators | `react`, `@tanstack/react-router` |
38
- | `pagegraph/tanstack-start/server` | runtime (Start server routes) | `robotsTxt`, `sitemapXml`, `seoGraph`, `seoSite` | the `pagegraph()` plugin |
39
- | `pagegraph/tanstack-start` | build (`vite.config.ts`, `seo.config.ts`) | `pagegraph()` Vite plugin, `tanstackStartGraph`, `evaluateAppGraph` | `vite`, `@tanstack/router-generator` |
205
+ | `pagegraph` | runtime (Worker, SSR, browser) | `buildSeoGraph`, `contentCollection`, `renderSitemap`, `renderRobots`, `pageHeads`, `graphToJson`/`graphFromJson`, checks, `inspectHtml`, document types, facts, markdown and locks | — |
206
+ | `pagegraph/react` | runtime | `createSeo` → `seo.head`, `seoHead`, `Breadcrumbs`, JSON-LD generators, authored page primitives | `react`, `@tanstack/react-router` |
207
+ | `pagegraph/tanstack-start/server` | runtime (Start server routes) | `robotsTxt`, `sitemapXml`, `llmsTxt`, `llmsSection`, `seoGraph`, `seoSite` | the `pagegraph()` plugin |
208
+ | `pagegraph/tanstack-start/markdown` | runtime (Start server) | `markdownRequest`, `markdownPagePaths` | `react`, `react-dom`, `@tanstack/react-start`, `@tanstack/react-router` |
209
+ | `pagegraph/tanstack-start/react` | runtime | capture-aware `Link` | `react`, `@tanstack/react-router` |
210
+ | `pagegraph/tanstack-start/prerender-worker` | non-deployed prerender Worker | compiled Start server | the `pagegraph()` plugin |
211
+ | `pagegraph/tanstack-start` | build (`vite.config.ts`, `pagegraph.config.ts`) | `pagegraph()` Vite plugin, `tanstackStartGraph`, `evaluateAppGraph` | `vite`, `@tanstack/router-generator`, `effect` |
40
212
  | `pagegraph/vite` | build (`vite.config.ts`) | `seoRouteConfig` coverage gate | `vite`, `@tanstack/router-generator` |
41
- | `pagegraph/config` | build (`seo.config.ts`, CLI) | `defineSeoConfig`, `viteGraphLoader`, `loadPageHeads` | `vite` |
213
+ | `pagegraph/config` | build (`pagegraph.config.ts`, CLI) | `defineSeoConfig`, `viteGraphLoader`, `loadPageHeads` | `vite` |
214
+ | `pagegraph/claims` | build / CLI (Node or Bun) | `claimsInput`, `claimsFamily`, document resolution, committed claims checks | `effect` |
42
215
  | `pagegraph/audit` | build / CLI (Node or Bun) | Audit services, scanner protocol, rules, and report schemas | `effect` |
216
+ | `pagegraph/oxlint` | build (oxlint) | `pagegraph` plugin: `no-bare-text`, `t-children` | — |
43
217
  | `pagegraph` bin | CLI (Bun) | CLI over the same graph | bundled |
44
218
 
45
219
  Build entries resolve to a stub under the `workerd`, `worker`, and `browser` export conditions.
@@ -48,21 +222,14 @@ instead of crashing on a native binding when a request first runs the code. The
48
222
  have **zero runtime dependencies** beyond their listed peers; a test pins each entry's import
49
223
  graph.
50
224
 
51
- The CLI **bundles PageGraph's Effect and TypeSafe provider runtime**, so it does not depend on the
52
- app's Effect version; it runs on [Bun](https://bun.sh) (`bunx pagegraph`). Agentic workflows load the
53
- OpenCode SDK only when invoked. The first workflow installs pinned OpenCode 2.0.22 into
54
- `~/.cache/pagegraph/opencode-2.0.22-2`, outside the app's dependency tree; subsequent workflows
55
- reuse the verified cache. This keeps consumer dependency overrides away from OpenCode's declared
56
- Effect and Drizzle versions. Executor plugins must use OpenCode's Effect version (`4.0.0-rc.112`).
57
- Bun and network access to npm are required for the first install. The pinned OpenCode client's
58
- HTTP error handling retains the method, path, status, and response body for diagnostics;
59
- undeclared error bodies are capped at 16 KiB.
60
- Discovery-only research gets one continuation to complete provider evidence. Every workflow failure
61
- after run creation writes `failure.json` in the run directory, with the stage, session ID when available,
62
- transcript, and cause chain. `vite` stays a peer — graph commands
63
- load your app through Vite at runtime — and `lighthouse` is only needed by `pagegraph audit`.
64
- Importing `pagegraph/audit` programmatically needs `effect@^4.0.0`. The Effect and TypeSafe
65
- peers remain optional for other library entry points and the bundled CLI.
225
+ The CLI **bundles PageGraph's Effect, TypeSafe provider runtime, and Pi agent runtime**, so it does
226
+ not depend on the app's Effect version; it runs on [Bun](https://bun.sh) (`bunx pagegraph`). Every
227
+ workflow runs in-process on Pi 1.0.0. Improve workflows use guarded repository edit tools;
228
+ research and dry-run actions expose only read tools. Every workflow failure after run creation
229
+ writes `failure.json` with its stage, agent messages, and cause chain. `vite` stays a peer — graph commands load your app through Vite at
230
+ runtime — and `lighthouse` is only needed by `pagegraph audit`. Importing `pagegraph/audit`
231
+ programmatically needs `effect@^4.0.0`. The Effect and TypeSafe peers remain optional for other
232
+ library entry points and the bundled CLI.
66
233
 
67
234
  ### Agent skill
68
235
 
@@ -195,15 +362,14 @@ import { robotsTxt } from "pagegraph/tanstack-start/server";
195
362
  export const Route = createFileRoute("/robots.txt")({ server: { handlers: { GET: robotsTxt } } });
196
363
  ```
197
364
 
198
- **5. Gate CI.** The CLI reads the same graph through `seo.config.ts`:
365
+ **5. Gate CI.** The CLI reads the same graph through `pagegraph.config.ts`:
199
366
 
200
367
  ```ts
201
- // seo.config.ts
368
+ // pagegraph.config.ts
202
369
  import { defineSeoConfig } from "pagegraph/config";
203
370
  import { tanstackStartGraph } from "pagegraph/tanstack-start";
204
371
 
205
372
  export default defineSeoConfig({
206
- origin: "https://example.com",
207
373
  loadGraph: tanstackStartGraph({ root: import.meta.dirname }),
208
374
  });
209
375
  ```
@@ -217,7 +383,7 @@ pagegraph check
217
383
  `buildSeoGraph({ routeTree, collections })` builds the same graph from any TanStack
218
384
  Router tree; render it with `renderSitemap(graph, { origin, indexable })` and
219
385
  `renderRobots(graph, { origin, indexable, disallow, contentSignal })`, and point
220
- `seo.config.ts` at it with `viteGraphLoader` (see [CLI](#cli)).
386
+ `pagegraph.config.ts` at it with `viteGraphLoader` (see [CLI](#cli)).
221
387
 
222
388
  `renderRobots` does not invent a Content-Signal. Pass `contentSignal` for
223
389
  the value (the plugin prefixes `Content-Signal: `), `directives` for extra
@@ -227,28 +393,37 @@ Preview hosts (`indexable: false`) drop `contentSignal` and `directives`;
227
393
 
228
394
  ## Agentic workflows
229
395
 
230
- PageGraph can combine the deterministic route graph with a project-owned embedded OpenCode agent,
231
- live tools exposed by an Executor plugin, and Jev decisions. Scaffold the OpenCode preset once:
396
+ PageGraph combines the deterministic route graph with project-owned agent instructions, live
397
+ Executor tools, and Jev decisions. Every workflow uses Pi in-process. Scaffold the shared preset once:
232
398
 
233
399
  ```bash
234
400
  pagegraph init
235
401
  ```
236
402
 
237
- The generated `.pagegraph/opencode` directory contains one `seo` agent and customizable `content`,
238
- `technical`, and `authority` skills. Add your Executor plugin to its `opencode.jsonc`; OpenCode and
239
- the plugin own model and Executor authentication. PageGraph does not copy credentials or assume
240
- fixed provider-tool addresses.
403
+ The generated `.pagegraph/agent` directory contains the `seo` agent instructions, `AGENTS.md`,
404
+ and customizable workflow skills with Executor recipes. Pi composes these files into its system
405
+ prompt and fails if a required file is missing. Pi uses its provider-specific environment variables
406
+ for model authentication, such as `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`.
241
407
 
242
- Enable workflows in `seo.config.ts`:
408
+ Research connects to [Executor](https://github.com/RhysSullivan/executor) through
409
+ `@pi-ext/executor`. Set `EXECUTOR_BASE_URL` to the server, plus `EXECUTOR_CLIENT_ID[_FILE]` and
410
+ `EXECUTOR_CLIENT_SECRET[_FILE]` when Cloudflare Access fronts it (both or neither). Every workflow
411
+ declines Executor approval requests, and only provider calls that succeeded count as evidence.
412
+ Use `executor_search` to discover tools and their TypeScript shapes, then `executor_execute` with
413
+ a TypeScript snippet calling `tools.<namespace>.<tool>(input)` and a top-level `return`.
414
+ Repository tools are `read_file`, `list_files`, and `search_text`; structured results go through
415
+ `submit_result`. Improve action turns expose `edit_file` and `write_file` in write mode.
416
+
417
+ Enable workflows in `pagegraph.config.ts`:
243
418
 
244
419
  ```ts
245
420
  export default defineSeoConfig({
246
421
  // origin, disallow, and loadGraph as above
247
422
  workflows: {
248
- opencode: {
249
- configDirectory: ".pagegraph/opencode",
423
+ agent: {
424
+ presetDirectory: ".pagegraph/agent",
250
425
  defaultModel: "openrouter/example/model",
251
- // Optional per-completion budget in milliseconds; default 180000.
426
+ // Optional per-turn budget in milliseconds; default 180000.
252
427
  timeoutMs: 360_000,
253
428
  models: {
254
429
  "research.keywords": "openrouter/example/research-model",
@@ -279,9 +454,9 @@ Executor's live catalog and inspect the discovered tools' argument schemas, then
279
454
  structured observations with Jev. Tool paths are discovered at runtime; the generated skills contain
280
455
  editable starter recipes, not a fixed integration list.
281
456
 
282
- `workflows.opencode.timeoutMs` sets the positive integer deadline in milliseconds for each OpenCode
283
- completion (maximum `2147483647`). It covers prompt admission, model execution, and any workflow JSON
284
- repair prompt. A later action or continuation turn gets a separate budget. The default is `180000`.
457
+ `workflows.agent.timeoutMs` sets the positive integer deadline in milliseconds for each Pi research or action turn
458
+ (maximum `2147483647`, default `180000`). Research and action share the same agent conversation,
459
+ so action turns retain the research context.
285
460
 
286
461
  Selecting pages preserves their incoming and outgoing relationships to non-selected pages in
287
462
  `evidence.neighborhood`. Those neighbors supply architecture context without becoming workflow
@@ -289,12 +464,13 @@ targets or consuming `--limit`. The agent receives the limit before research, an
289
464
  the decoded result before decisions.
290
465
 
291
466
  After research is validated, the run directory contains `research.json`: an incomplete checkpoint
292
- with the collected evidence, decision inputs, and OpenCode transcript. A downstream failure reports
293
- its path so the evidence remains available for diagnosis. Only a completed workflow writes
467
+ with the collected evidence, decision inputs, and agent messages. A downstream failure
468
+ reports its path so the evidence remains available for diagnosis. Only a completed workflow writes
294
469
  `run.json` and `summary.md`; a research checkpoint is not a final recommendation.
295
- Model JSON drops null properties and array elements before validation. Schema validation collects all
296
- issues for one read-only repair turn. Every error after run creation writes `failure.json` in that
297
- run directory with the workflow, stage, available OpenCode session ID, transcript, and full cause
470
+ Model results drop null properties and array elements before validation. Pi receives decode issues
471
+ and missing Executor evidence through `submit_result`, so it can correct the result in the same
472
+ agent loop; repeated rejected submissions fail the run. Every error after run creation writes
473
+ `failure.json` with the workflow, stage, agent messages, and full cause
298
474
  chain. The error reports the artifact path and a next step; a failed artifact write preserves the
299
475
  original error with a note.
300
476
 
@@ -313,20 +489,24 @@ original error with a note.
313
489
  | `improve links` | Contextual internal-link edits from graph and page evidence |
314
490
 
315
491
  The common selectors are repeatable `--page`, `--query`, and `--kind`, plus `--limit`, `--market`,
316
- `--language`, `--refresh`, `--model`, `--opencode-config`, and `--out`. Relevant workflows also
317
- accept `--competitor`, `--domain`, or `--device desktop|mobile`. Workflows are noninteractive and
318
- fail immediately if the agent asks a question or requests permission.
492
+ `--language`, `--refresh`, `--model`, `--preset-directory`, and `--out`. Relevant workflows also
493
+ accept `--competitor`, `--domain`, or `--device desktop|mobile`. Workflow prompts direct the agent
494
+ to proceed without questions or forms and fail clearly when required input is missing.
319
495
 
320
496
  The four `improve` workflows write source files by default and require a clean Git tree. Use
321
- `--dry-run` to keep repository edit and shell tools disabled, or `--allow-dirty` when you explicitly
497
+ `--dry-run` to expose only read tools, or `--allow-dirty` when you explicitly
322
498
  want the agent to edit alongside existing changes. PageGraph leaves every diff uncommitted and
323
- records modified, added, and deleted files after the workflow finishes.
499
+ records modified, added, and deleted files after the workflow finishes. Write tools reject paths
500
+ outside the repository, including symlink escapes, and paths under `.git`, `node_modules`,
501
+ the runs directory, and the preset directory. There is no shell tool. For `improve links`,
502
+ `fetch_page` reads extracted sentences from served pages on suggestion-report origins, respecting
503
+ robots rules and the response size limit.
324
504
 
325
505
  Each run writes `.pagegraph/runs/<run-id>/run.json` and `summary.md`. The JSON retains the
326
- deterministic graph, context, complete OpenCode session export, Executor tool evidence, Jev question
327
- definitions and answers, Git provenance, changed files, and model provenance. Keep this directory
328
- ignored: provider output can be large or account-specific. Workflows fail when the configured model,
329
- active Executor plugin, catalog search plus discovered tool call, or `TYPESAFE_API_KEY` for Jev is
506
+ deterministic graph, context, Executor tool evidence, Jev question definitions and answers, Git
507
+ provenance, changed files, and model provenance. Schema-version-2 artifacts record `agent.runtime`
508
+ as the literal `pi`, with messages and usage. Keep this directory ignored: provider output can be large or account-specific. Workflows fail when the configured model,
509
+ Executor toolset, catalog search plus completed tool call, or `TYPESAFE_API_KEY` for Jev is
330
510
  unavailable.
331
511
 
332
512
  ## Typed paths
@@ -406,27 +586,106 @@ It also derives a small config module from the route tree: `robotsExclusions`
406
586
  (the plugin adds them to robots.txt; a Router-only app feeds them to `renderRobots`) and
407
587
  `reservedSegments` (top-level segments an app must not hand out as tenant/org slugs).
408
588
 
589
+ ## Claims gate
590
+
591
+ Declare probability rules against `claimsInput` from the build-only `pagegraph/claims` entry.
592
+ Each rule asks whether the section violates your policy:
593
+
594
+ ```ts
595
+ import { Decision } from "effect/ai";
596
+ import { claimsInput } from "pagegraph/claims";
597
+ import { pagegraph } from "pagegraph/tanstack-start";
598
+
599
+ const rules = Decision.make({
600
+ input: claimsInput,
601
+ decisions: {
602
+ unsupportedPromise: Decision.probability({
603
+ instructions: "Does this section promise a capability unsupported by its facts or evidence?",
604
+ }),
605
+ },
606
+ });
607
+
608
+ const graph = pagegraph({
609
+ origin: "https://example.com",
610
+ markdown: { origin: "https://example.com" },
611
+ facts: "src/facts.ts",
612
+ claims: { rules, model: "typesafe/jev", cutoff: 0.8 },
613
+ });
614
+ ```
615
+
616
+ Pass `graph.prerenderPages` to Start as shown in the rendered Markdown setup. The build persists
617
+ captured documents and graph heads, then replays committed answers without a model or credentials.
618
+ Missing answers and violations fail the build and name the page, section, and rule.
619
+
620
+ Run `pagegraph claims check` after changing prose, facts, context, or rules, then review and commit
621
+ `.pagegraph/decisions/claims/`. The cache key includes the rule definition fingerprint, model, and
622
+ input hash. Cached answers are reused; `--refresh` asks again and replaces them. The command reports
623
+ cached and asked counts and exits nonzero on violations. Set `TYPESAFE_API_KEY` when answers need
624
+ asking. `pagegraph check` also replays configured claims without model calls.
625
+
626
+ ```sh
627
+ pagegraph claims check
628
+ pagegraph claims check --refresh
629
+ pagegraph claims check --dev http://localhost:3000
630
+ ```
631
+
632
+ `claims.context` supplies policy text to every input. Inputs include the section's authored
633
+ Markdown, messages, all code-owned facts, and other sections marked as evidence. Page metadata is
634
+ judged as a `head` section, including graph pages outside rendered capture. `excludeHeads` accepts
635
+ path globs to omit metadata while keeping captured sections in the gate. The default cutoff is
636
+ `0.8` only when omitted; any probability at or above the configured cutoff is a violation, and
637
+ values below it pass. Changing the cutoff reuses answers and recalculates verdicts.
638
+
639
+ ## Markdown CLI
640
+
641
+ Inspect captured documents, search page metadata and sections, and maintain a reviewable lock:
642
+
643
+ ```sh
644
+ pagegraph markdown show /pricing
645
+ pagegraph markdown find email
646
+ pagegraph markdown lock
647
+ pagegraph markdown lock --check
648
+ ```
649
+
650
+ `show` prints the page's Markdown twin. `find` reports matching pages and sections.
651
+ `lock` writes `.pagegraph/markdown.lock.json` from document and message hashes, including
652
+ head-only pages; `--check` exits nonzero if the lock is missing or differs. These commands use
653
+ `.pagegraph/documents/` and `.pagegraph/heads.json` from the build. Use `--dev <origin>` to inspect
654
+ the development bundle instead. `--json` emits structured output using the CLI's usual conventions.
655
+ Markdown and claims settings come from `pagegraph()` in Vite, including the canonical Markdown
656
+ origin and facts module. Built-mode commands read Vite with `command: "build"` and
657
+ `mode: "production"`; `--dev` reads `command: "serve"` and `mode: "development"`.
658
+ Put CLI policies and the graph loader in `pagegraph.config.ts`.
659
+
409
660
  ## CLI
410
661
 
411
- The graph commands acquire your graph through `seo.config.ts` at the app root. A
662
+ The graph commands acquire your graph through `pagegraph.config.ts` at the app root.
663
+ Rename an existing `seo.config.ts`, `.js`, or `.mjs` to its `pagegraph.config.*` equivalent.
664
+ The file holds CLI policies and `loadGraph`; site identity and robots policy come from the
665
+ loader result, and Markdown and claims settings come from the Vite plugin. A
412
666
  TanStack Start app uses `tanstackStartGraph` (above), which also supplies the robots
413
- policy. Otherwise `viteGraphLoader` evaluates your graph module inside a headless Vite
414
- server, so path aliases, content plugins, and virtual modules all resolve:
667
+ policy and site identity from `pagegraph()`, using build settings in production mode by default.
668
+ Pass `command: "serve", mode: "development"` to inspect a development configuration instead.
669
+ Otherwise `viteGraphLoader` evaluates your graph
670
+ module inside a headless Vite server, so path aliases, content plugins, and virtual modules all resolve:
415
671
 
416
672
  ```ts
417
- // seo.config.ts
673
+ // pagegraph.config.ts
418
674
  import { defineSeoConfig, viteGraphLoader } from "pagegraph/config";
675
+ import { routeConfig } from "./src/lib/route-config";
419
676
 
420
677
  export default defineSeoConfig({
421
- origin: "https://example.com",
422
- disallow: routeConfig.robotsExclusions,
423
- contentSignal: "search=yes, ai-input=yes, ai-train=yes",
424
678
  // Fail `check` unless each named money page has enough contextual links.
425
679
  coverage: [{ path: "/pricing", minInbound: 2 }, { path: "/features/*", minInbound: 1 }],
426
680
  loadGraph: viteGraphLoader({
427
681
  root: import.meta.dirname,
428
682
  entry: "/lib/seo/graph.ts",
429
683
  exportName: "loadSeoGraph",
684
+ site: {
685
+ origin: "https://example.com",
686
+ indexable: true,
687
+ robots: { disallow: routeConfig.robotsExclusions, contentSignal: "search=yes, ai-input=yes, ai-train=yes" },
688
+ },
430
689
  }),
431
690
  });
432
691
  ```
@@ -461,7 +720,7 @@ pagegraph graph # the graph as a tree · --format mermaid | json
461
720
  pagegraph inspect /pricing # one node: policy, sitemap status, in/out edges
462
721
  pagegraph inspect <url> --live # fetch a deployed page, validate its rendered <head>
463
722
  pagegraph links verify <url> # crawl served HTML: depth, orphans, declared-vs-rendered
464
- pagegraph links verify <url> --assert-coverage # also assert seo.config.ts coverage on served anchors
723
+ pagegraph links verify <url> --assert-coverage # also assert pagegraph.config.ts coverage on served anchors
465
724
  pagegraph links verify <url> --emit-rendered <file> # save the rendered edge set
466
725
  pagegraph links candidates # propose contextual links from the declared graph
467
726
  pagegraph research keywords --query "email api" # combine graph, Executor, and Jev evidence
@@ -477,7 +736,7 @@ Stdout is data, stderr is status — `pagegraph check --json | jq` just works.
477
736
  `pagegraph links verify` crawls a site's served HTML — through the same DNS-pinned,
478
737
  private-IP-blocked HTTP path as `pagegraph audit` — and reports what a crawler
479
738
  actually receives: the real homepage depth, the pages with no incoming internal
480
- edge (rendered orphans), and, when the app has a `seo.config.ts`, the
739
+ edge (rendered orphans), and, when the app has a `pagegraph.config.ts`, the
481
740
  declared-vs-rendered link diff. It is bounded with `--limit` and needs no
482
741
  framework or config. Discovery reads same-origin `robots.txt` and bounded
483
742
  sitemaps before following page anchors, so a sitemap-only page can appear as an
@@ -532,7 +791,7 @@ It is also a drop-in input for `pagegraph links candidates --rendered`.
532
791
 
533
792
  ### Assert coverage on the rendered graph
534
793
 
535
- `pagegraph check` evaluates the `seo.config.ts` `coverage` rules against the
794
+ `pagegraph check` evaluates the `pagegraph.config.ts` `coverage` rules against the
536
795
  **declared** graph; `pagegraph links verify --assert-coverage` evaluates the same
537
796
  rules against the anchors a crawler **actually receives**. Only same-origin
538
797
  body-region anchors count — nav, header, and footer links are chrome — which is
@@ -549,7 +808,7 @@ non-zero) rather than reporting a false pass when the crawl outran `--limit`, a
549
808
  page failed to fetch (its anchors are missing), a body hit `--max-body-bytes`, the
550
809
  robots or sitemap discovery is incomplete, the
551
810
  config declares no `coverage`, or the artifact's origin differs from
552
- `seo.config.ts`. Under `--assert-coverage` the report gains a `coverage` block
811
+ `pagegraph.config.ts`. Under `--assert-coverage` the report gains a `coverage` block
553
812
  (`{ rules, ok, violations }`); the default `--json` summary is unchanged.
554
813
 
555
814
  ### Propose contextual links
@@ -615,7 +874,7 @@ both `links candidates --site` and `improve links --suggestions`.
615
874
 
616
875
  ### Contextual-link coverage
617
876
 
618
- Declare a contextual-link coverage policy in `seo.config.ts` (a `coverage` array
877
+ Declare a contextual-link coverage policy in `pagegraph.config.ts` (a `coverage` array
619
878
  of `{ path, minInbound }`), or pass repeatable `--require-inbound "<path-glob>=<n>"`
620
879
  flags to override it for one run. `pagegraph check` then fails (exit 1) unless
621
880
  every sitemap-eligible page matching the glob has at least `minInbound` incoming
@@ -709,7 +968,7 @@ undated rather than stale; give content its `modifiedAt` (or a route its
709
968
 
710
969
  ### Audit any website
711
970
 
712
- `pagegraph audit` is framework-independent and does not need `seo.config.ts`. It
971
+ `pagegraph audit` is framework-independent and does not need `pagegraph.config.ts`. It
713
972
  validates target URLs before making requests, follows redirects through the same
714
973
  validation boundary, inspects the rendered document and discovery files, and
715
974
  can collect Lighthouse evidence through a validating proxy.