pagegraph 0.7.0 → 0.8.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.
package/README.md CHANGED
@@ -34,13 +34,14 @@ bun add -D lighthouse # only for `pagegraph audit` performance evidence
34
34
  | `pagegraph/audit` | Audit services, scanner protocol, rules, and report schemas | `effect` |
35
35
  | `pagegraph` bin | CLI over the same graph | bundled — runs on Bun |
36
36
 
37
- The core and React entries have **zero runtime dependencies** — everything above is a
38
- peer, and only the entries you import need theirs installed.
37
+ The core and React import graphs have **zero runtime dependencies** — everything they use is a
38
+ peer, and only the entries you import need those peers loaded.
39
39
 
40
- The CLI **bundles Effect and the TypeSafe provider**, so it needs no Effect peers and does not
41
- depend on the app's Effect RC; it runs on [Bun](https://bun.sh) (`bunx pagegraph`). `vite` stays a
42
- peer — the graph commands load your app through Vite at runtime — and `lighthouse` is only needed by
43
- `pagegraph audit`. Importing `pagegraph/audit` programmatically still needs `effect`.
40
+ The CLI **bundles PageGraph's Effect and TypeSafe provider runtime**, so it does not depend on the
41
+ app's Effect RC; it runs on [Bun](https://bun.sh) (`bunx pagegraph`). Agentic workflows load the
42
+ OpenCode SDK and its private Effect runtime only when invoked. `vite` stays a peer — graph commands
43
+ load your app through Vite at runtime — and `lighthouse` is only needed by `pagegraph audit`.
44
+ Importing `pagegraph/audit` programmatically still needs `effect`.
44
45
 
45
46
  ## Quick start
46
47
 
@@ -205,6 +206,89 @@ Preview hosts (`indexable: false`) drop `contentSignal` and `directives`;
205
206
  pagegraph check
206
207
  ```
207
208
 
209
+ ## Agentic workflows
210
+
211
+ PageGraph can combine the deterministic route graph with a project-owned embedded OpenCode agent,
212
+ live tools exposed by an Executor plugin, and Jev decisions. Scaffold the OpenCode preset once:
213
+
214
+ ```bash
215
+ pagegraph init
216
+ ```
217
+
218
+ The generated `.pagegraph/opencode` directory contains one `seo` agent and customizable `content`,
219
+ `technical`, and `authority` skills. Add your Executor plugin to its `opencode.jsonc`; OpenCode and
220
+ the plugin own model and Executor authentication. PageGraph does not copy credentials or assume
221
+ fixed provider-tool addresses.
222
+
223
+ Enable workflows in `seo.config.ts`:
224
+
225
+ ```ts
226
+ export default defineSeoConfig({
227
+ // origin, disallow, and loadGraph as above
228
+ workflows: {
229
+ opencode: {
230
+ configDirectory: ".pagegraph/opencode",
231
+ defaultModel: "openrouter/example/model",
232
+ models: {
233
+ "research.keywords": "openrouter/example/research-model",
234
+ },
235
+ },
236
+ context: {
237
+ files: ["AGENTS.md"],
238
+ byWorkflow: {
239
+ "research.keywords": ["docs/seo/measurement.md"],
240
+ },
241
+ },
242
+ runsDirectory: ".pagegraph/runs",
243
+ },
244
+ });
245
+ ```
246
+
247
+ Run a page-backed workflow:
248
+
249
+ ```bash
250
+ pagegraph research keywords \
251
+ --query "transactional email api" \
252
+ --market us \
253
+ --language en
254
+ ```
255
+
256
+ Every workflow starts from the selected graph and context files, asks the SEO agent to search
257
+ Executor's live catalog and compose suitable tools, then evaluates its structured observations with
258
+ Jev. Tool paths are discovered at runtime; the generated skills contain editable starter recipes,
259
+ not a fixed integration list.
260
+
261
+ | Command | Result |
262
+ | --- | --- |
263
+ | `research keywords` | Query demand, intent, and page ownership opportunities |
264
+ | `research competitors` | Competitor topics, pages, and positioning evidence |
265
+ | `research authority` | Qualified editorial, directory, partner, and community opportunities |
266
+ | `analyze serp` | Search-result intent and page-shape fit |
267
+ | `analyze content` | Substance, answer quality, and intent overlap |
268
+ | `analyze ai-search` | Citation readiness and answer-engine coverage |
269
+ | `plan architecture` | A page-backed information-architecture plan |
270
+ | `improve content` | Evidence-backed source copy edits |
271
+ | `improve metadata` | Title and description edits at the owning source |
272
+ | `improve schema` | Structured-data edits justified by visible content |
273
+ | `improve links` | Contextual internal-link edits from graph and page evidence |
274
+
275
+ The common selectors are repeatable `--page`, `--query`, and `--kind`, plus `--limit`, `--market`,
276
+ `--language`, `--refresh`, `--model`, `--opencode-config`, and `--out`. Relevant workflows also
277
+ accept `--competitor`, `--domain`, or `--device desktop|mobile`. Workflows are noninteractive and
278
+ fail immediately if the agent asks a question or requests permission.
279
+
280
+ The four `improve` workflows write source files by default and require a clean Git tree. Use
281
+ `--dry-run` to keep repository edit and shell tools disabled, or `--allow-dirty` when you explicitly
282
+ want the agent to edit alongside existing changes. PageGraph leaves every diff uncommitted and
283
+ records modified, added, and deleted files after the workflow finishes.
284
+
285
+ Each run writes `.pagegraph/runs/<run-id>/run.json` and `summary.md`. The JSON retains the
286
+ deterministic graph, context, complete OpenCode session export, Executor tool evidence, Jev question
287
+ definitions and answers, Git provenance, changed files, and model provenance. Keep this directory
288
+ ignored: provider output can be large or account-specific. Workflows fail when the configured model,
289
+ active Executor plugin, catalog search plus discovered tool call, or `TYPESAFE_API_KEY` for Jev is
290
+ unavailable.
291
+
208
292
  ## Typed paths
209
293
 
210
294
  Augment `Register` (the same pattern TanStack Router uses) and `related` /
@@ -308,8 +392,8 @@ pagegraph links verify <url> # crawl served HTML: depth, orphans, declared-vs
308
392
  pagegraph links verify <url> --assert-coverage # also assert seo.config.ts coverage on served anchors
309
393
  pagegraph links verify <url> --emit-rendered <file> # save the rendered edge set
310
394
  pagegraph links candidates # propose contextual links from the declared graph
311
- pagegraph links decide <file> # answer typed link questions with Jev (TYPESAFE_API_KEY)
312
- pagegraph decide <family> [<file>] # answer a decision batch with Jev (TYPESAFE_API_KEY)
395
+ pagegraph research keywords --query "email api" # combine graph, Executor, and Jev evidence
396
+ pagegraph improve metadata --page /pricing # apply a clean-tree source improvement
313
397
  pagegraph sitemap # print sitemap.xml
314
398
  pagegraph robots # print robots.txt
315
399
  ```
@@ -395,195 +479,18 @@ Pairs already declared as a `related` edge are excluded, and `--rendered` accept
395
479
  a JSON dump of already-served anchors (`[{ from, to }]` or `{ edges: [...] }`) to
396
480
  exclude those too.
397
481
 
398
- The output is a reviewable plan: human text by default, versioned JSON with
399
- `--json`. Nothing is applied. `--limit` and `--cluster` bound the plan, and
400
- `--decide` optionally hands the candidates to Jev for a recommendation and
401
- confidence per pair (requires `TYPESAFE_API_KEY`):
482
+ The output is a deterministic, reviewable plan: human text by default and
483
+ versioned JSON with `--json`. Nothing is applied; `--limit` and `--cluster`
484
+ bound the plan. Use `pagegraph improve links` when you want Executor evidence,
485
+ Jev evaluation, and source edits:
402
486
 
