lv24-seo 0.1.0__tar.gz
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.
- lv24_seo-0.1.0/.env.example +50 -0
- lv24_seo-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +46 -0
- lv24_seo-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +36 -0
- lv24_seo-0.1.0/.github/pull_request_template.md +29 -0
- lv24_seo-0.1.0/.github/workflows/ci.yml +96 -0
- lv24_seo-0.1.0/.github/workflows/release.yml +212 -0
- lv24_seo-0.1.0/.gitignore +48 -0
- lv24_seo-0.1.0/.gitleaks.toml +16 -0
- lv24_seo-0.1.0/AGENTS.md +91 -0
- lv24_seo-0.1.0/CHANGELOG.md +54 -0
- lv24_seo-0.1.0/CLAUDE.md +4 -0
- lv24_seo-0.1.0/CODE_OF_CONDUCT.md +85 -0
- lv24_seo-0.1.0/CONTRIBUTING.md +63 -0
- lv24_seo-0.1.0/LICENSE +202 -0
- lv24_seo-0.1.0/PKG-INFO +163 -0
- lv24_seo-0.1.0/README.md +125 -0
- lv24_seo-0.1.0/README.ru.md +42 -0
- lv24_seo-0.1.0/SECURITY.md +44 -0
- lv24_seo-0.1.0/TRADEMARKS.md +13 -0
- lv24_seo-0.1.0/docs/architecture.md +115 -0
- lv24_seo-0.1.0/docs/cli.md +100 -0
- lv24_seo-0.1.0/docs/config.md +98 -0
- lv24_seo-0.1.0/docs/data-sources.md +126 -0
- lv24_seo-0.1.0/docs/decisions/ADR-0001-stack.md +29 -0
- lv24_seo-0.1.0/docs/decisions/ADR-0002-honesty-envelope.md +33 -0
- lv24_seo-0.1.0/docs/decisions/ADR-0003-write-gating.md +30 -0
- lv24_seo-0.1.0/docs/guides/github-actions.md +395 -0
- lv24_seo-0.1.0/docs/guides/mcp-clients.md +339 -0
- lv24_seo-0.1.0/docs/guides/quickstart.md +78 -0
- lv24_seo-0.1.0/docs/guides/troubleshooting.md +390 -0
- lv24_seo-0.1.0/docs/guides/vps-cron.md +340 -0
- lv24_seo-0.1.0/docs/mcp.md +121 -0
- lv24_seo-0.1.0/docs/plans/2026-11-10-implementation-plan.md +379 -0
- lv24_seo-0.1.0/docs/releases.md +372 -0
- lv24_seo-0.1.0/docs/research/2026-11-landscape.md +124 -0
- lv24_seo-0.1.0/docs/scheduling.md +66 -0
- lv24_seo-0.1.0/docs/skills.md +60 -0
- lv24_seo-0.1.0/examples/config/seo.toml +82 -0
- lv24_seo-0.1.0/examples/github-actions/seo-weekly.yml +146 -0
- lv24_seo-0.1.0/llms.txt +28 -0
- lv24_seo-0.1.0/packages/npm/README.md +50 -0
- lv24_seo-0.1.0/packages/npm/bin/lv24-seo.js +74 -0
- lv24_seo-0.1.0/packages/npm/package.json +42 -0
- lv24_seo-0.1.0/pyproject.toml +79 -0
- lv24_seo-0.1.0/schemas/tools.json +222 -0
- lv24_seo-0.1.0/server.json +32 -0
- lv24_seo-0.1.0/skills/manifest.json +45 -0
- lv24_seo-0.1.0/skills/seo-indexing-triage/SKILL.md +63 -0
- lv24_seo-0.1.0/skills/seo-technical-audit/SKILL.md +64 -0
- lv24_seo-0.1.0/skills/seo-weekly/SKILL.md +69 -0
- lv24_seo-0.1.0/src/lv24_seo/__init__.py +2 -0
- lv24_seo-0.1.0/src/lv24_seo/analysis/__init__.py +9 -0
- lv24_seo-0.1.0/src/lv24_seo/analysis/delta.py +177 -0
- lv24_seo-0.1.0/src/lv24_seo/analysis/fusion.py +316 -0
- lv24_seo-0.1.0/src/lv24_seo/analysis/indexing.py +196 -0
- lv24_seo-0.1.0/src/lv24_seo/analysis/logs.py +383 -0
- lv24_seo-0.1.0/src/lv24_seo/analysis/opportunities.py +430 -0
- lv24_seo-0.1.0/src/lv24_seo/automation/__init__.py +17 -0
- lv24_seo-0.1.0/src/lv24_seo/automation/scheduler.py +499 -0
- lv24_seo-0.1.0/src/lv24_seo/cli/__init__.py +91 -0
- lv24_seo-0.1.0/src/lv24_seo/cli/auth.py +334 -0
- lv24_seo-0.1.0/src/lv24_seo/cli/main.py +1661 -0
- lv24_seo-0.1.0/src/lv24_seo/core/__init__.py +1 -0
- lv24_seo-0.1.0/src/lv24_seo/core/config.py +365 -0
- lv24_seo-0.1.0/src/lv24_seo/core/envelope.py +76 -0
- lv24_seo-0.1.0/src/lv24_seo/core/errors.py +82 -0
- lv24_seo-0.1.0/src/lv24_seo/core/http.py +314 -0
- lv24_seo-0.1.0/src/lv24_seo/core/logging.py +315 -0
- lv24_seo-0.1.0/src/lv24_seo/core/ratelimit.py +242 -0
- lv24_seo-0.1.0/src/lv24_seo/core/secrets.py +409 -0
- lv24_seo-0.1.0/src/lv24_seo/core/timeutil.py +240 -0
- lv24_seo-0.1.0/src/lv24_seo/core/write_gate.py +327 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/__init__.py +1 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/fetcher.py +398 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/graph.py +193 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/html.py +409 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/models.py +79 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/__init__.py +28 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/base.py +39 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_broken_internal_link.py +102 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_canonical_mismatch.py +59 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_canonical_missing.py +44 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_client_error.py +43 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_duplicate_organization.py +81 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_h1_missing.py +41 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_h1_multiple.py +42 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_hreflang_reciprocal.py +115 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_hreflang_target.py +94 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_hreflang_x_default.py +53 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_html_lang.py +50 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_html_lang_mismatch.py +82 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_image_alt_missing.py +49 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_jsonld_event.py +107 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_jsonld_organization.py +82 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_jsonld_required.py +102 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_meta_description_duplicate.py +60 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_meta_description_missing.py +43 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_mixed_content.py +65 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_noindex_on_200.py +41 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_redirect_chain.py +40 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_robots_blocked.py +68 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_server_error.py +35 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_sitemap_url_noindex.py +106 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_sitemap_url_not_ok.py +74 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_soft_404.py +52 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_thin_content.py +75 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_title_duplicate.py +60 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_title_length.py +53 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/rules/rule_title_missing.py +41 -0
- lv24_seo-0.1.0/src/lv24_seo/crawl/sitemap.py +245 -0
- lv24_seo-0.1.0/src/lv24_seo/mcp/__init__.py +28 -0
- lv24_seo-0.1.0/src/lv24_seo/mcp/registry.py +158 -0
- lv24_seo-0.1.0/src/lv24_seo/mcp/server.py +411 -0
- lv24_seo-0.1.0/src/lv24_seo/mcp/tools.py +1051 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/__init__.py +5 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/base.py +80 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/bing/__init__.py +5 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/bing/client.py +381 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/crux/__init__.py +5 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/crux/client.py +201 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/gsc/__init__.py +5 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/gsc/client.py +288 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/psi/__init__.py +5 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/psi/client.py +213 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/yandex_metrika/__init__.py +5 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/yandex_metrika/client.py +345 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/yandex_webmaster/__init__.py +5 -0
- lv24_seo-0.1.0/src/lv24_seo/providers/yandex_webmaster/client.py +546 -0
- lv24_seo-0.1.0/src/lv24_seo/report/__init__.py +1 -0
- lv24_seo-0.1.0/src/lv24_seo/report/builders.py +464 -0
- lv24_seo-0.1.0/src/lv24_seo/report/delivery.py +402 -0
- lv24_seo-0.1.0/src/lv24_seo/report/models.py +58 -0
- lv24_seo-0.1.0/src/lv24_seo/report/render.py +309 -0
- lv24_seo-0.1.0/src/lv24_seo/services.py +1662 -0
- lv24_seo-0.1.0/src/lv24_seo/store/__init__.py +16 -0
- lv24_seo-0.1.0/src/lv24_seo/store/db.py +79 -0
- lv24_seo-0.1.0/src/lv24_seo/store/repositories.py +581 -0
- lv24_seo-0.1.0/src/lv24_seo/store/schema.sql +146 -0
- lv24_seo-0.1.0/tests/unit/test_analysis_delta.py +218 -0
- lv24_seo-0.1.0/tests/unit/test_analysis_fusion.py +226 -0
- lv24_seo-0.1.0/tests/unit/test_analysis_indexing.py +255 -0
- lv24_seo-0.1.0/tests/unit/test_analysis_logs.py +366 -0
- lv24_seo-0.1.0/tests/unit/test_analysis_opportunities.py +253 -0
- lv24_seo-0.1.0/tests/unit/test_automation_scheduler.py +279 -0
- lv24_seo-0.1.0/tests/unit/test_cli.py +591 -0
- lv24_seo-0.1.0/tests/unit/test_cli_writes.py +388 -0
- lv24_seo-0.1.0/tests/unit/test_config.py +462 -0
- lv24_seo-0.1.0/tests/unit/test_crawl_fetcher.py +379 -0
- lv24_seo-0.1.0/tests/unit/test_crawl_graph.py +263 -0
- lv24_seo-0.1.0/tests/unit/test_crawl_html.py +314 -0
- lv24_seo-0.1.0/tests/unit/test_crawl_sitemap.py +293 -0
- lv24_seo-0.1.0/tests/unit/test_envelope.py +63 -0
- lv24_seo-0.1.0/tests/unit/test_http.py +345 -0
- lv24_seo-0.1.0/tests/unit/test_logging.py +303 -0
- lv24_seo-0.1.0/tests/unit/test_mcp.py +672 -0
- lv24_seo-0.1.0/tests/unit/test_provider_base.py +47 -0
- lv24_seo-0.1.0/tests/unit/test_provider_bing.py +258 -0
- lv24_seo-0.1.0/tests/unit/test_provider_crux_psi.py +347 -0
- lv24_seo-0.1.0/tests/unit/test_provider_gsc.py +336 -0
- lv24_seo-0.1.0/tests/unit/test_provider_yandex_metrika.py +313 -0
- lv24_seo-0.1.0/tests/unit/test_provider_yandex_webmaster.py +387 -0
- lv24_seo-0.1.0/tests/unit/test_ratelimit.py +298 -0
- lv24_seo-0.1.0/tests/unit/test_registry.py +54 -0
- lv24_seo-0.1.0/tests/unit/test_report_builders.py +326 -0
- lv24_seo-0.1.0/tests/unit/test_report_delivery.py +590 -0
- lv24_seo-0.1.0/tests/unit/test_report_render.py +305 -0
- lv24_seo-0.1.0/tests/unit/test_rules_content.py +338 -0
- lv24_seo-0.1.0/tests/unit/test_rules_indexability.py +343 -0
- lv24_seo-0.1.0/tests/unit/test_rules_structured.py +500 -0
- lv24_seo-0.1.0/tests/unit/test_secrets.py +288 -0
- lv24_seo-0.1.0/tests/unit/test_services.py +804 -0
- lv24_seo-0.1.0/tests/unit/test_store.py +373 -0
- lv24_seo-0.1.0/tests/unit/test_timeutil.py +409 -0
- lv24_seo-0.1.0/tests/unit/test_write_gate.py +340 -0
- lv24_seo-0.1.0/uv.lock +1583 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# lv24-seo-optimizer — configuration template
|
|
2
|
+
# Copy to `.env` (git-ignored) or export the variables in your shell.
|
|
3
|
+
# Every value is optional: the default path needs no paid key at all.
|
|
4
|
+
|
|
5
|
+
# ---------- Google Search Console ----------
|
|
6
|
+
# Either an OAuth client (loopback flow) or a service-account JSON key that was added
|
|
7
|
+
# to the property as Full/Owner. Prefer OAuth for a domain property you control.
|
|
8
|
+
LV24_SEO_GSC_CLIENT_ID=
|
|
9
|
+
LV24_SEO_GSC_CLIENT_SECRET=
|
|
10
|
+
LV24_SEO_GSC_SERVICE_ACCOUNT_FILE=
|
|
11
|
+
LV24_SEO_GSC_PROPERTY=sc-domain:example.com
|
|
12
|
+
|
|
13
|
+
# ---------- Yandex Webmaster ----------
|
|
14
|
+
# Scope: webmaster:hostinfo (+ webmaster:verify if you add hosts programmatically).
|
|
15
|
+
LV24_SEO_YANDEX_WEBMASTER_TOKEN=
|
|
16
|
+
LV24_SEO_YANDEX_HOST_ID=https:example.com:443
|
|
17
|
+
|
|
18
|
+
# ---------- Yandex Metrika ----------
|
|
19
|
+
# Scope: metrika:read
|
|
20
|
+
LV24_SEO_YANDEX_METRIKA_TOKEN=
|
|
21
|
+
LV24_SEO_YANDEX_METRIKA_COUNTER=
|
|
22
|
+
|
|
23
|
+
# ---------- Bing Webmaster + IndexNow ----------
|
|
24
|
+
LV24_SEO_BING_API_KEY=
|
|
25
|
+
LV24_SEO_INDEXNOW_KEY=
|
|
26
|
+
|
|
27
|
+
# ---------- Chrome UX Report / PageSpeed Insights ----------
|
|
28
|
+
LV24_SEO_CRUX_API_KEY=
|
|
29
|
+
LV24_SEO_PSI_API_KEY=
|
|
30
|
+
|
|
31
|
+
# ---------- Optional paid adapters (BYO key; disabled by default) ----------
|
|
32
|
+
LV24_SEO_DATAFORSEO_LOGIN=
|
|
33
|
+
LV24_SEO_DATAFORSEO_PASSWORD=
|
|
34
|
+
LV24_SEO_SERPAPI_KEY=
|
|
35
|
+
|
|
36
|
+
# ---------- Delivery ----------
|
|
37
|
+
LV24_SEO_SMTP_HOST=
|
|
38
|
+
LV24_SEO_SMTP_PORT=587
|
|
39
|
+
LV24_SEO_SMTP_USER=
|
|
40
|
+
LV24_SEO_SMTP_PASSWORD=
|
|
41
|
+
LV24_SEO_SMTP_FROM=seo@example.com
|
|
42
|
+
LV24_SEO_MAIL_TO=owner@example.com
|
|
43
|
+
LV24_SEO_TELEGRAM_BOT_TOKEN=
|
|
44
|
+
LV24_SEO_TELEGRAM_CHAT_ID=
|
|
45
|
+
|
|
46
|
+
# ---------- Storage / behaviour ----------
|
|
47
|
+
LV24_SEO_DATA_DIR=./data
|
|
48
|
+
LV24_SEO_LOG_LEVEL=INFO
|
|
49
|
+
# Set to 1 only in CI/containers where keyring is unavailable.
|
|
50
|
+
LV24_SEO_ALLOW_FILE_SECRETS=0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something in lv24-seo-optimizer does not work as documented
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thanks for the report. Please do not paste tokens, cookies or real query data.
|
|
9
|
+
- type: input
|
|
10
|
+
id: version
|
|
11
|
+
attributes:
|
|
12
|
+
label: Version
|
|
13
|
+
description: Output of `lv24-seo version --json`
|
|
14
|
+
validations:
|
|
15
|
+
required: true
|
|
16
|
+
- type: dropdown
|
|
17
|
+
id: surface
|
|
18
|
+
attributes:
|
|
19
|
+
label: Surface
|
|
20
|
+
options: [CLI, MCP server, skill, report/email, crawl, provider API, install, docs]
|
|
21
|
+
validations:
|
|
22
|
+
required: true
|
|
23
|
+
- type: input
|
|
24
|
+
id: provider
|
|
25
|
+
attributes:
|
|
26
|
+
label: Provider (if relevant)
|
|
27
|
+
placeholder: gsc / yandex_webmaster / yandex_metrika / bing / crux / psi / crawl
|
|
28
|
+
- type: textarea
|
|
29
|
+
id: repro
|
|
30
|
+
attributes:
|
|
31
|
+
label: Steps to reproduce
|
|
32
|
+
placeholder: |
|
|
33
|
+
1. lv24-seo ...
|
|
34
|
+
2. ...
|
|
35
|
+
- type: textarea
|
|
36
|
+
id: expected
|
|
37
|
+
attributes:
|
|
38
|
+
label: Expected vs actual
|
|
39
|
+
validations:
|
|
40
|
+
required: true
|
|
41
|
+
- type: textarea
|
|
42
|
+
id: logs
|
|
43
|
+
attributes:
|
|
44
|
+
label: Redacted output
|
|
45
|
+
description: Run with `--verbose` and remove tokens, cookies and personal data.
|
|
46
|
+
render: text
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Propose a provider, crawl rule, report or skill
|
|
3
|
+
labels: ["enhancement"]
|
|
4
|
+
body:
|
|
5
|
+
- type: dropdown
|
|
6
|
+
id: kind
|
|
7
|
+
attributes:
|
|
8
|
+
label: What is it?
|
|
9
|
+
options: [free-tier provider, paid adapter (BYO key), crawl rule, report, MCP tool, CLI command, skill, docs]
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: problem
|
|
14
|
+
attributes:
|
|
15
|
+
label: Problem
|
|
16
|
+
description: What SEO question cannot be answered today?
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: data
|
|
21
|
+
attributes:
|
|
22
|
+
label: Data source
|
|
23
|
+
description: API/docs link, free quota, auth model, what it can and cannot return.
|
|
24
|
+
validations:
|
|
25
|
+
required: true
|
|
26
|
+
- type: checkboxes
|
|
27
|
+
id: rules
|
|
28
|
+
attributes:
|
|
29
|
+
label: Project rules
|
|
30
|
+
options:
|
|
31
|
+
- label: It works on a free tier (or is an optional adapter with a user-supplied key)
|
|
32
|
+
required: true
|
|
33
|
+
- label: It does not require scraping Google/Yandex SERPs
|
|
34
|
+
required: true
|
|
35
|
+
- label: I checked that no existing tool in the repo covers this
|
|
36
|
+
required: true
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## Problem
|
|
2
|
+
|
|
3
|
+
<!-- What SEO question or failure does this change address? -->
|
|
4
|
+
|
|
5
|
+
## Solution
|
|
6
|
+
|
|
7
|
+
<!-- What changed, and why this way. -->
|
|
8
|
+
|
|
9
|
+
## Provider limits touched
|
|
10
|
+
|
|
11
|
+
<!-- GSC / Yandex / Bing quotas, truncation, data lag. Write "none" if not applicable. -->
|
|
12
|
+
|
|
13
|
+
## Verification
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv run ruff check .
|
|
17
|
+
uv run ruff format --check .
|
|
18
|
+
uv run mypy src
|
|
19
|
+
uv run pytest -q
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Checklist
|
|
23
|
+
|
|
24
|
+
- [ ] No paid dependency added to the default path
|
|
25
|
+
- [ ] `limitations[]` / truncation honest
|
|
26
|
+
- [ ] Write path gated (`--allow-write`, idempotency key)
|
|
27
|
+
- [ ] Docs updated in the same change
|
|
28
|
+
- [ ] Works on Windows
|
|
29
|
+
- [ ] No secrets, tokens or real query data in tests/fixtures
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths-ignore: ["docs/**", "**/*.md", "LICENSE"]
|
|
7
|
+
pull_request:
|
|
8
|
+
paths-ignore: ["docs/**", "**/*.md", "LICENSE"]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
# Zero-spend policy: public repo + standard ubuntu runners only, no matrix explosion,
|
|
12
|
+
# no larger runners, no artifacts beyond 7 days, concurrency cancels stale runs.
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: ci-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
lint:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 10
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v7
|
|
26
|
+
with:
|
|
27
|
+
fetch-depth: 0
|
|
28
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
- name: Install
|
|
32
|
+
run: uv sync --extra dev
|
|
33
|
+
- name: Ruff lint
|
|
34
|
+
run: uv run ruff check .
|
|
35
|
+
- name: Ruff format
|
|
36
|
+
run: uv run ruff format --check .
|
|
37
|
+
- name: Mypy
|
|
38
|
+
run: uv run mypy src
|
|
39
|
+
- name: Gitleaks
|
|
40
|
+
run: |
|
|
41
|
+
curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.24.3/gitleaks_8.24.3_linux_x64.tar.gz \
|
|
42
|
+
| tar -xz -C /usr/local/bin gitleaks
|
|
43
|
+
gitleaks detect --source . --redact --verbose --exit-code 2 --log-opts="--all"
|
|
44
|
+
|
|
45
|
+
test:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
timeout-minutes: 15
|
|
48
|
+
strategy:
|
|
49
|
+
fail-fast: false
|
|
50
|
+
matrix:
|
|
51
|
+
python: ["3.11", "3.13"]
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v7
|
|
54
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
55
|
+
with:
|
|
56
|
+
enable-cache: true
|
|
57
|
+
python-version: ${{ matrix.python }}
|
|
58
|
+
- name: Install
|
|
59
|
+
run: uv sync --extra dev
|
|
60
|
+
- name: Tests (offline only)
|
|
61
|
+
run: uv run pytest -q -m "not live and not network"
|
|
62
|
+
|
|
63
|
+
schemas:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
timeout-minutes: 10
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v7
|
|
68
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
69
|
+
with:
|
|
70
|
+
enable-cache: true
|
|
71
|
+
- name: Install
|
|
72
|
+
run: uv sync --extra dev
|
|
73
|
+
- name: Regenerate tool schemas
|
|
74
|
+
run: uv run python -m lv24_seo.mcp.registry > /tmp/tools.json
|
|
75
|
+
- name: Fail on drift
|
|
76
|
+
run: diff -u schemas/tools.json /tmp/tools.json
|
|
77
|
+
|
|
78
|
+
npm:
|
|
79
|
+
# The npm package is a thin launcher; this job only proves it is syntactically valid and
|
|
80
|
+
# packages the intended files. Publishing happens in release.yml on a v* tag.
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
timeout-minutes: 5
|
|
83
|
+
defaults:
|
|
84
|
+
run:
|
|
85
|
+
working-directory: packages/npm
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v7
|
|
88
|
+
- uses: actions/setup-node@v7.1.0
|
|
89
|
+
with:
|
|
90
|
+
node-version: "22"
|
|
91
|
+
- name: Syntax check
|
|
92
|
+
run: node --check bin/lv24-seo.js
|
|
93
|
+
- name: Package contents
|
|
94
|
+
run: npm pack --dry-run
|
|
95
|
+
|
|
96
|
+
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Build and publish lv24-seo when a v* tag is pushed.
|
|
4
|
+
#
|
|
5
|
+
# Triggers:
|
|
6
|
+
# * push of a v* tag -> build + publish to PyPI;
|
|
7
|
+
# * workflow_dispatch -> build-only dry run unless the selected ref is a v* tag.
|
|
8
|
+
# There is deliberately no pull_request trigger, so releases never run for PRs.
|
|
9
|
+
#
|
|
10
|
+
# Zero-spend policy (same as ci.yml):
|
|
11
|
+
# * public repository + standard ubuntu-latest runners only = $0;
|
|
12
|
+
# * no matrix, no larger runners, no paid services;
|
|
13
|
+
# * two short jobs, artifacts kept for 7 days;
|
|
14
|
+
# * PyPI uses OIDC Trusted Publishing, so no long-lived API token secret exists.
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
tags: ["v*"]
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
concurrency:
|
|
24
|
+
group: release-${{ github.ref }}
|
|
25
|
+
# Never cancel a run that may be halfway through a PyPI upload.
|
|
26
|
+
cancel-in-progress: false
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
build:
|
|
30
|
+
name: build distributions
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
timeout-minutes: 10
|
|
33
|
+
steps:
|
|
34
|
+
- name: Check out the repository
|
|
35
|
+
uses: actions/checkout@v7
|
|
36
|
+
with:
|
|
37
|
+
persist-credentials: false
|
|
38
|
+
|
|
39
|
+
- name: Install uv and Python
|
|
40
|
+
uses: astral-sh/setup-uv@v10.3.3
|
|
41
|
+
with:
|
|
42
|
+
enable-cache: true
|
|
43
|
+
python-version: "3.13"
|
|
44
|
+
|
|
45
|
+
- name: Check tag, pyproject, __version__ and server.json agree
|
|
46
|
+
# A release must never publish a tag that disagrees with the package metadata.
|
|
47
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
48
|
+
run: |
|
|
49
|
+
python3 - "${GITHUB_REF_NAME#v}" <<'PY'
|
|
50
|
+
import json
|
|
51
|
+
import pathlib
|
|
52
|
+
import re
|
|
53
|
+
import sys
|
|
54
|
+
import tomllib
|
|
55
|
+
|
|
56
|
+
tag = sys.argv[1]
|
|
57
|
+
pyproject = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
|
|
58
|
+
server = json.loads(pathlib.Path("server.json").read_text(encoding="utf-8"))
|
|
59
|
+
npm = json.loads(pathlib.Path("packages/npm/package.json").read_text(encoding="utf-8"))
|
|
60
|
+
init_source = pathlib.Path("src/lv24_seo/__init__.py").read_text(encoding="utf-8")
|
|
61
|
+
init_version = re.search(r'^__version__\s*=\s*"([^"]+)"', init_source, re.MULTILINE)
|
|
62
|
+
versions = {
|
|
63
|
+
"tag": tag,
|
|
64
|
+
"pyproject.toml": pyproject["project"]["version"],
|
|
65
|
+
"src/lv24_seo/__init__.py": init_version.group(1) if init_version else "missing",
|
|
66
|
+
"server.json": server["version"],
|
|
67
|
+
"server.json packages[0]": server["packages"][0]["version"],
|
|
68
|
+
"packages/npm/package.json": npm["version"],
|
|
69
|
+
}
|
|
70
|
+
if len(set(versions.values())) != 1:
|
|
71
|
+
print("::error::version mismatch: " + ", ".join(f"{key}={value}" for key, value in versions.items()))
|
|
72
|
+
raise SystemExit(1)
|
|
73
|
+
print(f"versions aligned at {tag}")
|
|
74
|
+
PY
|
|
75
|
+
|
|
76
|
+
- name: Build sdist and wheel
|
|
77
|
+
# hatchling is the build backend declared in pyproject.toml.
|
|
78
|
+
# uv.lock must be committed: CI and the local release checks use it for
|
|
79
|
+
# reproducible installs, so the tagged commit has a tested dependency set.
|
|
80
|
+
run: uv build
|
|
81
|
+
|
|
82
|
+
- name: List distributions
|
|
83
|
+
run: ls -lh dist/
|
|
84
|
+
|
|
85
|
+
- name: Upload distributions
|
|
86
|
+
uses: actions/upload-artifact@v7
|
|
87
|
+
with:
|
|
88
|
+
name: dist
|
|
89
|
+
path: dist/
|
|
90
|
+
retention-days: 7
|
|
91
|
+
if-no-files-found: error
|
|
92
|
+
|
|
93
|
+
publish-pypi:
|
|
94
|
+
name: publish to PyPI
|
|
95
|
+
needs: build
|
|
96
|
+
# A manual dispatch is a build-only dry run unless it targets a v* tag.
|
|
97
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
timeout-minutes: 10
|
|
100
|
+
environment:
|
|
101
|
+
name: pypi
|
|
102
|
+
url: https://pypi.org/p/lv24-seo
|
|
103
|
+
permissions:
|
|
104
|
+
# Job-level permissions replace the workflow-level block.
|
|
105
|
+
# OIDC only: no PyPI password or API token is stored in this repository.
|
|
106
|
+
id-token: write
|
|
107
|
+
steps:
|
|
108
|
+
- name: Download distributions
|
|
109
|
+
uses: actions/download-artifact@v7
|
|
110
|
+
with:
|
|
111
|
+
name: dist
|
|
112
|
+
path: dist/
|
|
113
|
+
|
|
114
|
+
- name: Publish distributions to PyPI
|
|
115
|
+
# One-time setup on PyPI: project lv24-seo -> Publishing -> add a trusted
|
|
116
|
+
# publisher with owner LevapVeeskela, repository lv24-seo-optimizer,
|
|
117
|
+
# workflow release.yml and environment pypi. Without it this step fails.
|
|
118
|
+
# PyPI files are immutable: a version can never be uploaded twice.
|
|
119
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
120
|
+
with:
|
|
121
|
+
packages-dir: dist/
|
|
122
|
+
|
|
123
|
+
publish-npm:
|
|
124
|
+
name: publish @lv24/seo to npm
|
|
125
|
+
needs: build
|
|
126
|
+
# A manual dispatch is a build-only dry run unless it targets a v* tag.
|
|
127
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
128
|
+
runs-on: ubuntu-latest
|
|
129
|
+
timeout-minutes: 10
|
|
130
|
+
permissions:
|
|
131
|
+
contents: read
|
|
132
|
+
# npm provenance attests the tarball to this workflow run via OIDC.
|
|
133
|
+
id-token: write
|
|
134
|
+
defaults:
|
|
135
|
+
run:
|
|
136
|
+
working-directory: packages/npm
|
|
137
|
+
steps:
|
|
138
|
+
- name: Check out the repository
|
|
139
|
+
uses: actions/checkout@v7
|
|
140
|
+
with:
|
|
141
|
+
persist-credentials: false
|
|
142
|
+
|
|
143
|
+
- name: Detect the npm token
|
|
144
|
+
# `secrets` cannot be used in `if:` expressions, so the gate is a step output.
|
|
145
|
+
# Without NPM_TOKEN the job skips instead of failing the whole release.
|
|
146
|
+
id: gate
|
|
147
|
+
env:
|
|
148
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
149
|
+
run: |
|
|
150
|
+
if [ -n "$NPM_TOKEN" ]; then
|
|
151
|
+
echo "enabled=true" >> "$GITHUB_OUTPUT"
|
|
152
|
+
else
|
|
153
|
+
echo "enabled=false" >> "$GITHUB_OUTPUT"
|
|
154
|
+
echo "::notice::NPM_TOKEN is not set — skipping the npm publish."
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
- name: Install Node
|
|
158
|
+
if: steps.gate.outputs.enabled == 'true'
|
|
159
|
+
uses: actions/setup-node@v7.1.0
|
|
160
|
+
with:
|
|
161
|
+
node-version: "22"
|
|
162
|
+
registry-url: https://registry.npmjs.org
|
|
163
|
+
|
|
164
|
+
- name: Check the npm version matches the tag
|
|
165
|
+
if: steps.gate.outputs.enabled == 'true'
|
|
166
|
+
run: |
|
|
167
|
+
node -e '
|
|
168
|
+
const pkg = require("./package.json");
|
|
169
|
+
const tag = process.env.GITHUB_REF_NAME.replace(/^v/, "");
|
|
170
|
+
if (pkg.version !== tag) {
|
|
171
|
+
console.error(`npm version ${pkg.version} != tag ${tag}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
console.log(`npm version aligned at ${tag}`);
|
|
175
|
+
'
|
|
176
|
+
|
|
177
|
+
- name: Publish with provenance
|
|
178
|
+
if: steps.gate.outputs.enabled == 'true'
|
|
179
|
+
env:
|
|
180
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
181
|
+
run: npm publish --access public --provenance
|
|
182
|
+
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# Optional and disabled: GHCR image for Docker/VPS users (WP10.3).
|
|
185
|
+
# Public container packages are free to store and pull, but building and
|
|
186
|
+
# pushing still consumes Actions minutes, and this repository has no
|
|
187
|
+
# Dockerfile yet. Enable only after the first PyPI release is green, then
|
|
188
|
+
# change `needs` to publish-pypi if the image must follow a successful upload.
|
|
189
|
+
#
|
|
190
|
+
# publish-ghcr:
|
|
191
|
+
# name: publish container to GHCR
|
|
192
|
+
# needs: build
|
|
193
|
+
# if: startsWith(github.ref, 'refs/tags/v')
|
|
194
|
+
# runs-on: ubuntu-latest
|
|
195
|
+
# timeout-minutes: 20
|
|
196
|
+
# permissions:
|
|
197
|
+
# contents: read
|
|
198
|
+
# packages: write
|
|
199
|
+
# steps:
|
|
200
|
+
# - uses: actions/checkout@v7
|
|
201
|
+
# with:
|
|
202
|
+
# persist-credentials: false
|
|
203
|
+
# - uses: docker/login-action@v3
|
|
204
|
+
# with:
|
|
205
|
+
# registry: ghcr.io
|
|
206
|
+
# username: ${{ github.actor }}
|
|
207
|
+
# password: ${{ secrets.GITHUB_TOKEN }}
|
|
208
|
+
# - uses: docker/build-push-action@v6
|
|
209
|
+
# with:
|
|
210
|
+
# context: .
|
|
211
|
+
# push: true
|
|
212
|
+
# tags: ghcr.io/levapveeskela/lv24-seo:${{ github.ref_name }}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Byte-compiled / optimized
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
|
|
9
|
+
# Virtual envs
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
|
|
14
|
+
# Tooling caches
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
.uv/
|
|
21
|
+
|
|
22
|
+
# Local data (never commit crawled data, tokens or reports)
|
|
23
|
+
data/
|
|
24
|
+
.env
|
|
25
|
+
.env.*
|
|
26
|
+
!.env.example
|
|
27
|
+
*.sqlite
|
|
28
|
+
*.sqlite3
|
|
29
|
+
*.db
|
|
30
|
+
*.sqlite3-wal
|
|
31
|
+
*.sqlite3-shm
|
|
32
|
+
reports/*.html
|
|
33
|
+
reports/*.json
|
|
34
|
+
!reports/.gitkeep
|
|
35
|
+
logs/
|
|
36
|
+
*.log
|
|
37
|
+
|
|
38
|
+
# Editors / OS
|
|
39
|
+
.idea/
|
|
40
|
+
.vscode/*
|
|
41
|
+
!.vscode/extensions.json
|
|
42
|
+
.DS_Store
|
|
43
|
+
Thumbs.db
|
|
44
|
+
desktop.ini
|
|
45
|
+
|
|
46
|
+
# Node (npm launcher package)
|
|
47
|
+
node_modules/
|
|
48
|
+
packages/npm/package-lock.json
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Gitleaks configuration.
|
|
2
|
+
# The default rule set is kept; we only allowlist the documentation template whose values are
|
|
3
|
+
# intentionally empty placeholders (a key *name* without a secret still matches generic-api-key).
|
|
4
|
+
|
|
5
|
+
[extend]
|
|
6
|
+
useDefault = true
|
|
7
|
+
|
|
8
|
+
[allowlist]
|
|
9
|
+
description = "Template files contain empty placeholders only; env var NAMES are not secrets"
|
|
10
|
+
paths = [
|
|
11
|
+
'''^\.env\.example$''',
|
|
12
|
+
'''^tests/''',
|
|
13
|
+
]
|
|
14
|
+
regexes = [
|
|
15
|
+
'''LV24_SEO_[A-Z0-9_]+''',
|
|
16
|
+
]
|
lv24_seo-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# AGENTS.md — contract for AI agents working in this repo
|
|
2
|
+
|
|
3
|
+
This file is the canonical instruction set for any coding agent (Claude Code, OpenCode, Codex,
|
|
4
|
+
Cursor, Gemini, Aider). `CLAUDE.md` is a pointer to this file — never let the two diverge.
|
|
5
|
+
|
|
6
|
+
## 1. What this project is
|
|
7
|
+
|
|
8
|
+
`lv24-seo-optimizer` is a Python package that ships four surfaces over one core:
|
|
9
|
+
|
|
10
|
+
| Surface | Entry point | Contract |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| CLI | `src/lv24_seo/cli/` | every command supports `--json`, returns `{ok,data,meta,warnings}`, stable exit codes |
|
|
13
|
+
| MCP server | `src/lv24_seo/mcp/` | 8–12 stable tools, read-only by default, `structuredContent` + text fallback |
|
|
14
|
+
| Skills | `skills/*/SKILL.md` | canonical skill + thin mirrors (`.claude/skills`, `.opencode/skills`) |
|
|
15
|
+
| Automation | `src/lv24_seo/automation/` | cron / systemd / Task Scheduler generators, email + Telegram delivery |
|
|
16
|
+
|
|
17
|
+
Single source of truth for tool metadata: `src/lv24_seo/mcp/registry.py`. CLI help, MCP schemas,
|
|
18
|
+
`schemas/tools.json` and the docs tables are **generated** from it. Never hand-edit generated files.
|
|
19
|
+
|
|
20
|
+
## 2. Hard rules
|
|
21
|
+
|
|
22
|
+
1. **Zero spend.** Default path must work on free tiers only. Do not add a dependency that requires a
|
|
23
|
+
paid account, a card on file, or a paid CI runner. Optional paid adapters are allowed only behind
|
|
24
|
+
an explicit provider key and must degrade gracefully.
|
|
25
|
+
2. **Read-only by default.** Any destructive or quota-consuming action (sitemap submit, recrawl
|
|
26
|
+
request, IndexNow submit) requires `--allow-write` (CLI) / `destructiveHint` + confirm token (MCP),
|
|
27
|
+
plus an idempotency key.
|
|
28
|
+
3. **Honesty over completeness.** Every bounded response must carry `truncated`, `total`, `data_age`
|
|
29
|
+
and `limitations[]`. Never let an agent claim full coverage when a provider capped the data.
|
|
30
|
+
4. **No secrets in the repo, logs, fixtures or tests.** Tokens live in the OS keychain, `0600` files
|
|
31
|
+
outside the repo, or `LV24_SEO_*` env vars. Tests use synthetic fixtures only.
|
|
32
|
+
5. **No scraping of SERPs.** Rank tracking uses provider APIs (GSC, Yandex Webmaster), never HTML
|
|
33
|
+
scraping of Google/Yandex result pages.
|
|
34
|
+
6. **No vendor resale.** If a paid provider is integrated, the user supplies their own key and the
|
|
35
|
+
data stays local.
|
|
36
|
+
7. **Windows, macOS and Linux must all work.** No POSIX-only paths in user-facing commands; shell
|
|
37
|
+
snippets in docs come in both bash and PowerShell where relevant.
|
|
38
|
+
8. **Deterministic output.** Report and CLI output is stable enough to diff between runs (sorted keys,
|
|
39
|
+
ISO-8601 UTC timestamps, no random ids in visible output).
|
|
40
|
+
|
|
41
|
+
## 3. Repo layout
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
src/lv24_seo/
|
|
45
|
+
core/ config, errors, envelopes, rate limiting, http, time
|
|
46
|
+
providers/ gsc, yandex_webmaster, yandex_metrika, bing, crux, psi, ga4 (optional)
|
|
47
|
+
crawl/ fetcher, parsers, rules, link graph, log ingest
|
|
48
|
+
store/ sqlite schema + repositories
|
|
49
|
+
report/ builders, renderers (html/text/md), delivery
|
|
50
|
+
mcp/ server, registry (SoT), resources, prompts
|
|
51
|
+
cli/ Typer commands
|
|
52
|
+
automation/ scheduler generators, GitHub Actions recipes
|
|
53
|
+
schemas/ generated JSON schemas (tools.json)
|
|
54
|
+
skills/ canonical SKILL.md files
|
|
55
|
+
docs/ architecture, limits, guides, plans, research
|
|
56
|
+
tests/ unit, contract (recorded fixtures), e2e (network-gated)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 4. How to work
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv sync # install dev env
|
|
63
|
+
uv run ruff check . # lint
|
|
64
|
+
uv run ruff format --check .
|
|
65
|
+
uv run mypy src # types
|
|
66
|
+
uv run pytest -q # offline tests only
|
|
67
|
+
uv run pytest -q -m live # requires tokens; never in CI
|
|
68
|
+
uv run lv24-seo doctor # environment self-check
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Before claiming done:
|
|
72
|
+
|
|
73
|
+
- `ruff check` + `ruff format --check` + `mypy` + `pytest -q` are green.
|
|
74
|
+
- Generated artifacts are regenerated, not edited: `uv run python -m lv24_seo.mcp.registry --write`.
|
|
75
|
+
- Docs updated in the same change (command help, tool table, `docs/*`).
|
|
76
|
+
- No new required dependency without a note in the PR body explaining why and its license.
|
|
77
|
+
- Do not weaken `limitations[]`, truncation reporting or write-gating to make a test pass.
|
|
78
|
+
|
|
79
|
+
## 5. Change review protocol
|
|
80
|
+
|
|
81
|
+
- Small, focused commits. Subject format: `[SEO](<area>) - <what changed>`, e.g.
|
|
82
|
+
`[SEO](providers) - add yandex recrawl quota guard`.
|
|
83
|
+
- One area per commit: `cli`, `mcp`, `providers`, `crawl`, `report`, `store`, `docs`, `infra`, `tests`.
|
|
84
|
+
- PR body: problem, solution, verification commands, and a note about provider limits touched.
|
|
85
|
+
- Never commit generated fixtures containing real customer data or personal queries.
|
|
86
|
+
|
|
87
|
+
## 6. Where decisions live
|
|
88
|
+
|
|
89
|
+
- Product and architecture decisions: `docs/decisions/ADR-*.md` (append a new ADR, do not rewrite old ones).
|
|
90
|
+
- Execution plan and wave status: `docs/plans/2026-11-10-implementation-plan.md`.
|
|
91
|
+
- Provider capability matrix: `docs/data-sources.md` — update it in the same PR that touches a provider.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning is [SemVer](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
### Added — engine (waves W0–W7)
|
|
9
|
+
|
|
10
|
+
- **Core**: config (`seo.toml` + `LV24_SEO_*` env), secret store (keychain → env → `0600` file),
|
|
11
|
+
HTTP client with IPv4 pinning/retry/`Retry-After`, token-bucket rate limiter, redacting JSON logging,
|
|
12
|
+
time/period helpers, typed error codes + response envelope.
|
|
13
|
+
- **Providers**: Google Search Console (search analytics with pagination, URL inspection, sites,
|
|
14
|
+
sitemaps), Yandex Webmaster (hosts, summary, indexing history, search-urls events, diagnostics,
|
|
15
|
+
popular queries, recrawl quota), Yandex Metrika (counters, goals, stat/v1), Bing Webmaster +
|
|
16
|
+
IndexNow, Chrome UX Report, PageSpeed Insights. Every result carries `truncated`, `total`,
|
|
17
|
+
`data_age_days`, `limitations[]`.
|
|
18
|
+
- **Crawler**: robots.txt (RFC 9309), manual redirects, sitemap reader (index, gzip, 50k/50MB caps),
|
|
19
|
+
stdlib HTML extraction (title/meta/canonical/hreflang/JSON-LD/links/images/word count), internal
|
|
20
|
+
link graph, render-parity hook, and **30 rules** (indexability, content, structured data, i18n,
|
|
21
|
+
mixed content, sitemap membership).
|
|
22
|
+
- **Storage**: SQLite (WAL) with 9 tables, repositories, snapshot hashing, delivery ledger.
|
|
23
|
+
- **Analysis**: period deltas with thresholds, striking-distance/CTR-gap/decay opportunities, indexing
|
|
24
|
+
triage buckets with next actions, GSC↔Yandex↔Metrika fusion with explicit caveats, nginx JSON log
|
|
25
|
+
stats (bots, crawl waste, p95).
|
|
26
|
+
- **Reports**: weekly/indexing/opportunities builders, text/Markdown/HTML renderers (email-safe,
|
|
27
|
+
escaped), SMTP + Telegram delivery with content hashing and idempotent sends, change-gated sends.
|
|
28
|
+
- **CLI**: `version`, `doctor`, `init`, `config show`, `auth status|set|login|logout`, `properties`,
|
|
29
|
+
`gsc query`, `yandex queries|indexing`, `crawl`, `report weekly`, `schedule print|install`,
|
|
30
|
+
`install --client`, `export tools` — every command supports `--json` and stable exit codes.
|
|
31
|
+
- **MCP**: 10 registry-driven tools (8 read + 2 gated write), resources `seo://config`,
|
|
32
|
+
`seo://report/latest`, `seo://docs/tools`, prompts `weekly_seo_review` / `indexing_triage` /
|
|
33
|
+
`content_opportunities`; stdio by default, stateless HTTP optional.
|
|
34
|
+
- **Automation**: cron / systemd user units / Windows Task Scheduler generators with `--print` and
|
|
35
|
+
idempotent install; GitHub Actions and VPS cron recipes.
|
|
36
|
+
|
|
37
|
+
### Added — docs and repository
|
|
38
|
+
|
|
39
|
+
- Apache-2.0 license, agent contract (`AGENTS.md`), `llms.txt`, CI, issue/PR templates, gitleaks.
|
|
40
|
+
- `lv24_seo.mcp.registry` — canonical tool registry (10 tools) and generated `schemas/tools.json`.
|
|
41
|
+
- Docs: architecture, data sources and quotas, MCP surface, CLI, config, scheduling, skills,
|
|
42
|
+
3 ADRs, quickstart, GitHub Actions and VPS guides.
|
|
43
|
+
- Research: 2026 landscape (Google/Yandex APIs, existing SEO MCP servers, paid tooling, audit signals).
|
|
44
|
+
- Skills: `seo-weekly`, `seo-indexing-triage`, `seo-technical-audit`.
|
|
45
|
+
|
|
46
|
+
### Known gaps
|
|
47
|
+
|
|
48
|
+
- GSC service-account auth is documented but not wired (OAuth bearer token only).
|
|
49
|
+
- Write operations exist in adapters but have no CLI/MCP confirm-token flow yet.
|
|
50
|
+
- No live end-to-end run against a real property; PyPI/MCP-registry publishing pending.
|
|
51
|
+
|
|
52
|
+
### Notes
|
|
53
|
+
|
|
54
|
+
- Pre-release: interfaces may change until `v0.1.0`.
|
lv24_seo-0.1.0/CLAUDE.md
ADDED