feat(docs): complete SEO/GEO daily reporting - #2541
Conversation
Walkthrough本次变更将 SEO 监控升级为双站点、三段 DataForSEO 的 SEO/GEO 日报系统,加入执行状态、IndexNow 证据、GSC/GA4 趋势、技术 SEO 探测、统一报告与严格契约校验喵。 ChangesSEO/GEO 日报系统
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
https://github.com/Project-N-E-K-O/N.E.K.O/blob/994906f4895edd8eae9688c64734f2edcfdf4b3c/docs/scripts/seo-monitoring/collectors.mjs#L781-L784
Include content checks in the technical status
When all five probe URLs return successfully but the page lacks a canonical or GA4 ID, robots.txt omits the sitemap, or the sitemap is empty, this expression still marks the technical source ok. sourceBlockers relies only on that top-level status, and the production workflow runs assert-report --level daily, which does not call validateTechnical, so the daily report can claim complete sources and generate growth actions despite failed technical checks. Derive status from the content-level invariants as well as the HTTP/crawler checks.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Greptile Summary本次 PR 完成 SEO/GEO 日报流水线升级喵。
Confidence Score: 5/5当前修订看起来可以安全合并,未发现符合本次跟进审查范围的新问题或仍未解决的既有问题喵。 先前的 dry-run 或非 main 基线污染路径已由 paid、main 和成功门禁阻断,而历史报告比较也会验证时间顺序及分段契约;没有阻塞性故障仍然存在喵。
|
| Filename | Overview |
|---|---|
| .github/workflows/dataforseo.yml | 付费基线现由严格日报门禁、paid 模式和 main 分支条件共同保护,先前两条基线污染问题均未在当前代码中继续存在喵。 |
| docs/scripts/seo-monitoring/report.mjs | 统一报告增加多分段证据与比较逻辑,并拒绝时间顺序或比较契约不兼容的历史报告喵。 |
| docs/scripts/seo-monitoring/assert-report.mjs | 新增日报完整性断言,确保核心排名、分析、IndexNow 与技术证据缺失时失败关闭喵。 |
| docs/scripts/dataforseo/run-status.mjs | 新增分段执行状态清单,使失败、部分完成和 dry-run 状态可被统一报告及门禁明确识别喵。 |
| .github/workflows/docs.yml | IndexNow 部署步骤现在生成并上传可供日报消费的执行证据喵。 |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Schedule[Scheduled or manual run] --> Tests[Tests and dry-run plans]
Tests --> Collect[Collect three ranking segments]
Collect --> Evidence[Combine GSC, GA4, IndexNow and technical evidence]
Evidence --> Report[Generate JSON and Markdown report]
Report --> Gate{Strict daily contract passes?}
Gate -->|No| Diagnostic[Upload diagnostic artifact only]
Gate -->|Yes, paid on main| Baseline[Publish verified comparison baseline]
Baseline --> Next[Next run loads latest main baseline]
Reviews (7): Last reviewed commit: "fix(docs): harden SEO evidence validatio..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (4)
docs/scripts/seo-monitoring/report.mjs (2)
1206-1208: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
aiComparison缺失时会直接炸掉喵。上面用
report.aiCitationFrequency?.comparison做了可选链,这里却在 else 分支裸解引用aiComparison.comparableQueries;一旦渲染外部构造的旧 artifact 就是 TypeError。建议把?.status === 'not_run'改成!aiComparison || aiComparison.status === 'not_run',保持和topTenChangeLine一致的防御姿势。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/scripts/seo-monitoring/report.mjs` around lines 1206 - 1208, Update the aiComparison status condition in the comparison report template to handle a missing aiComparison before accessing its fields: treat !aiComparison the same as status === 'not_run', while preserving the existing comparison formatting for present, runnable data.
49-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value这个三元两边一模一样,白写了喵~
report?.status === 'unavailable' ? 'unknown' : 'unknown'恒为'unknown',直接写常量更诚实。♻️ 简化
- runStatus: report?.status === 'unavailable' ? 'unknown' : 'unknown', - rankingStatus: report?.status === 'unavailable' ? 'unknown' : 'unknown', + runStatus: 'unknown', + rankingStatus: 'unknown',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/scripts/seo-monitoring/report.mjs` around lines 49 - 58, 简化 inferExecution 中 runStatus 和 rankingStatus 的赋值,移除恒等三元表达式并直接使用 'unknown' 常量;保留其他状态字段和 reason 的现有行为不变。docs/seo/monitoring.config.json (1)
33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value两站的
aiReferralRegex逐字重复,将来一定会漂移喵。建议抽成顶层共享字段(例如
defaults.ga4.aiReferralRegex),站点只在需要时覆盖,免得改一处忘一处。Also applies to: 62-62
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/seo/monitoring.config.json` at line 33, 将重复定义的 aiReferralRegex 提取到顶层共享配置字段(例如 defaults.ga4.aiReferralRegex),并让两个站点配置通过继承或引用使用该默认值;仅在站点确实需要差异时覆盖,确保两处当前行为保持一致且后续只需维护一份正则。docs/scripts/seo-monitoring/assert-report.mjs (1)
424-430: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winGSC 的
dataThrough没有新鲜度校验,陈旧窗口也算 COMPLETE 喵。GA4 那边有
dataThrough === shiftDate(report.reportDate, -1),GSC 只校验窗口自洽性;如果 API 卡在两周前的 finalized 日,日报仍然全绿。建议加一条上界(例如reportDate往前 N 天内),把"动态 finalized 最新日"真正锁住。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/scripts/seo-monitoring/assert-report.mjs` around lines 424 - 430, Update the GSC validation around dataThrough and reportDate to enforce freshness, not only internal window consistency. Require dataThrough to be within the supported finalized-day lag from reportDate, reusing the existing date-shifting logic and failure-reporting pattern used for GA4, while preserving the current equality check against windows.latest.endDate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/dataforseo.yml:
- Around line 141-147: Update the artifact query used by the previous unified
report lookup around record to apply the fixed artifact name through the API’s
name filter instead of fetching only the first page and filtering with
startswith. Make the same change to the dataforseo-cn-report lookup near the
corresponding ranking artifact query, preserving startswith only if that
artifact name includes a run-specific suffix.
In `@docs/scripts/dataforseo/run-status.mjs`:
- Around line 96-104: Update the fallback branch of keywordMetricsStatus in the
status construction so that when expectsKeywordMetrics is true,
report?.keywordMetrics is not an array, and neither failed nor partial applies,
it returns a conservative failure state instead of 'complete'. Preserve the
existing 'failed', 'partial', and valid-array completion behavior.
In `@docs/scripts/seo-monitoring/cli.mjs`:
- Around line 74-82: Update readOptionalJson so missing or unconfigured paths
continue returning the value normalizedIndexNow converts to notRun, while read
or JSON parsing failures return an unavailable result that remains unavailable.
Preserve the existing error context in failure messages and ensure
normalizedIndexNow consequently includes a blocker for the affected IndexNow
evidence.
In `@docs/scripts/seo-monitoring/collectors.mjs`:
- Around line 678-685: Protect URL resolution in the script/link collection
within collectTechnicalSeo so one malformed src or href cannot abort the entire
evidence collection. Safely parse each match using the existing base URL,
discard invalid or unresolvable entries, and preserve the origin filtering,
deduplication, and ten-URL limit for valid URLs.
In `@docs/scripts/seo-monitoring/report.mjs`:
- Around line 77-84: Update docs/scripts/seo-monitoring/report.mjs:77-84 by
changing hasMetrics to require an array with length greater than zero, so empty
keywordMetrics produces not_run. Update
docs/scripts/seo-monitoring/assert-report.mjs:269-274 so searchVolumeStatus ===
'complete' also validates that the corresponding Volume data is non-empty,
optionally cross-checking demandBySegment.reportedQueries > 0; do not rely on
the status string alone.
In `@docs/seo/reports/2026-07-28-integrated-skill-report.md`:
- Around line 79-83: Update the header of the report to state that it was
generated before the renderer changes, or regenerate the report with the current
renderMarkdown output. Ensure the audited sample no longer appears to define the
current field or heading contract, including the outdated DataForSEO AIO and GSC
wording.
In `@docs/seo/reports/2026-07-28-preflight.md`:
- Around line 8-10: 合并该 Markdown 文件中由空行分隔的两个引用块,移除引用块之间的空行并保持两行内容连续使用引用标记,以消除
MD028 告警。
In `@docs/seo/reports/README.md`:
- Line 31: Replace the relative documentation links with VitePress-compatible
absolute paths: in docs/seo/reports/README.md lines 31-31 and
docs/seo/reports/2026-07-29-pr-validation.md lines 238-238, use
/contributing/seo-geo-daily-monitoring; in docs/seo/reports/README.md line
12-12, replace the relative monitoring.config.json link with a repository GitHub
absolute URL or remove the link while retaining the inline code.
---
Nitpick comments:
In `@docs/scripts/seo-monitoring/assert-report.mjs`:
- Around line 424-430: Update the GSC validation around dataThrough and
reportDate to enforce freshness, not only internal window consistency. Require
dataThrough to be within the supported finalized-day lag from reportDate,
reusing the existing date-shifting logic and failure-reporting pattern used for
GA4, while preserving the current equality check against windows.latest.endDate.
In `@docs/scripts/seo-monitoring/report.mjs`:
- Around line 1206-1208: Update the aiComparison status condition in the
comparison report template to handle a missing aiComparison before accessing its
fields: treat !aiComparison the same as status === 'not_run', while preserving
the existing comparison formatting for present, runnable data.
- Around line 49-58: 简化 inferExecution 中 runStatus 和 rankingStatus
的赋值,移除恒等三元表达式并直接使用 'unknown' 常量;保留其他状态字段和 reason 的现有行为不变。
In `@docs/seo/monitoring.config.json`:
- Line 33: 将重复定义的 aiReferralRegex 提取到顶层共享配置字段(例如
defaults.ga4.aiReferralRegex),并让两个站点配置通过继承或引用使用该默认值;仅在站点确实需要差异时覆盖,确保两处当前行为保持一致且后续只需维护一份正则。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 47a4dc2a-0a6f-48e3-89d4-134a727fdd38
📒 Files selected for processing (33)
.github/workflows/dataforseo.yml.github/workflows/docs.ymldocs/.vitepress/indexing-policy.mjsdocs/.vitepress/theme/analytics-consent.mjsdocs/contributing/dataforseo-seo-monitoring.mddocs/contributing/seo-geo-daily-monitoring.mddocs/scripts/analytics-consent.test.mjsdocs/scripts/dataforseo/cli.mjsdocs/scripts/dataforseo/config.test.mjsdocs/scripts/dataforseo/report.mjsdocs/scripts/dataforseo/report.test.mjsdocs/scripts/dataforseo/run-status.mjsdocs/scripts/dataforseo/run-status.test.mjsdocs/scripts/dataforseo/workflow.test.mjsdocs/scripts/seo-monitoring/assert-report.mjsdocs/scripts/seo-monitoring/assert-report.test.mjsdocs/scripts/seo-monitoring/cli.mjsdocs/scripts/seo-monitoring/collectors.mjsdocs/scripts/seo-monitoring/collectors.test.mjsdocs/scripts/seo-monitoring/report.mjsdocs/scripts/seo-monitoring/report.test.mjsdocs/scripts/submit-indexnow.mjsdocs/scripts/submit-indexnow.test.mjsdocs/seo/dataforseo.cn.config.jsondocs/seo/dataforseo.config.jsondocs/seo/dataforseo.online-zh.config.jsondocs/seo/monitoring.config.jsondocs/seo/reports/2026-07-28-integrated-skill-report.mddocs/seo/reports/2026-07-28-preflight.mddocs/seo/reports/2026-07-29-pr-validation.mddocs/seo/reports/README.mddocs/seo/reports/SKILL-INTEGRATION.mddocs/seo/reports/TEMPLATE.md
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
|
Review follow-up completed.
Validation: 101/101 tests pass; VitePress build and SEO checks pass with 299 indexable pages, 37 noindex pages, and 1140 hreflang links; YAML, Markdown path, and diff checks pass. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/seo/reports/2026-07-29-pr-validation.md`:
- Line 108: 将该报告中的 GSC 陈旧判定说明改为可执行的逐站点规则,明确列出 .online 与 .cn
各自使用的天数阈值;若阈值由配置计算,则说明对应配置来源及计算方式,避免保留无法判定的“1–4 天”范围。
- Around line 68-74: 更新 DataForSEO 测试组的表格行,明确“42”代表整组 DataForSEO
测试,而非仅对应“三段配置”。拆分或重写“数量”和“覆盖重点”展示,使三段配置、Volume/KD、depth
100、AIO、成本、重试、workflow/artifact 及空证据 fail-closed 等覆盖范围与总数的对应关系清晰可读。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d1852f9c-e96d-4bef-b8e2-a14b96a4caea
📒 Files selected for processing (10)
docs/contributing/seo-geo-daily-monitoring.mddocs/scripts/dataforseo/config.test.mjsdocs/scripts/seo-monitoring/assert-report.mjsdocs/scripts/seo-monitoring/assert-report.test.mjsdocs/scripts/seo-monitoring/cli.mjsdocs/scripts/seo-monitoring/cli.test.mjsdocs/scripts/seo-monitoring/report.mjsdocs/scripts/seo-monitoring/report.test.mjsdocs/seo/monitoring.config.jsondocs/seo/reports/2026-07-29-pr-validation.md
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
🚧 Files skipped from review as they are similar to previous changes (7)
- docs/scripts/seo-monitoring/cli.mjs
- docs/seo/monitoring.config.json
- docs/scripts/seo-monitoring/report.test.mjs
- docs/scripts/dataforseo/config.test.mjs
- docs/scripts/seo-monitoring/assert-report.test.mjs
- docs/contributing/seo-geo-daily-monitoring.md
- docs/scripts/seo-monitoring/report.mjs
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6cc353b733
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/dataforseo.yml (1)
156-207: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
generated_at为空时会让 node 直接抛异常崩溃喵,建议提前判空。第 191 行把
jq -r '.generatedAt // empty'取到的$generated_at(可能是空字符串)直接丢给new Date(process.argv[1])再喂给Intl.DateTimeFormat().format(...)。传入非法日期时format()会抛RangeError: Invalid time value,而这个 bash 步骤默认在-e下运行,命令替换赋值失败会让脚本立刻中断退出(不会执行到后面的比较和 echo)。多亏这一步有continue-on-error: true,最终还是会安全回退到本地采集,没有真正的数据风险,但 Actions 日志里会出现一个莫名其妙的未捕获异常,而不是「今天日期不匹配,回退本地采集」这种清晰提示,排查起来会让人摸不着头脑喵。🐾 建议加个提前判空
generated_at="$(jq -r '.generatedAt // empty' "$cn_status")" + if [[ -z "$generated_at" ]]; then + echo "The .cn artifact has no generatedAt timestamp; this run will collect the fallback locally." + exit 0 + fi artifact_date="$(node -e 'console.log(new Intl.DateTimeFormat("en-CA", {timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit"}).format(new Date(process.argv[1])))' "$generated_at")"其余这段 cn 权威产物复用校验逻辑(服务端 name= 精确过滤、
main分支约束、拒绝过期证据)都已经按之前的评论修好了,逻辑本身没毛病喵,只是这一处日期解析可以更稳一点点~🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/dataforseo.yml around lines 156 - 207, Guard the generated_at handling before the artifact_date Node conversion: when generated_at is empty, emit the existing incomplete/invalid-artifact fallback message and exit successfully before invoking Intl.DateTimeFormat. Keep the date conversion and today comparison unchanged for non-empty timestamps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.github/workflows/dataforseo.yml:
- Around line 156-207: Guard the generated_at handling before the artifact_date
Node conversion: when generated_at is empty, emit the existing
incomplete/invalid-artifact fallback message and exit successfully before
invoking Intl.DateTimeFormat. Keep the date conversion and today comparison
unchanged for non-empty timestamps.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7c689b86-a33b-4e68-be5c-189091f8d340
📒 Files selected for processing (8)
.github/workflows/dataforseo.ymldocs/scripts/dataforseo/run-status.mjsdocs/scripts/dataforseo/run-status.test.mjsdocs/scripts/dataforseo/workflow.test.mjsdocs/scripts/seo-monitoring/assert-report.mjsdocs/scripts/seo-monitoring/assert-report.test.mjsdocs/scripts/seo-monitoring/collectors.mjsdocs/scripts/seo-monitoring/collectors.test.mjs
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
wehos
left a comment
There was a problem hiding this comment.
说不上来 但真感觉挺奇怪的 尤其seo/reports里那一滩东西,以及dataforseo.config.json都是啥玩意儿?
Summary
Production dependency
The repository already has the DataForSEO, Google service-account, GA4 and GSC settings. A fine-grained
SEO_REPORTS_TOKENwith Actions read access to the privateN.E.K.O.OfficialWebsiterepository is still required to reuse its authoritative CN ranking and IndexNow artifacts. Without it, ranking falls back locally but the strict daily contract correctly fails on missing private IndexNow evidence.Verification
cd docs && npm test: 92/92 passedcd docs && npm run build:check: passed (299 indexable, 37 noindex, 1140 hreflang)git diff --check: passedDetailed evidence:
docs/seo/reports/2026-07-29-pr-validation.mdSummary by CodeRabbit