seo 0.0.15 → 0.2.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 (93) hide show
  1. package/LICENSE +201 -0
  2. package/PRIVACY.md +95 -0
  3. package/README.md +431 -35
  4. package/SECURITY.md +55 -0
  5. package/TERMS.md +94 -0
  6. package/TRADEMARKS.md +31 -0
  7. package/dist/cli.js +141 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/dist-6bK_8r5M.js +713 -0
  10. package/dist/dist-6bK_8r5M.js.map +1 -0
  11. package/dist/dist-vRmqfhR4.js +3 -0
  12. package/dist/index.d.ts +18013 -0
  13. package/dist/index.js +27197 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/mcp.d.ts +74 -0
  16. package/dist/mcp.js +28611 -0
  17. package/dist/mcp.js.map +1 -0
  18. package/dist/package-7aT8_Xp0.js +23818 -0
  19. package/dist/package-7aT8_Xp0.js.map +1 -0
  20. package/evals/README.md +117 -0
  21. package/evals/affected-urls.json +57 -0
  22. package/evals/ai-readiness.json +84 -0
  23. package/evals/ai-referrals.json +71 -0
  24. package/evals/ai-search-scorecard.json +70 -0
  25. package/evals/audit-page.json +57 -0
  26. package/evals/audit-urls.json +58 -0
  27. package/evals/cannibalisation.json +57 -0
  28. package/evals/community-intent.json +57 -0
  29. package/evals/compare-crawls.json +56 -0
  30. package/evals/content-optimization.json +57 -0
  31. package/evals/crawl-diff.json +58 -0
  32. package/evals/crawl-history.json +44 -0
  33. package/evals/crawl-report.json +44 -0
  34. package/evals/crawler-rules.json +44 -0
  35. package/evals/ctr-underperformers.json +57 -0
  36. package/evals/decaying-pages.json +57 -0
  37. package/evals/entity-readiness.json +57 -0
  38. package/evals/explain-crawl-issue.json +44 -0
  39. package/evals/generate-llms-txt.json +44 -0
  40. package/evals/geo-gaps.json +57 -0
  41. package/evals/index-coverage-plan.json +58 -0
  42. package/evals/index-coverage.json +70 -0
  43. package/evals/index-monitor.json +58 -0
  44. package/evals/index-watch.json +68 -0
  45. package/evals/internal-links.json +57 -0
  46. package/evals/link-recovery.json +58 -0
  47. package/evals/llms-txt-audit.json +44 -0
  48. package/evals/measure-change.json +58 -0
  49. package/evals/monthly-action-plan.json +56 -0
  50. package/evals/monthly-report.json +58 -0
  51. package/evals/narrative-report.json +58 -0
  52. package/evals/okf-build.json +44 -0
  53. package/evals/okf-validate.json +44 -0
  54. package/evals/page-opportunities.json +57 -0
  55. package/evals/performance-audit.json +57 -0
  56. package/evals/pseo-audit.json +84 -0
  57. package/evals/query-clusters.json +57 -0
  58. package/evals/quick-wins.json +86 -0
  59. package/evals/redirect-trace.json +57 -0
  60. package/evals/refresh-priorities.json +57 -0
  61. package/evals/search-performance-overview.json +86 -0
  62. package/evals/second-page.json +58 -0
  63. package/evals/segment-impact.json +57 -0
  64. package/evals/seo-fix-queue.json +85 -0
  65. package/evals/seo-geo-readiness.json +84 -0
  66. package/evals/seo-site-audit.json +85 -0
  67. package/evals/seo-to-ai-query.json +57 -0
  68. package/evals/seo.json +87 -0
  69. package/evals/setup-check.json +56 -0
  70. package/evals/site-crawl.json +44 -0
  71. package/evals/striking-distance.json +85 -0
  72. package/evals/technical-watch.json +57 -0
  73. package/evals/top-fixes.json +44 -0
  74. package/evals/traffic-anomaly.json +57 -0
  75. package/evals/update-correlation.json +57 -0
  76. package/evals/update-postmortem.json +57 -0
  77. package/package.json +103 -23
  78. package/skills/README.md +68 -0
  79. package/skills/seo/SKILL.md +99 -0
  80. package/skills/seo/agents/openai.yaml +4 -0
  81. package/.jshintrc +0 -18
  82. package/.npmignore +0 -3
  83. package/.travis.yml +0 -3
  84. package/Makefile +0 -10
  85. package/index.js +0 -2
  86. package/lib/middleware.js +0 -159
  87. package/lib/seo.js +0 -32
  88. package/test/init.js +0 -43
  89. package/test/middleware.test.js +0 -111
  90. package/test/seo-page-result.html +0 -9
  91. package/test/seo-page.html +0 -9
  92. package/test/seo-routes.js +0 -5
  93. package/test/vendor/jquery-1.11.1.min.js +0 -4