403
487
  ```bash
404
488
  pagegraph links candidates
405
489
  pagegraph links candidates --cluster blog --limit 20
406
490
  pagegraph links candidates --rendered rendered.json --json | jq
407
- pagegraph links candidates --decide
408
- ```
409
-
410
- ### Decide with Jev
411
-
412
- `pagegraph links decide` answers four decisions per candidate — *does the source have a genuine
413
- reason to link to the destination?*, *is descriptive anchor text already in the copy?*, *which
414
- direction deserves the link?*, and *how relevant is it?* — and routes anything inside the confidence
415
- band `(1−t, t)` to a `review` bucket instead of auto-applying. `--budget` (default 4) keeps only the
416
- top-K candidates per source by relevance.
417
-
418
- ```bash
419
- pagegraph links decide candidates.json --threshold 0.9
420
- pagegraph links decide candidates.json --budget 2 --json | jq
421
- cat candidates.json | pagegraph links decide
491
+ pagegraph improve links --page "/docs/**"
422
492
  ```
423
493
 
424
- Decisions run through [Jev](https://typesafe.ai) via `@effect/ai-typesafe` (model `jev-latest`),
425
- bundled into the CLI. The API key is read from the **`TYPESAFE_API_KEY` environment variable** — it
426
- is deliberately not a `seo.config.ts` field, so keys never live in the repo; `seo.config.ts` carries
427
- non-secret decision settings only. Without a key the command exits 1 with a clear message and an
428
- empty stdout.
429
-
430
- ### The decision runner
431
-
432
- `pagegraph decide <family>` generalizes Jev decisions over a batch. Each input answers every decision
433
- for that input in one provider call; confident answers resolve and anything inside the confidence
434
- band goes to a `review` bucket. Nothing is applied automatically — the report is the end product.
435
-
436
- ```bash
437
- pagegraph decide <family> inputs.json --json | jq
438
- cat inputs.json | pagegraph decide <family> --threshold 0.9
439
- ```
440
-
441
- A batch is a JSON array, a `{ "inputs": [...] }` envelope, or JSONL (one input per line), from a file
442
- argument or stdin. Flags are shared across families:
443
-
444
- | Flag | Default | Meaning |
445
- | ----------------- | ----------- | ------------------------------------------------------------- |
446
- | `--json` | false | Emit the versioned report as JSON on stdout |
447
- | `--model <id>` | `jev-latest` | TypeSafe System One model (`jev-latest`, `jev-preview`, …) |
448
- | `--threshold <t>` | `0.7` | Confidence boundary; `(1−t, t)` is the review band |
449
- | `--concurrency <n>` | `4` | In-flight decision calls |
450
- | `--cache <dir>` | — | Reuse model answers by family + model + input hash |
451
- | `--review-out <file>` | — | Write only the below-threshold records to JSON |
452
-
453
- `pagegraph decide links` is the links family alias of `pagegraph links decide` (see
454
- [Decide with Jev](#decide-with-jev)); it accepts the same candidate array.
455
-
456
- #### `decide serp` — is our page the shape this SERP rewards?
457
-
458
- Input: one saved results-page snapshot per input.
459
-
460
- ```json
461
- {
462
- "query": "best email api",
463
- "ourPage": {
464
- "url": "https://example.com/email-api",
465
- "title": "Email API for developers",
466
- "kind": "page",
467
- "firstWords": "Send transactional email from your product."
468
- },
469
- "serpItems": [
470
- { "type": "organic", "rank": 1, "domain": "a.example", "title": "Best email APIs", "snippet": "Compare" }
471
- ]
472
- }
473
- ```
474
-
475
- | Verdict | Meaning |
476
- | ---------- | ------------------------------------------------------------------ |
477
- | `aligned` | Intent and format are both confidently positive. |
478
- | `mismatch` | At least one of intent/format is confidently wrong. |
479
- | `review` | Any answer (`ourFormatFit`, `intentMatch`, `titlePatternMatch`) is inside the band. |
480
-
481
- #### `decide content` — does this page carry enough substance?
482
-
483
- Input: one page per input, with the evidence the rubric reads.
484
-
485
- ```json
486
- {
487
- "url": "https://example.com/email-api",
488
- "query": "best email api",
489
- "title": "Transactional email API for product teams",
490
- "h1": "Send transactional email",
491
- "first150Words": "The API sends one message per call and reports delivery events.",
492
- "headings": ["Quickstart", "Events", "Pricing"],
493
- "wordCount": 1200,
494
- "structuredData": ["Article", "FAQPage"],
495
- "categoryLock": "email api",
496
- "siblingIntents": ["email deliverability", "sms api"],
497
- "competitorExcerpts": ["A competitor's overview."]
498
- }
499
- ```
500
-
501
- | Verdict | Meaning |
502
- | -------- | --------------------------------------------------------------------------- |
503
- | `pass` | The value rating is not `thin` and no rubric probability is confidently false. |
504
- | `flag` | Value is `thin`, or a rubric probability (`originalValue`, `answersFirst`, …) is confidently false. |
505
- | `review` | Any rubric or value probability is inside the band, or an evidence-dependent rubric (`distinctIntent`, `competitiveSubstance`) is confidently positive without `siblingIntents`/`competitorExcerpts`. |
506
-
507
- `wordCount` must be a non-negative integer.
508
-
509
- `value` rates `thin | adequate | strong`.
510
-
511
- #### `decide fit` — which existing page should target this query?
512
-
513
- Input: a query plus the site's candidate pages. Candidate ids become the provider labels, so the
514
- family needs at least two candidates with unique ids of at most 255 characters.
515
-
516
- ```json
517
- {
518
- "query": "best email api",
519
- "candidates": [
520
- { "id": "/email-api", "title": "Email API", "excerpt": "Send transactional email." },
521
- { "id": "/pricing", "title": "Pricing", "excerpt": "Volume pricing." }
522
- ]
523
- }
524
- ```
525
-
526
- | Verdict | Meaning |
527
- | -------------- | -------------------------------------------------------- |
528
- | `map` | A page is confidently best; update it. |
529
- | `cannibalized` | Multiple pages already target the query. |
530
- | `gap` | No page can serve the query; a new page is needed. |
531
- | `review` | The conflict or the best page is inside the confidence band. |
532
-
533
- #### `decide meta` — rank supplied title/description candidates
534
-
535
- Input: a page plus the candidates to rank. Drafting is out of scope — only the provided candidates
536
- are judged. Needs at least two candidates with unique ids of at most 255 characters.
537
-
538
- ```json
539
- {
540
- "url": "https://example.com/email-api",
541
- "intent": "choose an email api",
542
- "categoryLock": "email api",
543
- "candidates": [
544
- { "id": "a", "title": "Email API", "description": "Send transactional email." },
545
- { "id": "b", "title": "Email pricing", "description": "Volume pricing." }
546
- ]
547
- }
548
- ```
549
-
550
- | Verdict | Meaning |
551
- | ------------- | ------------------------------------------------------------------------- |
552
- | `choose:<id>` | A candidate is confidently best with clear intent, length, category, and clickbait checks. |
553
- | `review` | A concern or an uncertain pick; a human decides. |
554
-
555
- #### `decide authority` — is this a real link opportunity?
556
-
557
- Input: one link target per input.
558
-
559
- ```json
560
- {
561
- "domain": "blog.example",
562
- "url": "https://blog.example/email-guide",
563
- "anchor": "transactional email API",
564
- "context": "We cover how a transactional email API works end to end.",
565
- "targetPath": "/email-api",
566
- "rd": 120,
567
- "signals": ["editorial", "no-outbound-links"]
568
- }
569
- ```
570
-
571
- | Verdict | Meaning |
572
- | -------- | ------------------------------------------------------------------------ |
573
- | `accept` | Legitimate, outreach-worthy, and a confident non-`none` fit. |
574
- | `spam` | Spam is confidently high. |
575
- | `review` | Any probability is inside the band, or the fit is `none`/uncertain. |
576
-
577
- `fit` classifies `directory | partner | editorial | community | none`.
578
-
579
- #### `decide links` — direction, relevance, and budget
580
-
581
- `pagegraph decide links` shares the links decisions (see [Decide with Jev](#decide-with-jev)) and
582
- adds `--budget <n>` (default 4): keep the top-K candidates per outbound page by `relevance`
583
- (`irrelevant | useful | essential`). `direction` classifies `a_to_b | b_to_a | both`; a record also
584
- carries the resulting `from`/`to` endpoints, so a `b_to_a` recommendation is reported — and
585
- budgeted — against the page the model actually chose to link *from*.
586
-
587
494
  ### Contextual-link coverage
588
495
 
589
496
  Declare a contextual-link coverage policy in `seo.config.ts` (a `coverage` array