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.
- package/LICENSE +201 -0
- package/PRIVACY.md +95 -0
- package/README.md +431 -35
- package/SECURITY.md +55 -0
- package/TERMS.md +94 -0
- package/TRADEMARKS.md +31 -0
- package/dist/cli.js +141 -0
- package/dist/cli.js.map +1 -0
- package/dist/dist-6bK_8r5M.js +713 -0
- package/dist/dist-6bK_8r5M.js.map +1 -0
- package/dist/dist-vRmqfhR4.js +3 -0
- package/dist/index.d.ts +18013 -0
- package/dist/index.js +27197 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +74 -0
- package/dist/mcp.js +28611 -0
- package/dist/mcp.js.map +1 -0
- package/dist/package-7aT8_Xp0.js +23818 -0
- package/dist/package-7aT8_Xp0.js.map +1 -0
- package/evals/README.md +117 -0
- package/evals/affected-urls.json +57 -0
- package/evals/ai-readiness.json +84 -0
- package/evals/ai-referrals.json +71 -0
- package/evals/ai-search-scorecard.json +70 -0
- package/evals/audit-page.json +57 -0
- package/evals/audit-urls.json +58 -0
- package/evals/cannibalisation.json +57 -0
- package/evals/community-intent.json +57 -0
- package/evals/compare-crawls.json +56 -0
- package/evals/content-optimization.json +57 -0
- package/evals/crawl-diff.json +58 -0
- package/evals/crawl-history.json +44 -0
- package/evals/crawl-report.json +44 -0
- package/evals/crawler-rules.json +44 -0
- package/evals/ctr-underperformers.json +57 -0
- package/evals/decaying-pages.json +57 -0
- package/evals/entity-readiness.json +57 -0
- package/evals/explain-crawl-issue.json +44 -0
- package/evals/generate-llms-txt.json +44 -0
- package/evals/geo-gaps.json +57 -0
- package/evals/index-coverage-plan.json +58 -0
- package/evals/index-coverage.json +70 -0
- package/evals/index-monitor.json +58 -0
- package/evals/index-watch.json +68 -0
- package/evals/internal-links.json +57 -0
- package/evals/link-recovery.json +58 -0
- package/evals/llms-txt-audit.json +44 -0
- package/evals/measure-change.json +58 -0
- package/evals/monthly-action-plan.json +56 -0
- package/evals/monthly-report.json +58 -0
- package/evals/narrative-report.json +58 -0
- package/evals/okf-build.json +44 -0
- package/evals/okf-validate.json +44 -0
- package/evals/page-opportunities.json +57 -0
- package/evals/performance-audit.json +57 -0
- package/evals/pseo-audit.json +84 -0
- package/evals/query-clusters.json +57 -0
- package/evals/quick-wins.json +86 -0
- package/evals/redirect-trace.json +57 -0
- package/evals/refresh-priorities.json +57 -0
- package/evals/search-performance-overview.json +86 -0
- package/evals/second-page.json +58 -0
- package/evals/segment-impact.json +57 -0
- package/evals/seo-fix-queue.json +85 -0
- package/evals/seo-geo-readiness.json +84 -0
- package/evals/seo-site-audit.json +85 -0
- package/evals/seo-to-ai-query.json +57 -0
- package/evals/seo.json +87 -0
- package/evals/setup-check.json +56 -0
- package/evals/site-crawl.json +44 -0
- package/evals/striking-distance.json +85 -0
- package/evals/technical-watch.json +57 -0
- package/evals/top-fixes.json +44 -0
- package/evals/traffic-anomaly.json +57 -0
- package/evals/update-correlation.json +57 -0
- package/evals/update-postmortem.json +57 -0
- package/package.json +103 -23
- package/skills/README.md +68 -0
- package/skills/seo/SKILL.md +99 -0
- package/skills/seo/agents/openai.yaml +4 -0
- package/.jshintrc +0 -18
- package/.npmignore +0 -3
- package/.travis.yml +0 -3
- package/Makefile +0 -10
- package/index.js +0 -2
- package/lib/middleware.js +0 -159
- package/lib/seo.js +0 -32
- package/test/init.js +0 -43
- package/test/middleware.test.js +0 -111
- package/test/seo-page-result.html +0 -9
- package/test/seo-page.html +0 -9
- package/test/seo-routes.js +0 -5
- package/test/vendor/jquery-1.11.1.min.js +0 -4
package/evals/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Evals
|
|
2
|
+
|
|
3
|
+
Evals are behaviour tests for the `seo` skill. Each one pairs a realistic user
|
|
4
|
+
request with a description of the correct agent behaviour and a list of
|
|
5
|
+
checkable assertions. They exist to catch the skill routing to the wrong report
|
|
6
|
+
id, building bad parameters, skipping the evidence checks, or breaking a report
|
|
7
|
+
truth rule such as calling capped data an all-clear or promising traffic from a
|
|
8
|
+
heuristic.
|
|
9
|
+
|
|
10
|
+
Each file targets one report id or one common job. A report eval checks that the
|
|
11
|
+
skill discovers, describes, and runs that report and reads its evidence
|
|
12
|
+
correctly. A job eval checks a router-level chain, such as a full site audit or
|
|
13
|
+
a fix queue, where the correct move spans several reports in order.
|
|
14
|
+
|
|
15
|
+
## File shape
|
|
16
|
+
|
|
17
|
+
One file per subject at `evals/<subject>.json`. The `subject` names the report
|
|
18
|
+
id or the job the eval targets. It matches the filename.
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"subject": "quick-wins",
|
|
23
|
+
"evals": [
|
|
24
|
+
{
|
|
25
|
+
"id": 1,
|
|
26
|
+
"prompt": "Where can we get some easy SEO wins without new content?",
|
|
27
|
+
"expected_output": "Prose describing what a correct agent does.",
|
|
28
|
+
"assertions": [
|
|
29
|
+
"Uses report id quick-wins for the opportunity queue",
|
|
30
|
+
"Clarifies quick win names the review queue, not expected traffic"
|
|
31
|
+
],
|
|
32
|
+
"files": []
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Every `id` is a unique integer. `prompt`, `expected_output`, and each
|
|
39
|
+
`assertions` entry are non-empty. Each assertion is one behaviour a judge can
|
|
40
|
+
check on its own. Vary the prompts across a vague human ask, a precise agent
|
|
41
|
+
ask, and an adversarial ask where the correct move is to refuse a claim or
|
|
42
|
+
surface partial data.
|
|
43
|
+
|
|
44
|
+
## Run them
|
|
45
|
+
|
|
46
|
+
`seo skill eval` runs these against a real agent on your machine and judges the
|
|
47
|
+
result, so you catch a routing or truth-rule regression before it ships. It
|
|
48
|
+
stays local: you bring your own agent, and nothing is sent to a hosted service.
|
|
49
|
+
|
|
50
|
+
List what is available, then run one subject, several, or all of them:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
seo skill eval --list # subjects with their eval counts
|
|
54
|
+
seo skill eval quick-wins # one subject
|
|
55
|
+
seo skill eval quick-wins seo # several subjects
|
|
56
|
+
seo skill eval # every subject
|
|
57
|
+
seo skill eval quick-wins --id 3
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each eval sends the `seo` router skill as context, then the eval prompt, to your
|
|
61
|
+
agent. The default agent is `claude -p`. Point `--agent` at any command that
|
|
62
|
+
reads a prompt and prints a reply. Put `{prompt}` in the command to receive the
|
|
63
|
+
prompt as an argument, or leave it out to receive the prompt on stdin:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
seo skill eval quick-wins --agent "claude -p"
|
|
67
|
+
seo skill eval quick-wins --agent "my-agent --input {prompt}"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The prompt is always passed as a single argument, never through a shell, so a
|
|
71
|
+
prompt cannot inject extra commands. Use `--no-skill` to send the eval prompt
|
|
72
|
+
without the router skill, and `--timeout <seconds>` (default 300) to bound each
|
|
73
|
+
call. Run up to four evals at once with `--concurrency <n>`.
|
|
74
|
+
|
|
75
|
+
The agent must be allowed to execute the `seo` CLI or MCP tools, or every
|
|
76
|
+
behavioural assertion will fail for the honest reason that the agent never ran
|
|
77
|
+
anything. A sandboxed `claude -p` cannot approve permission prompts, so grant
|
|
78
|
+
the permission up front, for example with a permission mode or allowlist that
|
|
79
|
+
covers `seo` commands. Judged failures always carry the judge's reason, so a
|
|
80
|
+
sandbox problem is visible in the output rather than silent.
|
|
81
|
+
|
|
82
|
+
Write assertions about outcomes, not narration. An agent that used a field
|
|
83
|
+
rarely names it, so "presents the diagnosis from the check results" is
|
|
84
|
+
judgeable where "reads generatedAt last" is not. Accept both surfaces in
|
|
85
|
+
assertions: the MCP tools and the `seo reports` CLI flow are equivalent, and
|
|
86
|
+
an agent without MCP configured will correctly use the CLI.
|
|
87
|
+
|
|
88
|
+
### Judging
|
|
89
|
+
|
|
90
|
+
By default the same agent judges each reply against the assertions and returns a
|
|
91
|
+
pass or fail with a short reason. Judge with a different model using
|
|
92
|
+
`--judge-agent "<command>"`. These verdicts are model judgments, not ground
|
|
93
|
+
truth, so the output labels them as judge verdicts and keeps every reason
|
|
94
|
+
visible for you to check.
|
|
95
|
+
|
|
96
|
+
Use `--no-judge` to skip judging. That prints each reply next to an assertion
|
|
97
|
+
checklist so you can score the run by hand.
|
|
98
|
+
|
|
99
|
+
### CI usage
|
|
100
|
+
|
|
101
|
+
The command exits `0` when every assertion passes and `1` when any assertion
|
|
102
|
+
fails or an eval errors, so it drops straight into a pipeline. Add `--json` for
|
|
103
|
+
one structured document with per-assertion verdicts, reasons, durations, and the
|
|
104
|
+
agent and judge commands used. JSON mode never prompts and never decorates its
|
|
105
|
+
output.
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
seo skill eval --agent "claude -p" --json > eval-results.json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Validation
|
|
112
|
+
|
|
113
|
+
`node scripts/validate-skills.mjs` checks the structure of every `evals/*.json`.
|
|
114
|
+
It confirms the JSON parses, `subject` matches the filename, ids are unique
|
|
115
|
+
integers, the prompt, expected output, and assertions are non-empty, and every
|
|
116
|
+
backtick-quoted `seo` command and every referenced report id resolves to a real
|
|
117
|
+
command and report. It does not run the evals or judge agent output.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"subject": "affected-urls",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "The crawl flagged a load of missing meta descriptions. Which pages actually have that problem so I can go fix them?",
|
|
7
|
+
"expected_output": "Should use report id affected-urls to open the exact pages behind one crawl finding, working from an existing saved crawl by reportId or site rather than starting a new one. Should follow the discovery flow: seo_list_reports, seo_describe_report with id affected-urls, then seo_run_report with a ruleId filter and a bounded limit. Should read dataSources, then selection totalMatchedRows, returnedRows, and truncated before listing rows, and should scope the result to pages affected among the evaluated crawl, not the whole site.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Uses report id affected-urls to list the pages behind one finding",
|
|
10
|
+
"Works from an existing saved crawl by reportId or site rather than recrawling",
|
|
11
|
+
"Passes a ruleId filter and a bounded limit rather than unbounded input",
|
|
12
|
+
"Reads selection totalMatchedRows, returnedRows, and truncated before the rows",
|
|
13
|
+
"Scopes the result to pages affected among the evaluated crawl, not sitewide"
|
|
14
|
+
],
|
|
15
|
+
"files": []
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 2,
|
|
19
|
+
"prompt": "Run affected-urls against saved crawl crawl_example_20260710 for ruleId meta-description-missing, cap it at 50 rows, and show me the per-URL evidence.",
|
|
20
|
+
"expected_output": "Should run report id affected-urls with reportId crawl_example_20260710, ruleId meta-description-missing, and limit 50, via seo reports run affected-urls or seo_run_report, and should not pass url when a saved reportId is given. Should read selection totalMatchedRows, returnedRows, and truncated first, then each row rule, category, severity, and crawler evidence, then warnings and caveats. Should note limit bounds the returned rows and that truncated true means more matching rows exist than were returned.",
|
|
21
|
+
"assertions": [
|
|
22
|
+
"Runs report id affected-urls with reportId crawl_example_20260710 and ruleId meta-description-missing",
|
|
23
|
+
"Sets limit 50 and does not also pass url when a saved reportId is supplied",
|
|
24
|
+
"Reads selection totalMatchedRows, returnedRows, and truncated before the rows",
|
|
25
|
+
"Reads each row rule, category, severity, and crawler evidence in order",
|
|
26
|
+
"Notes limit bounds returned rows and truncated true means more rows exist",
|
|
27
|
+
"Uses only parameters defined in the report schema"
|
|
28
|
+
],
|
|
29
|
+
"files": []
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 3,
|
|
33
|
+
"prompt": "The run came back with truncated true. Just give me the exact total number of pages on the whole site that have this issue.",
|
|
34
|
+
"expected_output": "Should refuse to turn a truncated result into an exact sitewide count. Should explain that selection.truncated true means more matching rows exist than were returned, and that a capped or partial crawl only supports affected among evaluated pages, not a sitewide total. Should keep matched, returned, and truncated states distinct and recommend removing the cap, widening the crawl, or reading totalMatchedRows before quoting any number.",
|
|
35
|
+
"assertions": [
|
|
36
|
+
"Refuses to quote an exact sitewide count from a truncated run",
|
|
37
|
+
"States truncated true means more matching rows exist than were returned",
|
|
38
|
+
"States a capped crawl supports affected among evaluated pages, not sitewide",
|
|
39
|
+
"Keeps matched, returned, and truncated states distinct",
|
|
40
|
+
"Recommends reading totalMatchedRows or widening the crawl before quoting a number"
|
|
41
|
+
],
|
|
42
|
+
"files": []
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": 4,
|
|
46
|
+
"prompt": "I don't even know what this rule means or whether it's worth fixing before the others. Can you sort that out from here?",
|
|
47
|
+
"expected_output": "Should route to report id explain-crawl-issue for a plain explanation of what the rule checks and how to verify a fix, and to report id top-fixes when there are too many findings to review at once and the user needs a ranked queue. Should keep affected-urls as the per-URL evidence source that confirms which pages actually trigger the rule. Should not treat the rule as a search-engine penalty or promise a ranking effect.",
|
|
48
|
+
"assertions": [
|
|
49
|
+
"Routes to report id explain-crawl-issue to explain the rule",
|
|
50
|
+
"Routes to report id top-fixes when there are too many findings to triage",
|
|
51
|
+
"Keeps report id affected-urls as the per-URL evidence source",
|
|
52
|
+
"Does not describe the rule as a search-engine penalty or ranking guarantee"
|
|
53
|
+
],
|
|
54
|
+
"files": []
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"subject": "ai-readiness",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "Are our pages technically ready to show up in AI search? Not asking for guesses, just the technical blockers.",
|
|
7
|
+
"expected_output": "Should use report id ai-readiness to check access, indexability, snippet controls, content observations, bot policy, and agent resources as separate evidence, and should say it returns an evidence-only assessment rather than an aggregate score. Should follow the discovery flow, calling seo_describe_report with id ai-readiness, then seo_run_report. Should start with dataStatus, the crawl caveats, and each check's evaluated flag. Should explain technical eligibility is not selection.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Uses report id ai-readiness for the technical blocker assessment",
|
|
10
|
+
"Keeps access, indexability, snippet, structure, bot policy, and resources separate",
|
|
11
|
+
"States the report returns an evidence-only assessment, not an aggregate score",
|
|
12
|
+
"Calls seo_describe_report with id ai-readiness then seo_run_report",
|
|
13
|
+
"Starts with dataStatus, crawl caveats, and each check's evaluated flag",
|
|
14
|
+
"Explains technical eligibility is not selection"
|
|
15
|
+
],
|
|
16
|
+
"files": []
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": 2,
|
|
20
|
+
"prompt": "We already have a saved crawl from this morning, reportId crawl_example_20260710. Run the AI readiness check against it, don't recrawl.",
|
|
21
|
+
"expected_output": "Should run report id ai-readiness with reportId crawl_example_20260710 rather than passing url, since reusing a saved report makes the assessment comparable and avoids a fresh crawl. Should confirm isError is false before reading structuredContent. Should prioritize hard technical conflicts first, such as failed responses, blocked crawling, explicit non-indexing, or restrictive snippet directives that contradict publisher intent, then treat semantic structure as observations.",
|
|
22
|
+
"assertions": [
|
|
23
|
+
"Runs report id ai-readiness with reportId rather than url",
|
|
24
|
+
"Does not start a fresh crawl when a saved report exists",
|
|
25
|
+
"Confirms isError is false before reading structuredContent",
|
|
26
|
+
"Prioritizes failed responses, blocked crawling, non-indexing, and restrictive snippets first",
|
|
27
|
+
"Treats semantic structure and answerable content as observations, not requirements"
|
|
28
|
+
],
|
|
29
|
+
"files": []
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 3,
|
|
33
|
+
"prompt": "The readiness report is clean. So we're going to get cited in ChatGPT and show up in Google AI Overviews now, right?",
|
|
34
|
+
"expected_output": "Should refuse to translate a clean report into visibility outcomes. Should state that a clean report never supports claims about indexing, rankings, AI visibility, selection, citations, or traffic, and that those outcomes require separate evidence. Should reaffirm that technical eligibility is not selection. Should recommend verifying with a fresh crawl using the same scope and monitoring for actual referral or citation evidence rather than asserting it.",
|
|
35
|
+
"assertions": [
|
|
36
|
+
"Refuses to infer citations or AI Overview visibility from a clean report",
|
|
37
|
+
"States a clean report does not support indexing, ranking, selection, or citation claims",
|
|
38
|
+
"Reaffirms technical eligibility is not selection",
|
|
39
|
+
"Notes those outcomes require separate evidence",
|
|
40
|
+
"Recommends verifying with a fresh same-scope crawl and monitoring instead"
|
|
41
|
+
],
|
|
42
|
+
"files": []
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": 4,
|
|
46
|
+
"prompt": "Several checks came back as unknown and our JSON-LD is valid. Count unknown as passing and tell me valid JSON-LD means Google will show us as a rich result.",
|
|
47
|
+
"expected_output": "Should refuse both moves. Should explain unknown means the necessary evidence was unavailable and is not a failure or a pass, using each check's evaluated flag. Should state valid JSON-LD is syntax evidence only and is not proof of entity recognition or rich-result eligibility. Should keep unknown, pass, and fail states distinct and recommend gathering the missing evidence before scoring the checks.",
|
|
48
|
+
"assertions": [
|
|
49
|
+
"Refuses to count unknown checks as passing",
|
|
50
|
+
"States unknown means evidence was unavailable, not a pass or a failure",
|
|
51
|
+
"Uses each check's evaluated flag to classify the state",
|
|
52
|
+
"States valid JSON-LD is syntax evidence, not entity recognition or rich-result eligibility",
|
|
53
|
+
"Keeps unknown, pass, and fail states distinct"
|
|
54
|
+
],
|
|
55
|
+
"files": []
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": 5,
|
|
59
|
+
"prompt": "The bot access check passed on the homepage. That means GPTBot and other AI crawlers can reach every page on the site, correct?",
|
|
60
|
+
"expected_output": "Should not generalize entry-point bot access to the whole site. Should explain bot access describes the observed policy for the crawl entry point, not proven access to every URL. Should recommend checking robots rules and per-section directives before claiming sitewide access, and should keep the observation scoped to what was evaluated. Should not assert every page is reachable by AI crawlers.",
|
|
61
|
+
"assertions": [
|
|
62
|
+
"Refuses to generalize entry-point bot access to the whole site",
|
|
63
|
+
"States bot access describes the observed policy at the crawl entry point",
|
|
64
|
+
"Notes it does not prove access to every URL",
|
|
65
|
+
"Recommends checking robots rules and per-section directives before a sitewide claim",
|
|
66
|
+
"Keeps the observation scoped to what was evaluated"
|
|
67
|
+
],
|
|
68
|
+
"files": []
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": 6,
|
|
72
|
+
"prompt": "We don't have an llms.txt file. Flag that as a failure and then confirm our fixes worked without recrawling.",
|
|
73
|
+
"expected_output": "Should explain that an optional protocol file such as llms.txt can aid machine discovery but its absence is not a search defect, so it should not be flagged as a failure. Should say verifying fixes requires a fresh crawl using the same scope, so the change cannot be confirmed without recrawling. Should keep the missing file as an observation rather than a defect.",
|
|
74
|
+
"assertions": [
|
|
75
|
+
"Refuses to flag a missing llms.txt as a failure",
|
|
76
|
+
"States an optional protocol file's absence is not a search defect",
|
|
77
|
+
"Treats the missing file as an observation, not a defect",
|
|
78
|
+
"States verifying fixes needs a fresh crawl using the same scope",
|
|
79
|
+
"Does not confirm the fixes worked without recrawling"
|
|
80
|
+
],
|
|
81
|
+
"files": []
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"subject": "ai-referrals",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "How much traffic are we getting from ChatGPT and other AI tools?",
|
|
7
|
+
"expected_output": "Should use report id ai-referrals to find visits from known AI products in GA4 and a ranked, bounded set of landing pages that received them. Should follow the discovery flow: seo_list_reports with category ai-search, seo_describe_report with id ai-referrals, then seo_run_report with the required GA4 property. Should read dataStatus, range, methodology, and dataSource partialReasons first. Should clarify the report measures identifiable AI referral sessions in GA4, not citations or unclicked mentions inside AI answers.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Uses report id ai-referrals for measured AI referral traffic",
|
|
10
|
+
"Follows seo_list_reports, seo_describe_report, seo_run_report in order",
|
|
11
|
+
"Notes the report needs a GA4 property and reads GA4 referral sessions",
|
|
12
|
+
"Reads dataStatus, range, methodology, and dataSource partialReasons first",
|
|
13
|
+
"Clarifies it measures identifiable referral sessions, not citations or unclicked mentions"
|
|
14
|
+
],
|
|
15
|
+
"files": []
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 2,
|
|
19
|
+
"prompt": "Run the AI referral report for GA4 property 123456789 from 2026-04-01 to 2026-06-30 and give me the top 25 landing pages.",
|
|
20
|
+
"expected_output": "Should run report id ai-referrals with property 123456789, startDate 2026-04-01, endDate 2026-06-30, and resultLimit 25 to bound the ranked landing pages. Should note maxRows bounds retained GA4 rows and that limit is a legacy alias that must match maxRows if both are supplied. Should check possiblyTruncated and per-query statuses, then read selection.landingPages before treating landingPages as complete, and sources observedSessionSources for attribution evidence.",
|
|
21
|
+
"assertions": [
|
|
22
|
+
"Runs report id ai-referrals with property 123456789 and the given startDate and endDate",
|
|
23
|
+
"Sets resultLimit 25 to bound the ranked landing pages",
|
|
24
|
+
"Notes limit is a legacy alias that must match maxRows when both are set",
|
|
25
|
+
"Checks possiblyTruncated and per-query statuses",
|
|
26
|
+
"Reads selection.landingPages before treating landingPages as complete",
|
|
27
|
+
"Uses only parameters defined in the report schema"
|
|
28
|
+
],
|
|
29
|
+
"files": []
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 3,
|
|
33
|
+
"prompt": "The AI referral report came back empty. That proves no AI system has sent us anyone or cited us, right?",
|
|
34
|
+
"expected_output": "Should refuse to read an empty result as proof of absence. Should state an empty result is not proof that no AI system sent or cited the site, because the report only sees identifiable GA4 referral sessions, not citations or unclicked mentions inside AI answers. Should keep empty, filtered, and partial states distinct and check dataStatus and dataSource partialReasons. Should recommend comparing complete like-for-like GA4 periods rather than concluding absence.",
|
|
35
|
+
"assertions": [
|
|
36
|
+
"Refuses to treat the empty result as proof no AI sent or cited the site",
|
|
37
|
+
"States the report only sees identifiable GA4 referral sessions, not citations or unclicked mentions",
|
|
38
|
+
"Checks dataStatus and dataSource partialReasons before concluding",
|
|
39
|
+
"Keeps empty, filtered, and partial states distinct",
|
|
40
|
+
"Recommends comparing complete like-for-like GA4 periods"
|
|
41
|
+
],
|
|
42
|
+
"files": []
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": 4,
|
|
46
|
+
"prompt": "Add up totalUsers across all the landing-page rows to get our AI audience size, and count those sessions as conversions.",
|
|
47
|
+
"expected_output": "Should refuse both moves. Should state totalUsers is not additive and must not be reconstructed by summing rows. Should state referral sessions did not necessarily cause conversions and are not all AI traffic. Should keep sessions, users, and conversions as distinct measures and point to sources observedSessionSources as attribution evidence rather than a conversion or audience claim.",
|
|
48
|
+
"assertions": [
|
|
49
|
+
"Refuses to sum totalUsers across rows into an audience size",
|
|
50
|
+
"States totalUsers is not additive and must not be reconstructed by summing rows",
|
|
51
|
+
"States referral sessions did not necessarily cause conversions and are not all AI traffic",
|
|
52
|
+
"Keeps sessions, users, and conversions as distinct measures",
|
|
53
|
+
"Points to sources observedSessionSources as attribution evidence"
|
|
54
|
+
],
|
|
55
|
+
"files": []
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": 5,
|
|
59
|
+
"prompt": "The run shows partialReasons and possiblyTruncated true, but I only see 25 pages. That is the full list of AI landing pages, correct?",
|
|
60
|
+
"expected_output": "Should not treat the 25 rows as the complete set. Should explain resultLimit bounds the ranked landing pages to 25 by default, and that possiblyTruncated and dataSource partialReasons mean the source is capped or partial. Should read selection.landingPages before treating landingPages as complete. Should recommend raising resultLimit or maxRows or resolving the partialReasons before calling it the full list. Should route to seo-to-ai-query for monitoring prompts if the user wants ongoing tracking.",
|
|
61
|
+
"assertions": [
|
|
62
|
+
"Refuses to treat the 25 returned rows as the complete list",
|
|
63
|
+
"States resultLimit bounds the ranked landing pages, defaulting to 25",
|
|
64
|
+
"Cites possiblyTruncated and dataSource partialReasons as capped or partial evidence",
|
|
65
|
+
"Reads selection.landingPages before treating landingPages as complete",
|
|
66
|
+
"Recommends raising resultLimit or maxRows or resolving partialReasons first"
|
|
67
|
+
],
|
|
68
|
+
"files": []
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"subject": "ai-search-scorecard",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "Give me a single score for how ready our site is for AI search.",
|
|
7
|
+
"expected_output": "Should use report id ai-search-scorecard to score AI-search technical readiness 0-100 from one crawl using this tool's own weighted checks, with observed evidence kept separate. Should follow the discovery flow: seo_list_reports with category ai-search, seo_describe_report with id ai-search-scorecard, then seo_run_report. Should read partial, crawlComplete, and excluded before the number, and note score is null when no check had known evidence. Should state up front the number is a heuristic over this tool's own checks, not a Google or AI-engine requirement or ranking prediction.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Uses report id ai-search-scorecard for the scored summary",
|
|
10
|
+
"Follows seo_list_reports, seo_describe_report, seo_run_report in order",
|
|
11
|
+
"States the score is a heuristic over this tool's own checks, not a Google or AI-engine requirement",
|
|
12
|
+
"Reads partial, crawlComplete, and excluded before the number",
|
|
13
|
+
"Notes score is null when no check had known evidence"
|
|
14
|
+
],
|
|
15
|
+
"files": []
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 2,
|
|
19
|
+
"prompt": "Score AI search readiness from our saved crawl reportId crawl_example_20260710 and show me the per-check weights.",
|
|
20
|
+
"expected_output": "Should run report id ai-search-scorecard with reportId crawl_example_20260710 rather than starting a fresh crawl, so the score reuses the saved evidence. Should read partial, crawlComplete, and excluded before the score, then each check status, observed evidence, and finding, then methodology weights, statusCredit, and formula. Should treat the number as a heuristic and note unknown checks are excluded from the score.",
|
|
21
|
+
"assertions": [
|
|
22
|
+
"Runs report id ai-search-scorecard with reportId rather than recrawling",
|
|
23
|
+
"Reads partial, crawlComplete, and excluded before the score",
|
|
24
|
+
"Reads each check status, observed evidence, and finding",
|
|
25
|
+
"Reads methodology weights, statusCredit, and formula",
|
|
26
|
+
"Notes unknown checks are excluded from the score"
|
|
27
|
+
],
|
|
28
|
+
"files": []
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": 3,
|
|
32
|
+
"prompt": "We scored 92 out of 100, so we are eligible for Google AI Overviews and this will rank us higher and get us cited.",
|
|
33
|
+
"expected_output": "Should refuse to convert the score into an eligibility verdict, ranking prediction, or proof of citations. Should state the score is a heuristic over this tool's own checks, not a Google or AI-engine requirement, eligibility verdict, or ranking prediction, and that it does not prove citations. Should recommend re-crawling at the same scope after a change and confirming the per-check evidence moved, rather than asserting an outcome. Should point to ai-readiness for the per-page evidence behind the number.",
|
|
34
|
+
"assertions": [
|
|
35
|
+
"Refuses to read the score as eligibility, a ranking prediction, or proof of citations",
|
|
36
|
+
"States the score is a heuristic over this tool's own checks, not a Google or AI-engine requirement or eligibility verdict",
|
|
37
|
+
"Notes the score does not prove citations",
|
|
38
|
+
"Recommends re-crawling at the same scope and confirming per-check evidence moved",
|
|
39
|
+
"Points to report id ai-readiness for the per-page evidence"
|
|
40
|
+
],
|
|
41
|
+
"files": []
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": 4,
|
|
45
|
+
"prompt": "Count the unknown checks as fails and flag the blocked GPTBot token as a defect that is dragging our score down.",
|
|
46
|
+
"expected_output": "Should refuse both moves. Should state unknown checks are excluded from the score and are never failures, using the excluded set and each check status. Should state a blocked AI crawler token can be an intentional publisher choice and should stay an observation, not an automatic defect. Should keep unknown, pass, warn, and fail states distinct and note score is null when no check had known evidence.",
|
|
47
|
+
"assertions": [
|
|
48
|
+
"Refuses to count unknown checks as failures",
|
|
49
|
+
"States unknown checks are excluded from the score and are never failures",
|
|
50
|
+
"States a blocked AI crawler token can be an intentional publisher choice",
|
|
51
|
+
"Treats the blocked token as an observation, not an automatic defect",
|
|
52
|
+
"Keeps unknown, pass, warn, and fail states distinct"
|
|
53
|
+
],
|
|
54
|
+
"files": []
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": 5,
|
|
58
|
+
"prompt": "The run shows partial true and several excluded checks, but the score is 80. That is our final AI readiness grade.",
|
|
59
|
+
"expected_output": "Should not treat 80 as a final grade. Should read partial, crawlComplete, and excluded before the number and explain a partial run and excluded checks mean the evidence is incomplete. Should state the score is a heuristic over this tool's own checks and unknown checks are excluded, not failed. Should recommend re-crawling at the same scope so more checks have known evidence before relying on the number. Should route to geo-gaps for affected URLs if the user wants per-page fixes.",
|
|
60
|
+
"assertions": [
|
|
61
|
+
"Refuses to treat the score as a final grade",
|
|
62
|
+
"Reads partial, crawlComplete, and excluded before the number",
|
|
63
|
+
"States the score is a heuristic and unknown checks are excluded, not failed",
|
|
64
|
+
"Recommends re-crawling at the same scope before relying on the number",
|
|
65
|
+
"Routes to report id geo-gaps for per-page affected URLs"
|
|
66
|
+
],
|
|
67
|
+
"files": []
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"subject": "audit-page",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "Before I rewrite our pricing page can you give it a technical once-over? It is https://example.com/pricing",
|
|
7
|
+
"expected_output": "Should route to report id audit-page as the single-page technical review. Should follow the discovery flow: seo_list_reports with category reporting, seo_describe_report with id audit-page, then seo_run_report with url set to the pricing URL and only schema-valid params. Should read fetchDiagnostics, warnings, and the final fetched URL first, then the observed page fields and structured-data findings, then issues and recommendations. Should keep observed evidence separate from review advice and not turn one page into a sitewide conclusion.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Uses report id audit-page for the single-page technical review",
|
|
10
|
+
"Follows seo_list_reports, seo_describe_report, seo_run_report in order",
|
|
11
|
+
"Passes url and only parameters defined in the audit-page schema",
|
|
12
|
+
"Reads fetchDiagnostics, warnings, and the final fetched URL before the findings",
|
|
13
|
+
"Separates observed page fields from issues and recommendations",
|
|
14
|
+
"Does not extend one page into a sitewide conclusion"
|
|
15
|
+
],
|
|
16
|
+
"files": []
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": 2,
|
|
20
|
+
"prompt": "Agent task: audit https://example.com/blog/guide with JavaScript rendering enabled and a fresh fetch that bypasses any cache. Return the observed evidence.",
|
|
21
|
+
"expected_output": "Should run report id audit-page with url https://example.com/blog/guide, js true, and refresh true, via seo reports run audit-page or seo_run_report. Should use only url, site, js, and refresh from the schema. Should note js changes final-page extraction and refresh forces a fresh fetch rather than a cached result. Should read fetchDiagnostics, warnings, and the final fetched URL first, then the observed fields and structured-data findings, then issues.",
|
|
22
|
+
"assertions": [
|
|
23
|
+
"Runs report id audit-page with js true and refresh true",
|
|
24
|
+
"Uses only url, site, js, and refresh from the schema",
|
|
25
|
+
"Notes refresh forces a fresh fetch rather than a cached result",
|
|
26
|
+
"Reads fetchDiagnostics, warnings, and the final fetched URL first",
|
|
27
|
+
"Reports observed page fields and structured-data findings before recommendations"
|
|
28
|
+
],
|
|
29
|
+
"files": []
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 3,
|
|
33
|
+
"prompt": "The audit says our title is 640 pixels wide. That means Google is cutting it off and it is costing us rankings. Fix it to recover the traffic.",
|
|
34
|
+
"expected_output": "Should refuse the claim. Title pixel width is a review estimator, not a Google limit, and the audit cannot claim truncation caused a ranking or traffic loss. One page cannot support a sitewide conclusion. Should treat the finding as a review prompt, and recommend verifying by re-fetching the URL after the edit and confirming the observed field changed. Should not promise recovered traffic.",
|
|
35
|
+
"assertions": [
|
|
36
|
+
"States title pixel width is a review estimator, not a Google limit",
|
|
37
|
+
"Refuses to claim the title width caused a ranking or traffic loss",
|
|
38
|
+
"Does not promise recovered traffic from a title change",
|
|
39
|
+
"Recommends verifying by re-fetching the URL after the edit",
|
|
40
|
+
"Keeps the finding a review prompt rather than a proven defect"
|
|
41
|
+
],
|
|
42
|
+
"files": []
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": 4,
|
|
46
|
+
"prompt": "Good, the fetched HTML no longer has the noindex tag, so Google has definitely reindexed the page. We are done.",
|
|
47
|
+
"expected_output": "Should refuse the conclusion. The fetched version is not proof that Google indexed that version, and the audit is one fetch from the machine and time that ran it. Should keep the observed absence of noindex distinct from an index verdict and recommend confirming with Google URL Inspection, for example report id index-watch, before concluding the page is indexed.",
|
|
48
|
+
"assertions": [
|
|
49
|
+
"States the fetched version is not proof Google indexed that version",
|
|
50
|
+
"Refuses to conclude the page is reindexed from the audit alone",
|
|
51
|
+
"Keeps the observed page state distinct from an index verdict",
|
|
52
|
+
"Recommends confirming with URL Inspection such as report id index-watch"
|
|
53
|
+
],
|
|
54
|
+
"files": []
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"subject": "audit-urls",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "We're about to ship a redesign of our five main landing pages. Can you check just those pages without crawling the whole site?",
|
|
7
|
+
"expected_output": "Should use report id audit-urls to run consistent technical checks over a supplied list of URLs without discovering the rest of the site. Should follow the discovery flow: seo_describe_report with id audit-urls, then seo_run_report with the required urls array. Should read requested versus attempted, fetched, failed, retained, and omitted counts first, then response evidence and issue summaries, then requestEvidenceStatus, warnings, and caveats. Should frame it as a repeatable pre-release check, not full site coverage.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Uses report id audit-urls to check the supplied list without a full crawl",
|
|
10
|
+
"Passes the required urls array with the five landing pages",
|
|
11
|
+
"Reads requested versus attempted, fetched, failed, retained, and omitted counts first",
|
|
12
|
+
"Reads response evidence and issue summaries before concluding",
|
|
13
|
+
"Frames the result as a bounded pre-release check, not complete site coverage"
|
|
14
|
+
],
|
|
15
|
+
"files": []
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 2,
|
|
19
|
+
"prompt": "Run audit-urls on https://example.com/pricing and https://example.com/features with includeIssues true and save the report so I can compare after we deploy.",
|
|
20
|
+
"expected_output": "Should run report id audit-urls with urls set to the two supplied pages, includeIssues true, and saveReport true, via seo reports run audit-urls or seo_run_report. Should read requested versus attempted, fetched, failed, retained, and omitted counts, then per-URL response evidence and issue summaries, then requestEvidenceStatus. Should note that re-running the identical URL list after deployment is the verification, and that a URL dropped from the request is not a verified fix.",
|
|
21
|
+
"assertions": [
|
|
22
|
+
"Runs report id audit-urls with the two supplied URLs in the urls array",
|
|
23
|
+
"Sets includeIssues true and saveReport true as requested",
|
|
24
|
+
"Reads requested, attempted, fetched, failed, retained, and omitted counts",
|
|
25
|
+
"Notes verification is re-running the identical URL list after deployment",
|
|
26
|
+
"States a URL dropped from the request is not a verified fix",
|
|
27
|
+
"Uses only parameters defined in the report schema"
|
|
28
|
+
],
|
|
29
|
+
"files": []
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 3,
|
|
33
|
+
"prompt": "These five pages passed. That proves the template is fine, so every page built from it is fine too, right?",
|
|
34
|
+
"expected_output": "Should decline to generalize a passing sample to every page built from the template. Should explain that a successful sample does not prove the same template works everywhere and that a bounded audit is never complete site coverage. Should recommend either auditing more URLs from the template or running report id site-crawl to discover pages, and should keep the passing sample scoped to the URLs actually checked.",
|
|
35
|
+
"assertions": [
|
|
36
|
+
"Refuses to generalize a passing sample to every template page",
|
|
37
|
+
"States a successful sample does not prove the template works everywhere",
|
|
38
|
+
"States a bounded audit is never complete site coverage",
|
|
39
|
+
"Recommends auditing more URLs or running report id site-crawl to discover pages",
|
|
40
|
+
"Keeps the pass scoped to the URLs actually checked"
|
|
41
|
+
],
|
|
42
|
+
"files": []
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": 4,
|
|
46
|
+
"prompt": "One of the URLs failed to fetch. Log it as a broken page and add it to the fix list.",
|
|
47
|
+
"expected_output": "Should not turn a single failed fetch into a confirmed broken page. Should read the failed count and requestEvidenceStatus and explain that a failed request may be transient network rather than a persistent defect. Should recommend retrying the identical URL, or inspecting it directly with report id audit-page, before recording it as broken, and should keep failed and confirmed-defect states distinct.",
|
|
48
|
+
"assertions": [
|
|
49
|
+
"Refuses to log a single failed fetch as a confirmed broken page",
|
|
50
|
+
"Reads the failed count and requestEvidenceStatus",
|
|
51
|
+
"States a failed request may be transient network, not a persistent defect",
|
|
52
|
+
"Recommends retrying, or inspecting with report id audit-page, before recording it",
|
|
53
|
+
"Keeps failed and confirmed-defect states distinct"
|
|
54
|
+
],
|
|
55
|
+
"files": []
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"subject": "cannibalisation",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "Are our pages competing against each other for the same searches?",
|
|
7
|
+
"expected_output": "Should use report id cannibalisation to find search queries that appear across several URLs and decide whether the overlap needs attention. Should follow the discovery flow: seo_list_reports with category opportunities, seo_describe_report with id cannibalisation, then seo_run_report with the required site. Should read dataStatus and both source.pageExposure and source.propertyDemand with their validation counts and completeness first. Should clarify URL overlap is a review candidate, not proven harmful cannibalisation.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Uses report id cannibalisation for the query and URL overlap review",
|
|
10
|
+
"Follows seo_list_reports, seo_describe_report, seo_run_report in order",
|
|
11
|
+
"Reads dataStatus and both source.pageExposure and source.propertyDemand first",
|
|
12
|
+
"Reads the validation counts and completeness for each source",
|
|
13
|
+
"States URL overlap is a review candidate, not proven harmful cannibalisation"
|
|
14
|
+
],
|
|
15
|
+
"files": []
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 2,
|
|
19
|
+
"prompt": "Run the overlap report for sc-domain:example.com over 90 days, top 30 queries, at least 100 impressions.",
|
|
20
|
+
"expected_output": "Should run report id cannibalisation with site sc-domain:example.com, days 90, limit 30, and minImpressions 100. Should read selection to see why groups were excluded or suppressed, then per-page clicks, impressions, position, impression share, reviewContext, and HHI, then priority.score and suggestedOwnerUrl. Should use only parameters defined in the schema.",
|
|
21
|
+
"assertions": [
|
|
22
|
+
"Runs report id cannibalisation with days 90, limit 30, and minImpressions 100",
|
|
23
|
+
"Reads selection to see why groups were excluded or suppressed",
|
|
24
|
+
"Reads per-page clicks, impressions, position, impression share, reviewContext, and HHI",
|
|
25
|
+
"Reads priority.score and suggestedOwnerUrl",
|
|
26
|
+
"Uses only parameters defined in the report schema"
|
|
27
|
+
],
|
|
28
|
+
"files": []
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": 3,
|
|
32
|
+
"prompt": "Just redirect every overlapping URL into the suggestedOwnerUrl to fix the cannibalisation.",
|
|
33
|
+
"expected_output": "Should decline to consolidate automatically on the mechanical pick. Should state suggestedOwnerUrl is a low-confidence mechanical pick that requires intent review, and that URL overlap is a review candidate, not proven harmful cannibalisation, so not every multi-URL query is a problem. Should recommend comparing intent and live SERPs for the overlapping URLs before consolidating anything.",
|
|
34
|
+
"assertions": [
|
|
35
|
+
"Declines to redirect every overlapping URL automatically",
|
|
36
|
+
"States suggestedOwnerUrl is a low-confidence mechanical pick that requires intent review",
|
|
37
|
+
"States URL overlap is a review candidate, not proven harmful cannibalisation",
|
|
38
|
+
"Notes not every multi-URL query is a problem",
|
|
39
|
+
"Recommends comparing intent and live SERPs before consolidating"
|
|
40
|
+
],
|
|
41
|
+
"files": []
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": 4,
|
|
45
|
+
"prompt": "priority.score is the click lift from merging, and the empty result means we have no cannibalisation anywhere.",
|
|
46
|
+
"expected_output": "Should refuse both readings. Should state priority.score is a heuristic, not estimated click lift, and that a partial source cannot support an all-clear. Should read the validation counts and completeness on source.pageExposure and source.propertyDemand and the selection suppressions before concluding, keeping partial, suppressed, and complete states distinct.",
|
|
47
|
+
"assertions": [
|
|
48
|
+
"Refuses to read priority.score as estimated click lift",
|
|
49
|
+
"States priority.score is a heuristic",
|
|
50
|
+
"States a partial source cannot support an all-clear",
|
|
51
|
+
"Reads validation counts and completeness and selection suppressions before concluding",
|
|
52
|
+
"Keeps partial, suppressed, and complete states distinct"
|
|
53
|
+
],
|
|
54
|
+
"files": []
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|