@@ -0,0 +1,57 @@
1
+ {
2
+ "subject": "seo-to-ai-query",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Give me a set of prompts to monitor whether AI tools mention us.",
7
+ "expected_output": "Should use report id seo-to-ai-query to turn real Search Console query wording into a repeatable set of prompts for external AI monitoring. Should follow the discovery flow: seo_list_reports with category ai-search, seo_describe_report with id seo-to-ai-query, then seo_run_report with the required site. Should read dataStatus states empty, filtered, partial, and available, then dateRange, source.possiblyTruncated, filters, selection.invalidRows, and limited rows. Should note methodology.observedAiPromptData and estimatedTrafficLift are both false.",
8
+ "assertions": [
9
+ "Uses report id seo-to-ai-query for the monitoring prompt set",
10
+ "Follows seo_list_reports, seo_describe_report, seo_run_report in order",
11
+ "Reads dataStatus states empty, filtered, partial, and available",
12
+ "Reads dateRange, source.possiblyTruncated, filters, and selection.invalidRows",
13
+ "Notes methodology.observedAiPromptData and estimatedTrafficLift are both false"
14
+ ],
15
+ "files": []
16
+ },
17
+ {
18
+ "id": 2,
19
+ "prompt": "Run the AI monitoring prompt report for sc-domain:example.com over 90 days, top 40 prompts, at least 25 impressions, and exclude brand.",
20
+ "expected_output": "Should run report id seo-to-ai-query with site sc-domain:example.com, days 90, limit 40, minImpressions 25, and includeBrand false. Should read source.possiblyTruncated and selection.invalidRows before treating the limited rows as complete, then per-item evidenceScope. Should note each prompt inherits the narrow retained-GSC-query scope, and use only parameters defined in the schema.",
21
+ "assertions": [
22
+ "Runs report id seo-to-ai-query with days 90, limit 40, and minImpressions 25",
23
+ "Sets includeBrand false to exclude brand terms",
24
+ "Reads source.possiblyTruncated and selection.invalidRows before treating rows as complete",
25
+ "Reads per-item evidenceScope",
26
+ "Uses only parameters defined in the report schema"
27
+ ],
28
+ "files": []
29
+ },
30
+ {
31
+ "id": 3,
32
+ "prompt": "You generated 40 prompts, so there is clearly AI demand for us and answers are mentioning our brand.",
33
+ "expected_output": "Should refuse to read the prompt count as demand or mentions. Should state the generated prompt count is not AI demand, and prompts do not prove anyone entered them or that an answer mentioned the site. Should note methodology.observedAiPromptData is false and each prompt inherits the narrow retained-GSC-query scope. Should recommend storing the source query and date range beside later monitoring observations, and point to ai-referrals for measured GA4 referral evidence.",
34
+ "assertions": [
35
+ "Refuses to read the prompt count as AI demand or as mentions",
36
+ "States the prompt count is not AI demand",
37
+ "States prompts do not prove anyone entered them or that an answer mentioned the site",
38
+ "Notes each prompt inherits the narrow retained-GSC-query scope",
39
+ "Points to report id ai-referrals for measured GA4 referral evidence"
40
+ ],
41
+ "files": []
42
+ },
43
+ {
44
+ "id": 4,
45
+ "prompt": "No prompt rows came back, so there are no AI prompt opportunities and this run already covers all our topics.",
46
+ "expected_output": "Should refuse to read no returned rows as an all-clear or as full topic coverage. Should state no returned rows is not an all-clear when the source is partial or filtered, and check dataStatus, source.possiblyTruncated, filters, and selection.invalidRows. Should note each prompt inherits the narrow retained-GSC-query scope, so the run does not cover every topic, and recommend widening the window or lowering minImpressions before concluding.",
47
+ "assertions": [
48
+ "Refuses to read no returned rows as an all-clear",
49
+ "States no returned rows is not an all-clear when the source is partial or filtered",
50
+ "Checks dataStatus, source.possiblyTruncated, filters, and selection.invalidRows",
51
+ "Notes each prompt inherits the narrow retained-GSC-query scope",
52
+ "Recommends widening the window or lowering minImpressions before concluding"
53
+ ],
54
+ "files": []
55
+ }
56
+ ]
57
+ }
package/evals/seo.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "subject": "seo",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Something feels off with our Google traffic lately but I can't tell what. Can you dig into it?",
7
+ "expected_output": "Should recognize this as an open diagnosis with no known cause and route to the main search performance baseline before any focused report. Should follow the compact MCP flow: seo_list_reports to find ids, seo_describe_report once for the chosen id, then seo_run_report with bounded params. Should name search-performance-overview as the right first report and offer the CLI fallback `seo report --project <project> --json` when MCP is unavailable. Should check dataStatus, source completeness, warnings, and caveats before summarizing, and should not invent clicks, sessions, or rankings.",
8
+ "assertions": [
9
+ "Routes to search-performance-overview as the first report for an unknown-cause drop",
10
+ "Uses the seo_list_reports then seo_describe_report then seo_run_report discovery order",
11
+ "Does not call seo_describe_report for every report up front",
12
+ "Offers the CLI fallback `seo report --project <project> --json` for the no-MCP case",
13
+ "Says it will check dataStatus, source completeness, warnings, and caveats before summarizing",
14
+ "Does not invent clicks, sessions, or ranking numbers"
15
+ ],
16
+ "files": []
17
+ },
18
+ {
19
+ "id": 2,
20
+ "prompt": "Agent task: return a machine-readable diagnosis for sc-domain:example.com. I need the structured result, not a written report.",
21
+ "expected_output": "Should reason from MCP structuredContent and treat text or Markdown as the display layer only. Should run report id search-performance-overview through seo_run_report for the property. Should check summary.updateAttributionStatus first and treat summary.updateAttribution as update-correlation context rather than the site's overall diagnosis. Should surface dataStatus, selection counts, and caveats in the structured handoff. Should not switch to the narrative Markdown surface when the caller asked for the machine contract.",
22
+ "assertions": [
23
+ "Reasons from structuredContent rather than the Markdown or text display layer",
24
+ "Runs report id search-performance-overview via seo_run_report",
25
+ "Checks summary.updateAttributionStatus before describing the diagnosis",
26
+ "Treats summary.updateAttribution as update-correlation context, not the overall verdict",
27
+ "Preserves dataStatus, selection counts, and caveats in the structured output",
28
+ "Does not substitute report-narrative or monthly-report Markdown for the machine contract"
29
+ ],
30
+ "files": []
31
+ },
32
+ {
33
+ "id": 3,
34
+ "prompt": "The quick-wins run shows an estimatedCtrClickShortfall of about 2,400. So if we fix those titles we get roughly 2,400 more clicks a month, right?",
35
+ "expected_output": "Should decline to confirm the number as a forecast. Should explain that quick-win CTR targets and estimatedCtrClickShortfall are deterministic prioritisation heuristics computed against a site-aware CTR benchmark, not estimated lift or promised traffic. Should note that average position is not a stable rank and that a title change does not guarantee CTR movement. Should reframe the shortfall as a way to rank review candidates and recommend measuring a later complete window to see the real effect.",
36
+ "assertions": [
37
+ "Refuses to present estimatedCtrClickShortfall as expected or guaranteed clicks",
38
+ "Labels the CTR target and shortfall as prioritisation heuristics, not lift",
39
+ "Notes that average position is not a stable daily rank",
40
+ "Explains the shortfall only ranks review candidates",
41
+ "Recommends comparing a later complete window to measure the real change",
42
+ "Does not restate 2,400 clicks as a projected gain"
43
+ ],
44
+ "files": []
45
+ },
46
+ {
47
+ "id": 4,
48
+ "prompt": "Pull the query totals grouped by page for last month so I can report exact monthly click counts per URL to my boss.",
49
+ "expected_output": "Should warn that grouped GSC query totals can undercount because anonymised query rows are dropped from grouped sums, so the grouped total is not a guaranteed property total. Should keep zero, missing, capped, and complete states distinct and never turn an anonymised or capped subset into a definitive count or zero. Should point the caller at page-dimension totals when a fuller per-URL number is needed and label any grouped figure as a retained subset. Should not present the grouped sum as the exact monthly clicks.",
50
+ "assertions": [
51
+ "Warns that grouped GSC query totals can undercount from anonymised rows",
52
+ "States the grouped sum is a retained subset, not a guaranteed property total",
53
+ "Keeps capped, missing, and complete states distinct",
54
+ "Does not present the grouped total as an exact per-URL count",
55
+ "Suggests the page-dimension total when a fuller number is required"
56
+ ],
57
+ "files": []
58
+ },
59
+ {
60
+ "id": 5,
61
+ "prompt": "I don't care about Search Console right now. I just want the technical issues on the site itself, like broken tags and crawl problems.",
62
+ "expected_output": "Should route away from the Search Console diagnosis reports and toward technical crawling. Should name report id site-crawl to build a saved baseline and report id top-fixes for a bounded queue, with the CLI path seo crawl --project <project> --max-pages 500 --save --json. Should keep advice structural and diagnostic and not claim search visibility or value from crawl evidence alone. Should offer report id explain-crawl-issue for an unfamiliar rule.",
63
+ "assertions": [
64
+ "Routes to technical crawling instead of Search Console diagnosis reports",
65
+ "Names report id site-crawl for the saved baseline",
66
+ "Names report id top-fixes for the bounded fix queue",
67
+ "Shows a seo crawl command with a page cap and --save",
68
+ "Does not claim search visibility or page value from crawl evidence alone",
69
+ "Offers report id explain-crawl-issue for unfamiliar rules"
70
+ ],
71
+ "files": []
72
+ },
73
+ {
74
+ "id": 6,
75
+ "prompt": "Just read the report and tell me exactly what position we rank at for 'invoice software' so I can put it in a slide.",
76
+ "expected_output": "Should explain that GSC average position is an averaged, sampled metric that varies by search, device, country, and day, so it is not a fixed live rank for a single keyword. Should present the observed average position with its window and source rather than a single exact rank, and should flag if the row is capped, filtered, or anonymised. Should offer to confirm current placement with a bounded live check rather than asserting a literal rank from retained data.",
77
+ "assertions": [
78
+ "Explains average position is averaged and sampled, not a live rank",
79
+ "Notes it varies by search, device, country, and day",
80
+ "Reports the value with its window and source instead of a single exact rank",
81
+ "Flags capped, filtered, or anonymised rows if present",
82
+ "Offers a bounded live verification instead of asserting the rank"
83
+ ],
84
+ "files": []
85
+ }
86
+ ]
87
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "subject": "setup-check",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "My reports keep erroring out. Is something wrong with my setup?",
7
+ "expected_output": "Should route to report id setup-check, which checks Google login, scopes, local configuration, and saved defaults before running reports. Should follow the discovery flow: seo_list_reports with category setup, seo_describe_report with id setup-check, then seo_run_report. Should note the report takes no parameters. Should read ok first, then each checks entry status, distinguishing fail from warn, with its detail and fix, then generatedAt.",
8
+ "assertions": [
9
+ "Uses report id setup-check to diagnose the local setup",
10
+ "Discovers or identifies setup-check through the report catalog (MCP tools or seo reports list/describe/run)",
11
+ "Notes the report takes no parameters",
12
+ "Reads ok first, then each check status",
13
+ "Distinguishes fail from warn and reads each detail and fix",
14
+ "Presents the diagnosis from the actual check results rather than generic setup advice"
15
+ ],
16
+ "files": []
17
+ },
18
+ {
19
+ "id": 2,
20
+ "prompt": "Agent task: run the local setup check and tell me exactly what to fix.",
21
+ "expected_output": "Should run report id setup-check with no parameters, since the schema is empty, via seo reports run setup-check or seo_run_report. Should read each checks entry, separate fail from warn, and surface the detail and fix for each failed check. Should recommend applying the fix, re-running setup-check, then running a small read-only report for the intended property.",
22
+ "assertions": [
23
+ "Runs report id setup-check with no parameters",
24
+ "Separates fail from warn across the checks",
25
+ "Surfaces the detail and fix for each failed check",
26
+ "Recommends applying the fix and re-running setup-check",
27
+ "Recommends a small read-only report after setup passes"
28
+ ],
29
+ "files": []
30
+ },
31
+ {
32
+ "id": 3,
33
+ "prompt": "Setup check passed, so my token works, I can pull data for any property, and the site's SEO is fine.",
34
+ "expected_output": "Should refuse the claims. A passing result means local prerequisites look present, not that a token is still accepted by Google or that the account can access a property. A setup pass is not evidence about rankings, indexing, or site health. Should recommend running a small read-only report for the intended property to confirm access.",
35
+ "assertions": [
36
+ "Refuses to read a pass as a working token or property access",
37
+ "States a pass means local prerequisites look present, not accepted by Google",
38
+ "States a setup pass is not evidence about rankings, indexing, or site health",
39
+ "Recommends a small read-only report to confirm property access"
40
+ ],
41
+ "files": []
42
+ },
43
+ {
44
+ "id": 4,
45
+ "prompt": "It warns about missing saved defaults, so that is a blocking failure, right?",
46
+ "expected_output": "Should not treat the warning as a blocking failure. Missing saved defaults are usually warnings because explicit parameters can replace them. Should distinguish warn from fail and note a report can still run when the site is passed explicitly rather than from a saved default.",
47
+ "assertions": [
48
+ "Refuses to treat the missing-defaults warning as a blocking failure",
49
+ "States missing saved defaults are usually warnings because explicit parameters can replace them",
50
+ "Distinguishes warn from fail",
51
+ "Notes a report can run with the site passed explicitly"
52
+ ],
53
+ "files": []
54
+ }
55
+ ]
56
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "subject": "site-crawl",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Can you crawl our site and find the technical SEO problems across our pages? Want a baseline I can come back to.",
7
+ "expected_output": "Should use report id site-crawl to crawl linked pages, check technical SEO signals, and save a local baseline with a saved crawl id for follow-up. Should follow the discovery flow: seo_describe_report with id site-crawl, then seo_run_report with the required url and a bounded maxPages. Should read status, requestEvidenceStatus, attempted and fetched counts, failures, retained pages, and pageLimitReached first, then data-source states, then warnings and caveats, with pages, requests, and issues kept opt-in. Should preserve the saved crawl id for later filtering or comparison.",
8
+ "assertions": [
9
+ "Uses report id site-crawl to crawl and save a baseline",
10
+ "Passes the required url and a bounded maxPages",
11
+ "Reads status, requestEvidenceStatus, attempted, fetched, failures, and pageLimitReached first",
12
+ "Keeps pages, requests, and issues opt-in rather than dumping them",
13
+ "Preserves the saved crawl id for later filtering or comparison"
14
+ ],
15
+ "files": []
16
+ },
17
+ {
18
+ "id": 2,
19
+ "prompt": "Crawl https://example.com with maxPages 200, maxDepth 3, and js true, then hand me the highest-priority fixes.",
20
+ "expected_output": "Should run report id site-crawl with url https://example.com, maxPages 200, maxDepth 3, and js true, via seo reports run site-crawl or seo_run_report. Should read status, attempted and fetched counts, failures, retained pages, and pageLimitReached, then route to report id top-fixes against the saved crawl to rank the queue. Should note that if pageLimitReached is true the crawl was capped, and that a crawl is not an index, traffic source, or guarantee of complete site coverage.",
21
+ "assertions": [
22
+ "Runs report id site-crawl with maxPages 200, maxDepth 3, and js true",
23
+ "Reads status, attempted, fetched, failures, retained pages, and pageLimitReached",
24
+ "Routes to report id top-fixes against the saved crawl to rank fixes",
25
+ "States a crawl is not an index, traffic source, or complete coverage",
26
+ "Uses only parameters defined in the report schema"
27
+ ],
28
+ "files": []
29
+ },
30
+ {
31
+ "id": 3,
32
+ "prompt": "The crawl finished and found no critical issues. That means the whole site is clean and every page is fine, right?",
33
+ "expected_output": "Should refuse to read a finished crawl as a sitewide all-clear. Should explain that a partial or capped crawl cannot support an all-clear or a definitive zero, and check pageLimitReached, attempted versus fetched counts, and failures before concluding. Should state that if JavaScript rendering was disabled that is a discovery boundary, and recommend preserving the saved crawl id and re-running the same configuration to confirm any change.",
34
+ "assertions": [
35
+ "Refuses to read the finished crawl as a sitewide all-clear",
36
+ "States a partial or capped crawl cannot support an all-clear or a definitive zero",
37
+ "Checks pageLimitReached, attempted versus fetched counts, and failures",
38
+ "States a disabled JavaScript rendering is a discovery boundary",
39
+ "Recommends re-running the same configuration to confirm a change"
40
+ ],
41
+ "files": []
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,85 @@
1
+ {
2
+ "subject": "striking-distance",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Find the keywords we're ranking just below page one for so we know what to push on.",
7
+ "expected_output": "Should use report id striking-distance to find retained query and page pairs with average positions from 11 through 20. Should follow the discovery flow: seo_list_reports with category diagnosis, seo_describe_report with id striking-distance, then seo_run_report with only described parameters. Should describe the 11 to 20 band as a heuristic boundary and note average position varies by search, device, country, and day, so it does not prove a URL consistently ranks on the literal second page. Should check range, dataStatus, and source before the item list.",
8
+ "assertions": [
9
+ "Uses report id striking-distance for the near-page-one queue",
10
+ "Follows seo_list_reports, seo_describe_report, seo_run_report in order",
11
+ "Describes the 11 to 20 band as a heuristic boundary",
12
+ "Notes average position varies by search, device, country, and day",
13
+ "Checks range, dataStatus, and source before the item list",
14
+ "Does not claim the URLs consistently sit on the literal second page"
15
+ ],
16
+ "files": []
17
+ },
18
+ {
19
+ "id": 2,
20
+ "prompt": "Run striking-distance for sc-domain:example.com over 28 days, minimum 50 impressions, top 10, brand excluded, and verify the top 5 pages.",
21
+ "expected_output": "Should run report id striking-distance with params site sc-domain:example.com, days 28, minImpressions 50, limit 10, includeBrand false, verifyContent true, verifyLimit 5, via seo reports run striking-distance --params or seo_run_report. Should inspect the schema before adding brandTerms, JavaScript rendering, or fetch-rate controls. Should read selection to see what was invalid, outside the range, branded, below thresholds, eligible, returned, or limited.",
22
+ "assertions": [
23
+ "Runs report id striking-distance with days 28 and minImpressions 50",
24
+ "Sets limit 10, includeBrand false, verifyContent true, verifyLimit 5",
25
+ "Inspects the schema before adding brandTerms or JavaScript rendering",
26
+ "Reads selection for invalid, out-of-range, branded, below-threshold, and returned counts",
27
+ "Uses only parameters defined in the report schema"
28
+ ],
29
+ "files": []
30
+ },
31
+ {
32
+ "id": 3,
33
+ "prompt": "Every row here is on page 2. Just rewrite all of them and we'll jump to page one. Kick that off.",
34
+ "expected_output": "Should not rewrite pages solely because they sit inside the position band. Should explain the 11 to 20 range is a heuristic boundary and average position varies, so it does not prove a stable second-page rank or predict how many clicks a change produces. Should recommend auditing a small number of high-impression candidates, fixing verified technical problems first, then comparing intent, title framing, coverage, and internal links against the observed query evidence. Should not promise a jump to page one.",
35
+ "assertions": [
36
+ "Refuses to rewrite pages solely for being in the position band",
37
+ "States the 11 to 20 range is a heuristic boundary, not a stable rank",
38
+ "Does not promise a jump to page one",
39
+ "Recommends auditing a small number of high-impression candidates",
40
+ "Fixes verified technical problems before content rewrites",
41
+ "Compares intent, title framing, coverage, and internal links against query evidence"
42
+ ],
43
+ "files": []
44
+ },
45
+ {
46
+ "id": 4,
47
+ "prompt": "The report returned 10 rows and nothing for our /guides section. So /guides has no near-page-one queries. Right?",
48
+ "expected_output": "Should not read the empty /guides result as a definitive zero. Should explain that source.possiblyTruncated and retained-row completeness limit any all-clear, and limit only caps returned rows without increasing coverage. Should keep filtered, out-of-range, capped, and complete states distinct. Should recommend raising limit, widening the window, or checking the source cap before concluding /guides has no candidates.",
49
+ "assertions": [
50
+ "Refuses to treat the empty /guides result as a definitive zero",
51
+ "Cites source.possiblyTruncated and retained-row completeness as limits",
52
+ "Notes limit caps returned rows and does not increase coverage",
53
+ "Keeps filtered, out-of-range, capped, and complete states distinct",
54
+ "Recommends widening scope or checking the cap before concluding"
55
+ ],
56
+ "files": []
57
+ },
58
+ {
59
+ "id": 5,
60
+ "prompt": "Content verification came back failed for two of the candidates. Treat them the same as the verified ones and prioritize a rewrite.",
61
+ "expected_output": "Should keep unverified or failed pages at lower confidence rather than treating them as verified. Should read the verification counts for requested, attempted, verified, technical, and failed. Should fix verified technical problems first and inspect the failed pages before recommending a content rewrite. Should not equate a failed verification with a confirmed content gap.",
62
+ "assertions": [
63
+ "Keeps unverified or failed pages at lower confidence",
64
+ "Reads verification requested, attempted, verified, technical, and failed counts",
65
+ "Does not treat failed pages the same as verified ones",
66
+ "Fixes verified technical problems before content rewrites",
67
+ "Inspects the failed pages before recommending a rewrite"
68
+ ],
69
+ "files": []
70
+ },
71
+ {
72
+ "id": 6,
73
+ "prompt": "Give me the priority score for each row and tell me how much traffic each one will bring once we optimize it.",
74
+ "expected_output": "Should present the priority score as a prioritisation heuristic described in methodology, not as expected traffic or ranking lift. Should refuse to convert the score into a traffic forecast. Should explain the score orders review candidates and that observed clicks, impressions, CTR, and average position are evidence, not predictions. Should recommend measuring a later complete window to see actual change.",
75
+ "assertions": [
76
+ "Presents the priority score as a prioritisation heuristic from methodology",
77
+ "Refuses to convert the score into a traffic forecast",
78
+ "Does not present the score as expected clicks or ranking lift",
79
+ "Explains the score orders review candidates only",
80
+ "Recommends comparing a later complete window to measure real change"
81
+ ],
82
+ "files": []
83
+ }
84
+ ]
85
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "subject": "technical-watch",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Set up a check that catches technical SEO regressions after we deploy.",
7
+ "expected_output": "Should use report id technical-watch to run crawl and index checks together to catch technical regressions on a schedule. Should follow the discovery flow: seo_list_reports with category workflows, seo_describe_report with id technical-watch, then seo_run_report with the required site and at least one crawl, index, or link recovery input. Should read each workflow step and its nested crawl, index, and recovery output independently. Should clarify a successful run does not prove every URL is crawlable, indexed, unchanged, or issue-free.",
8
+ "assertions": [
9
+ "Uses report id technical-watch for the recurring crawl and index checks",
10
+ "Notes it needs at least one crawl, index, or link recovery input",
11
+ "Follows seo_list_reports, seo_describe_report, seo_run_report in order",
12
+ "Reads each workflow step and its nested crawl, index, and recovery output independently",
13
+ "States a successful run does not prove every URL is crawlable, indexed, unchanged, or issue-free"
14
+ ],
15
+ "files": []
16
+ },
17
+ {
18
+ "id": 2,
19
+ "prompt": "Run technical watch for sc-domain:example.com against our sitemap https://example.com/sitemap.xml, inspect up to 50 URLs, and recover broken search-value links from the last 90 days.",
20
+ "expected_output": "Should run report id technical-watch with site sc-domain:example.com, sitemaps including https://example.com/sitemap.xml, inspectLimit 50, recoverLinks true, and recoverDays 90. Should read the crawl run ids, limits, failures, and changed items, then the inspection selected, unselected-due, attempted, failed, quota-blocked, deferred, and regression states, then recovery clicks-at-risk as history. Should use only parameters defined in the schema.",
21
+ "assertions": [
22
+ "Runs report id technical-watch with the sitemap and inspectLimit 50",
23
+ "Sets recoverLinks true and recoverDays 90 as requested",
24
+ "Reads crawl run ids, limits, failures, and changed items",
25
+ "Reads inspection selected, attempted, failed, quota-blocked, deferred, and regression states",
26
+ "Reads recovery clicks-at-risk as history"
27
+ ],
28
+ "files": []
29
+ },
30
+ {
31
+ "id": 3,
32
+ "prompt": "The run passed, so every page is crawlable, indexed, and issue-free, and all the sitemap URLs are indexed.",
33
+ "expected_output": "Should refuse the all-clear. Should state a successful run does not prove every URL is crawlable, indexed, unchanged, or issue-free, that URL Inspection is a provider snapshot and sitemap discovery is not proof of indexing, and that partial crawls, capped inventories, quotas, and failed subqueries must stay visible in any all-clear. Should read the crawl limits and inspection states before making any sitewide claim.",
34
+ "assertions": [
35
+ "Refuses the sitewide all-clear",
36
+ "States a successful run does not prove every URL is crawlable, indexed, unchanged, or issue-free",
37
+ "States URL Inspection is a provider snapshot and sitemap discovery is not proof of indexing",
38
+ "States partial crawls, capped inventories, quotas, and failed subqueries must stay visible",
39
+ "Reads the crawl limits and inspection states before any sitewide claim"
40
+ ],
41
+ "files": []
42
+ },
43
+ {
44
+ "id": 4,
45
+ "prompt": "Some inspections were quota-blocked but the rest passed, so there are no index regressions.",
46
+ "expected_output": "Should refuse to read quota-blocked inspections as clear. Should state quotas and failed subqueries must stay visible in any all-clear, and read the inspection unselected-due, quota-blocked, deferred, and regression states rather than assuming the unchecked URLs are fine. Should keep checked, quota-blocked, deferred, and regressed states distinct and recommend reproducing new failures, then scheduling another bounded run with a rollback path.",
47
+ "assertions": [
48
+ "Refuses to read the quota-blocked inspections as clear",
49
+ "States quotas and failed subqueries must stay visible in any all-clear",
50
+ "Reads the inspection unselected-due, quota-blocked, deferred, and regression states",
51
+ "Keeps checked, quota-blocked, deferred, and regressed states distinct",
52
+ "Recommends reproducing failures then scheduling another bounded run with a rollback path"
53
+ ],
54
+ "files": []
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "subject": "top-fixes",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Our crawl came back with hundreds of issues and I have no idea where to start. What should we fix first?",
7
+ "expected_output": "Should use report id top-fixes to turn crawl findings into a short, ranked queue of technical issues to investigate first. Should follow the discovery flow: seo_describe_report with id top-fixes, then seo_run_report from a saved crawl by reportId or site, with a bounded limit. Should read dataSources, summary, warnings, and caveats first, then per-item rule id, affected count, sample URLs, scoreFactors, and whyThisRanks, then effort, fix guidance, and verification. Should treat rank as a triage order among retained findings, not proof of business impact.",
8
+ "assertions": [
9
+ "Uses report id top-fixes to build the ranked triage queue",
10
+ "Works from a saved crawl by reportId or site with a bounded limit",
11
+ "Reads dataSources, summary, warnings, and caveats first",
12
+ "Reads per-item rule id, affected count, sample URLs, scoreFactors, and whyThisRanks",
13
+ "Treats rank as triage order, not proof of business impact"
14
+ ],
15
+ "files": []
16
+ },
17
+ {
18
+ "id": 2,
19
+ "prompt": "Run top-fixes on saved crawl crawl_example_20260710 for category indexability, limit 10, and give me the reasons each one ranks.",
20
+ "expected_output": "Should run report id top-fixes with reportId crawl_example_20260710, category indexability, and limit 10, via seo reports run top-fixes or seo_run_report. Should read dataSources, summary, warnings, and caveats, then per-item rule id, affected count, sample URLs, scoreFactors, and whyThisRanks, then effort and verification. Should route to report id affected-urls for the same rule and saved crawl to list every affected URL, and note limit bounds the returned items.",
21
+ "assertions": [
22
+ "Runs report id top-fixes with reportId crawl_example_20260710, category indexability, and limit 10",
23
+ "Reads per-item rule id, affected count, sample URLs, scoreFactors, and whyThisRanks",
24
+ "Routes to report id affected-urls for the same rule to list every URL",
25
+ "Notes limit bounds the returned items",
26
+ "Uses only parameters defined in the report schema"
27
+ ],
28
+ "files": []
29
+ },
30
+ {
31
+ "id": 3,
32
+ "prompt": "The number one item here is our single biggest business problem and this is the globally worst issue on the site. Forecast the traffic we'll gain by fixing it.",
33
+ "expected_output": "Should refuse all three claims. Should explain that rank means highest among retained eligible findings, not globally highest, because the crawl or category may be capped, and that this is not a forecast of rankings, traffic, or revenue. Should note that a numeric zero is meaningful only when the source is complete and missing provider joins stay unavailable. Should recommend confirming the finding with report id affected-urls before treating it as the top priority.",
34
+ "assertions": [
35
+ "Refuses to call the top item the single biggest business problem",
36
+ "States rank means highest among retained eligible findings, not globally highest",
37
+ "Notes the crawl or category may be capped",
38
+ "Refuses to forecast rankings, traffic, or revenue from the queue",
39
+ "Recommends confirming with report id affected-urls before prioritising"
40
+ ],
41
+ "files": []
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "subject": "traffic-anomaly",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Did anything weird happen with our search traffic lately?",
7
+ "expected_output": "Should route to report id traffic-anomaly, which finds recent clicks or impressions that moved beyond the site's normal Search Console pattern. Should follow the discovery flow: seo_list_reports with category diagnosis, seo_describe_report with id traffic-anomaly, then seo_run_report with site. Should read coverage with requested dates, expected versus observed days, missing days, invalid rows, and status first, then the anomalies with metric, baseline and comparison dates, percentChange, zScore, and significanceMethod, then direction and significant. Should treat the output as a movement signal, not a cause.",
8
+ "assertions": [
9
+ "Uses report id traffic-anomaly for the movement signal",
10
+ "Follows seo_list_reports, seo_describe_report, seo_run_report in order",
11
+ "Reads coverage with expected versus observed days and status first",
12
+ "Reads anomalies with percentChange, zScore, and significanceMethod",
13
+ "Reads direction and significant",
14
+ "Treats the output as a movement signal, not a cause"
15
+ ],
16
+ "files": []
17
+ },
18
+ {
19
+ "id": 2,
20
+ "prompt": "Agent task: run traffic-anomaly for sc-domain:example.com with a 90 day baseline and a 7 day recent window.",
21
+ "expected_output": "Should run report id traffic-anomaly with site sc-domain:example.com, days 90, and recentDays 7, via seo reports run traffic-anomaly or seo_run_report. Should use only site, days, recentDays, and refresh from the schema. Should check coverage status and missing days before reading whether the movement is significant, and if it is, run report id segment-impact next.",
22
+ "assertions": [
23
+ "Runs report id traffic-anomaly with days 90 and recentDays 7",
24
+ "Uses only parameters defined in the traffic-anomaly schema",
25
+ "Checks coverage status and missing days before reading significance",
26
+ "Plans to run report id segment-impact if the movement is significant"
27
+ ],
28
+ "files": []
29
+ },
30
+ {
31
+ "id": 3,
32
+ "prompt": "The spike is significant, so the algorithm update caused it. Put that down as the reason.",
33
+ "expected_output": "Should refuse. Statistical unusualness does not identify a cause, diagnose a defect, or prove an external event affected the property. Should keep the signal separate from a cause and recommend report id update-correlation for update timing and report id segment-impact for where the movement sits.",
34
+ "assertions": [
35
+ "Refuses to name the algorithm update as the cause",
36
+ "States statistical unusualness does not identify a cause or prove an external event",
37
+ "Keeps the movement signal separate from a cause",
38
+ "Recommends report id update-correlation for update timing",
39
+ "Recommends report id segment-impact for where the movement sits"
40
+ ],
41
+ "files": []
42
+ },
43
+ {
44
+ "id": 4,
45
+ "prompt": "percentChange is null, so there was zero change, and just ignore the missing days in the window.",
46
+ "expected_output": "Should refuse both readings. A null percentage from a zero baseline differs from zero change, and partial coverage can change the baseline. Should read coverage expected versus observed days, missing days, and status before trusting the signal, and not equate a null percentage with a flat result.",
47
+ "assertions": [
48
+ "Refuses to read a null percentChange as zero change",
49
+ "States a null percentage from a zero baseline differs from zero change",
50
+ "States partial coverage can change the baseline",
51
+ "Reads coverage expected versus observed days and missing days",
52
+ "Does not ignore the missing days in the window"
53
+ ],
54
+ "files": []
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "subject": "update-correlation",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Our traffic moved around the time of a Google update. Can you line those up?",
7
+ "expected_output": "Should route to report id update-correlation, which compares search movement dates with official Google ranking update windows and labels overlaps as correlation. Should follow the discovery flow: seo_list_reports with category diagnosis, seo_describe_report with id update-correlation, then seo_run_report with site. Should read the classification with attribution and confidence, returned as not-established and none, first, then anomalies, overlappingUpdates, and confounders, then evidence, caveats, and source.",
8
+ "assertions": [
9
+ "Uses report id update-correlation for the timeline",
10
+ "Follows seo_list_reports, seo_describe_report, seo_run_report in order",
11
+ "Reads classification with attribution not-established and confidence none first",
12
+ "Reads anomalies, overlappingUpdates, and confounders",
13
+ "Reads evidence, caveats, and source",
14
+ "Presents overlaps as correlation, not causation"
15
+ ],
16
+ "files": []
17
+ },
18
+ {
19
+ "id": 2,
20
+ "prompt": "Agent task: run update-correlation for sc-domain:example.com over 180 days with a 14 day recent window and 3 padding days.",
21
+ "expected_output": "Should run report id update-correlation with site sc-domain:example.com, days 180, recentDays 14, and paddingDays 3, via seo reports run update-correlation or seo_run_report. Should use only site, days, recentDays, paddingDays, and refresh from the schema. Should confirm the traffic window covers the update dates before reading overlaps.",
22
+ "assertions": [
23
+ "Runs report id update-correlation with days 180 and recentDays 14",
24
+ "Sets paddingDays 3 as requested",
25
+ "Uses only parameters defined in the update-correlation schema",
26
+ "Confirms the traffic window covers the update dates before reading overlaps"
27
+ ],
28
+ "files": []
29
+ },
30
+ {
31
+ "id": 3,
32
+ "prompt": "The drop overlaps the March core update and nothing else changed, so the update caused it. Confirm the penalty.",
33
+ "expected_output": "Should refuse. Overlap cannot show the update caused a gain or loss even with large movement and no other recorded change, and temporal overlap is not a penalty, reward, or confirmed effect. Attribution is returned as not-established. Should recommend testing saved deploys, redirects, and content changes that overlap the window before attributing the movement.",
34
+ "assertions": [
35
+ "Refuses to confirm the update caused the drop",
36
+ "States overlap cannot show causation even with large movement and no other change",
37
+ "States temporal overlap is not a penalty, reward, or confirmed effect",
38
+ "Notes attribution is returned as not-established",
39
+ "Recommends testing deploys, redirects, and content changes in the window first"
40
+ ],
41
+ "files": []
42
+ },
43
+ {
44
+ "id": 4,
45
+ "prompt": "There is an overlap but no significant movement, so mark it an impact. And where there is no overlap, that must be what explains the rest of the drop.",
46
+ "expected_output": "Should refuse both readings. update-overlap-without-significant-movement is not an impact result, and no-update-overlap does not explain remaining movement. Should keep the classification honest and recommend report id segment-impact and report id traffic-anomaly to investigate the movement rather than forcing the update timeline to explain it.",
47
+ "assertions": [
48
+ "Refuses to mark an overlap without significant movement as an impact",
49
+ "States update-overlap-without-significant-movement is not an impact result",
50
+ "States no-update-overlap does not explain remaining movement",
51
+ "Recommends report id segment-impact to investigate the movement",
52
+ "Recommends report id traffic-anomaly to confirm the movement"
53
+ ],
54
+ "files": []
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "subject": "update-postmortem",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "The March core update seems to have hit us. What happened?",
7
+ "expected_output": "Should use report id update-postmortem to find the pages and queries that gained or lost around a confirmed Google ranking update. Should follow the discovery flow: seo_list_reports with category workflows, seo_describe_report with id update-postmortem, then seo_run_report with the required site. Should read the workflow step statuses, finalized date windows, and source completeness first, then failed measurements, retained limits, and caveats, then update, insights, templateMovement, and segments. Should clarify the cause remains not established even when dates overlap.",
8
+ "assertions": [
9
+ "Uses report id update-postmortem for the winners and losers review",
10
+ "Follows seo_list_reports, seo_describe_report, seo_run_report in order",
11
+ "Reads workflow step statuses, finalized date windows, and source completeness first",
12
+ "Reads update, insights, templateMovement, and segments",
13
+ "States the cause remains not established even when dates overlap"
14
+ ],
15
+ "files": []
16
+ },
17
+ {
18
+ "id": 2,
19
+ "prompt": "Run the update postmortem for sc-domain:example.com over 90 days with a 28 day recent window, include the change log, and note that we ran a migration as a known confounder.",
20
+ "expected_output": "Should run report id update-postmortem with site sc-domain:example.com, days 90, recentDays 28, includeChangeLog true, and knownConfounders including the migration. Should read the finalized date windows and source completeness, then failed measurements, retained limits, and caveats, then templateMovement and segments. Should use only parameters defined in the schema.",
21
+ "assertions": [
22
+ "Runs report id update-postmortem with days 90 and recentDays 28",
23
+ "Sets includeChangeLog true and passes the migration in knownConfounders",
24
+ "Reads the finalized date windows and source completeness",
25
+ "Reads failed measurements, retained limits, and caveats",
26
+ "Reads templateMovement and segments"
27
+ ],
28
+ "files": []
29
+ },
30
+ {
31
+ "id": 3,
32
+ "prompt": "The update clearly caused every one of these changes. Tell me our recovery odds and next month's traffic.",
33
+ "expected_output": "Should refuse every move. Should state the cause remains not established even when dates overlap, and that the report does not claim update causation, recovery probability, or future traffic. Should recommend inspecting representative URLs from the affected segments and reproducing technical changes before any rollback, rather than asserting causation or a forecast.",
34
+ "assertions": [
35
+ "Refuses to attribute every change to the update",
36
+ "States the cause remains not established even when dates overlap",
37
+ "Refuses to claim recovery probability or future traffic",
38
+ "Recommends inspecting representative URLs from the affected segments",
39
+ "Recommends reproducing technical changes before any rollback"
40
+ ],
41
+ "files": []
42
+ },
43
+ {
44
+ "id": 4,
45
+ "prompt": "This winners and losers list is every affected page, even though our recent period is not final yet.",
46
+ "expected_output": "Should refuse both readings. Should state retained winners and losers are not a complete inventory when sources are capped or filtered, and that the report should be avoided when the comparison periods are incomplete or contain major unrecorded changes. Should read the finalized date windows, retained limits, failed measurements, and caveats, and recommend waiting for final data before treating the list as complete.",
47
+ "assertions": [
48
+ "Refuses to treat the list as every affected page",
49
+ "States retained winners and losers are not a complete inventory when sources are capped or filtered",
50
+ "States the report should be avoided when the comparison periods are incomplete",
51
+ "Reads the finalized date windows, retained limits, failed measurements, and caveats",
52
+ "Recommends waiting for final data before treating the list as complete"
53
+ ],
54
+ "files": []
55
+ }
56
+ ]
57
+ }