Repository navigation
fix(seo): cache SEO results for their TTL in seconds, keyed by every input - #273
Conversation
…input Root cause: the SEO layer gives TTLs in seconds (SEO_CACHE_*_TTL, SEOCacheManager.DEFAULT_TTL, "21600 // 6 hours" in every caller) but the base CacheManager counts milliseconds, so a 6 h analysis expired after 21.6 s and a 1 h audit after 3.6 s. That also hid cache keys that never covered their inputs: the shared cache answered post 5 of one site with post 5 of another (bulk keys had no site), schema/metadata keys ignored focus keywords, the audit key ignored maxPages, and nothing would have noticed an edited post. - SEOCacheManager.set() converts seconds to milliseconds, once; ttlFor() exposes the configured TTLs, so SEO_CACHE_*_TTL now reach the tools. - seoResultKey(): every call parameter (sorted, without force) plus the post's last modification for post-based results. Post-based tools fetch the post before the cache lookup. - analysisType narrows recommendations/details to its area (score and metrics stay whole-post); auditType selects the audit sections. - wp_seo_site_audit: maxPages gets minimum 1 / maximum 100; the never implemented includeExternalLinks is removed. API docs regenerated. Tests: 21 new (TTL lifetime, edits, focus keywords, maxPages, cross-site bulk, analysisType, auditType, schema); 4 existing cache-hit tests now count analyzer/generator calls, since every call fetches the post. Suite: 2778 passed before, 2799 passed after, 0 failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Reviewer's GuideThis PR fixes SEO cache TTL unit conversion, routes all result types through configured lifetimes, and creates input-complete, post-versioned keys so cached results remain isolated and refresh after edits. It also makes analysisType and auditType functional, tightens the site-audit schema, removes an ineffective option, and updates tests and documentation. Sequence diagram for versioned SEO result cachingsequenceDiagram
participant Caller
participant SEOTools
participant WordPressClient
participant SEOCacheManager
participant Analyzer
Caller->>SEOTools: analyzeContent(params)
SEOTools->>WordPressClient: getPost(postId)
WordPressClient-->>SEOTools: post with modified_gmt
SEOTools->>SEOCacheManager: get(seoResultKey(kind, params, post))
alt cached result
SEOCacheManager-->>SEOTools: cached result
else cache miss or force
SEOTools->>Analyzer: analyzePost(post, params)
Analyzer-->>SEOTools: SEO result
SEOTools->>SEOCacheManager: ttlFor(analysis)
SEOCacheManager->>SEOCacheManager: set(key, result, ttlSeconds * 1000)
SEOTools-->>Caller: SEO result
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
🟡 Changes recommended
Cache ordering, invalidation, bulk TTL configuration, and generated OpenAPI constraints remain incorrect.
4 open findings
What changed in this PR
Fixes SEO cache TTL units, key isolation, invalidation after edits, and analysis/audit filtering.
Changes:
- Converts SEO TTLs from seconds to milliseconds centrally.
- Expands cache keys to include result-shaping inputs and post modification times.
- Adds filtering behavior, validation, documentation, and regression tests.
| File | Description |
|---|---|
src/cache/SEOCacheManager.ts |
Adds centralized TTL conversion and key generation. |
src/cache/AGENTS.md |
Documents SEO cache contracts. |
src/tools/seo/SEOTools.ts |
Uses new keys, configured TTLs, and audit filtering. |
src/tools/seo/BulkOperations.ts |
Adds site-, input-, and edit-aware bulk keys. |
src/tools/seo/analyzers/ContentAnalyzer.ts |
Filters analysis details by requested area. |
src/tools/seo/SEOToolDefinitions.ts |
Updates schemas and descriptions. |
src/tools/seo/SEOHandlers.ts |
Removes unsupported external-link handling. |
src/types/seo.ts |
Removes the unused external-link parameter. |
src/tools/AGENTS.md |
Documents tool caching requirements. |
tests/cache/SEOCacheManager.test.js |
Tests corrected TTL behavior. |
tests/tools/seo/SEOToolsCaching.test.js |
Covers caching, invalidation, and filtering. |
tests/tools/seo/SEOToolsAnalyzeContent.test.js |
Updates cache-hit assertions. |
tests/tools/seo/BulkOperations.test.js |
Tests bulk cache isolation and edits. |
docs/api/tools/wp_seo_site_audit.md |
Regenerates site-audit documentation. |
docs/api/tools/wp_seo_analyze_content.md |
Documents analysis filtering. |
docs/api/openapi.json |
Regenerates SEO OpenAPI definitions. |
docs/api/README.md |
Updates generation date. |
docs/api/summary.json |
Updates generation timestamp. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…nt work Review follow-ups (Copilot on #273): - SEO keys are now seo:<kind>:<site>:<postId|->:<version>:<params>, so invalidatePostSEO/invalidateSiteSEO/clearSEOCache/getSEOCacheStats can find them. They never could: getAllKeys() was a placeholder returning [], and getSEOCacheKey() built "seo::type" keys. They now read the base cache's keys() and match on segments (site "s1" no longer matches "s10" as the old glob would have). - SERP keys keep keyword order: positions come back in request order. - Bulk analysis/metadata use the configured TTLs (ttlFor()). - DocumentationGenerator keeps a schema's minimum/maximum and takes enums and defaults from the schema; the name-keyed tables gave comment status the post statuses and an invalid default "publish". Docs regenerated. Tests: 9 new (8 failed before the fix); suite 2808 passed, 0 failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## [4.1.5](v4.1.4...v4.1.5) (2026-10-12) ### 🐛 Bug Fixes * **seo:** cache SEO results for their TTL in seconds, keyed by every input ([#273](#273)) ([b0711c6](b0711c6))
|
🎉 This PR is included in version 4.1.5 🎉 The release is available on: Your semantic-release bot 📦🚀 |



Pull Request
Description
The SEO tools gave cache TTLs in seconds (
SEO_CACHE_*_TTL,SEOCacheManager.DEFAULT_TTL,21600 // 6 hoursin every caller), but the baseCacheManagercounts milliseconds. As a result:Reproduced against the built code before the fix: an entry set with
ttl=21600had expired 30 s later.The short TTLs also hid cache keys that never covered their inputs. One
SEOCacheManagerserves every site:expected 3 to be 8before the fix.focusKeywords, and the audit key ignoredmaxPages.Found while reviewing #268.
Type of Change
Changes Made
SEOCacheManager.set()converts seconds to milliseconds in one place.ttlFor()exposes the configured TTLs, soSEO_CACHE_*_TTLnow reach the tools; before,SEOToolspassed hard-coded literals.seoResultKey()builds every key from all call parameters (sorted, withoutforce), plus the post'smodified_gmtfor post-based results. Post-based tools fetch the post before the cache lookup.analysisType(previously accepted but ignored) narrows recommendations and detail sections to its area: readability →content, keywords →keyword, structure →structure+technical. Score and metrics stay whole-post.auditType(previously ignored) selects the audit sections;technicalincludes site architecture.wp_seo_site_audit:maxPagesgetsminimum: 1, maximum: 100, matching its description. The never-implementedincludeExternalLinksis removed (checking external links would also mean fetching arbitrary URLs). API docs regenerated.src/cache/AGENTS.md(TTL units, key builder) andsrc/tools/AGENTS.md(contract) updated.Related Issues
Related to #268 (review follow-up).
Testing
Tests Performed
npm test): 2778 passed before, 2799 passed after, 0 failednpm run build),typecheck,lint,format:check,security:scanmain, each for the reason it names. The other 3 are guards that pass both ways: thefullanalysis, andauditTypeomitted orfull.Test Cases
tests/cache/SEOCacheManager.test.js: a 21600 s entry lives 6 h (not 21.6 s); 6 h default; 1 h audit.tests/tools/seo/SEOToolsCaching.test.js:maxPages, cached for 1 h,forcebypasses (asserted step by step: a total alone let the two bugs cancel out);analysisTypeandauditType;tests/tools/seo/BulkOperations.test.js: the same post ID on two sites stays apart; edits and other focus keywords are re-analysed.getPostcalls, since every call now fetches the post. Their intent is unchanged.Breaking Changes
None at runtime.
includeExternalLinksis gone from the schema; it never had an effect, and the argument validator strips unknown properties, so callers passing it are unaffected.Behaviour to know about: SEO results are now cached for their configured lifetimes. Keyword research is derived from site content and lasts 7 days (
SEO_CACHE_KEYWORDS_TTLto shorten); SERP estimates last 12 hours. Before this fix both expired within minutes.Documentation
docs/apiregenerated, DOX contracts)Additional Notes
Merge order with the performance follow-up PR doesn't matter: the two touch disjoint code and
git merge-treeis clean.🤖 Generated with Claude Code
Summary by Sourcery
Fix SEO caching to honor configured TTLs and isolate results by every input while aligning SEO behavior and API documentation with their contracts.
Bug Fixes:
Enhancements:
Documentation:
Tests: