Conversation
Contributor
|
Test on Playground |
Contributor
🔍 WordPress Plugin Check Report
📊 Report
|
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
232 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
377 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
381 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
388 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
📁 classes/suggested-tasks/data-collector/class-unpublished-content.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
103 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
📁 classes/suggested-tasks/data-collector/class-terms-without-posts.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
120 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_results()\n$query assigned unsafely at line 118. |
📁 classes/suggested-tasks/data-collector/class-yoast-orphaned-content.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
111 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_row()\n$query assigned unsafely at line 98. |
📁 classes/suggested-tasks/data-collector/class-terms-without-description.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
108 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_results()\n$query assigned unsafely at line 106. |
📁 classes/activities/class-query.php (2 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
71 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $table_name used in $wpdb->query()\n$table_name assigned unsafely at line 58. |
163 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $where_args used in $wpdb->get_results()\n$where_args assigned unsafely at line 153. |
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check
Contributor
✅ Code Coverage Report
🎉 Great job maintaining/improving code coverage! 📊 File-level Coverage Changes (7 files)🆕 New Files
📈 Coverage Improved
📉 Coverage Decreased
ℹ️ About this report
|
ilicfilip
force-pushed
the
filip/abilities-api
branch
from
September 18, 2026 11:00
19d91e4 to
0849e44
Compare
Adds progress-planner/complete-recommendation, which applies a recommendation either by provider ID or, with no argument, the highest-priority one that needs no value from the caller. Seventeen providers qualify. The set is an explicit list, not everything extending Tasks_Interactive: that base class also covers sending a test email (a diagnostic, not a fix), deleting terms, and rewriting permalinks, which flushes rewrite rules and changes every URL on the site. Each entry in Recommendation_Fixes was read individually and is listed because it writes one known setting and nothing else. Yoast's tasks had no server-side save path at all. They predate the AIOSEO integration and only ever got a UI affordance: the popover deep-links into Yoast's own settings screen and highlights the field. That is useful to a person and meaningless to an agent, which never loads the page. The setting and its value always come from that table and never from caller input, so no ability argument can reach an arbitrary option. Anything outside the list is returned with status "manual" and an admin URL rather than half-applied, and each SEO entry checks its plugin is active first. Completion is observed, not asserted: after writing, the provider decides whether the site now satisfies the task. Claiming otherwise would award points for work that did not happen. Because evaluation runs on admin_init, a just-fixed task is still pending in the database, so next-mode skips providers that already report themselves satisfied instead of picking the same one twice. There is no nonce, deliberately. An authenticated agent call is not a forged cross-origin form post, so manage_options and the fixed settings list are what bound this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ilicfilip
force-pushed
the
filip/abilities-api-write
branch
from
September 18, 2026 11:01
c626cd3 to
33607cb
Compare
Email delivery is the one check a site cannot verify for itself: WordPress knows wp_mail() returned true, not that anything arrived. The existing token solves that by living only in the delivered message, so presenting it back proves delivery. The token is delivered as the href of a "Click here" link, which works for a person who clicks and not for a person who wants to relay it. Reading it off the screen is impossible; copying it means right-clicking and pasting a 128-character URL. That matters now this question gets asked through an AI assistant rather than the dashboard. An assistant with mailbox access can read the token and needs nothing from the user. An assistant without one has to ask, and "tell it the code from the email" only works if the code is short enough to say out loud. So the email now carries both: the link, unchanged, and a four-character code shown as text. The code is deliberately weaker than the token, and the docblock on generate_task_confirmation_code() records why that is acceptable -- single use, 24-hour expiry, scoped to one user and one task, manage_options required, and rate limiting around it. Anyone who could satisfy all of that can complete the task from wp-admin anyway. The alphabet omits characters that are misread when spoken or retyped (0/O, 1/I/L, 5/S, 8/B), and comparison ignores case and surrounding whitespace, because a code that survives being read aloud is the entire point. A test asserts the alphabet holds across repeated generation. The email body was built in two places; it is now built once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds hello-world and sample-page, bringing the applicable set to 19. These are the first entries that remove something rather than change a setting, and they are treated differently for it: - Annotated destructive. The annotation describes what the ability can do, not what a given call does, so complete-recommendation as a whole is now destructive and a client prompts before any of it runs. - Confirm-only, so next-mode skips them. A daily unattended run must never be the thing that deleted something; they can only be applied by naming the provider. - Trashed, not force-deleted. The dashboard's JavaScript passes force=true and removes the post outright. An agent should leave a way back, and the completion check passes either way because it looks for a published post. What makes them defensible at all is that the target is not ambiguous: the data collector resolves the specific post WordPress ships, by slug with a title fallback, and no ID is ever taken from the caller. The success message says the content was trashed and can be restored, rather than reusing the settings wording. The new tests create and trash posts, which writes to the activities table. That table is custom, so WP_UnitTestCase does not roll it back, and post IDs are reused across tests -- a leftover row was being seen by an unrelated test that asserts a fresh post has no activity. The class now clears the table in tearDown. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings the applicable set to 22. These recommendations do not create a page. They ask whether the site has one, and record the answer plus which page it is. The interesting part is who decides. The plugin deliberately does not search for the page. Searching a real site for "about" returns everything whose content mentions the word: on the demo site that is eleven pages, including one called "Job opening", with the actual About page ranked ninth and titled "About Emilia" at /about-us/. Deciding which of those is the About page means weighing title, slug, hierarchy and navigation in whatever language the site is written in. A caller that can read the site's pages does that well; a title match in PHP would get it wrong quietly, and worse on non-English sites. So the division of labour is: the caller identifies the page, and this verifies the ID before writing. A missing ID, an ID that does not exist, a draft, or a post that is not a page are each a distinct error rather than a silent no-op, because "we recorded your About page" is worth being true. The page type comes from the table and never from the caller, and the allowed post types are derived from the hierarchical public ones rather than hardcoded to 'page', so a site serving these roles from a custom post type still works. They need a value, so next-mode skips them: there is no correct page to choose on the owner's behalf. One caveat is recorded in is_satisfied(). These providers do not override is_task_completed(), so satisfaction now falls back to should_add_task(). That can still read pre-write state within the same request, because page-type lookups are memoised in a static cache with no invalidation -- which is why the status distinguishes "applied" from "satisfied" instead of assuming they are the same. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
progress-planner/complete-recommendation, which applies a recommendation either by provider ID or — with no argument — the highest-priority one that needs no value from the caller.Stacked on #782. Review that first; this PR targets its branch, so the diff here is only the write surface.
The fixable set is vetted, not inferred
Seventeen providers qualify: six core settings (tagline, timezone, date format, search-engine visibility, and two comment settings), seven Yoast, four All in One SEO.
The list is explicit, not "everything extending
Tasks_Interactive". That inference is wrong, and I made it before checking: the same base class also covers sending a test email (a diagnostic, not a fix), deleting terms (irreversible), and rewriting permalinks (flushes rewrite rules and changes every URL on the site). Each entry inRecommendation_Fixeswas read individually and is listed because it writes one known setting and nothing else.Yoast's tasks had no server-side save path at all. They predate the AIOSEO integration and only ever got a UI affordance: the popover deep-links into Yoast's own settings screen and highlights the field. That is useful to a person and meaningless to an agent, which never loads the page — the underlying setting is a plain boolean.
aioseo-crawl-settings-feed-commentsis deliberately left out: it writes two settings, which the single-path model does not express.Safety
manualand an admin URL, never half-applied.manage_optionsand the fixed settings list are what bound this. Worth a reviewer's eye: that list is the real control.Completion is observed, not asserted
After writing the setting, the provider decides whether the site now satisfies the task. Claiming otherwise would award points for work that did not happen, and there is a distinct status for "setting changed but task not satisfied yet".
Because evaluation runs on
admin_init, a just-fixed task is still pending in the database. Next-mode therefore skips providers that already report themselves satisfied, instead of picking the same one twice — found by testing a repeated run, not by reasoning about it.Next-mode also skips any fix needing a value: there is no correct tagline to invent on the site owner's behalf.
Verified
Against a live WordPress 7.1 install with Yoast active:
blog_publicto0, called the ability, it wrote1and reportedcompleted. Called next-mode again and it moved on to the next task rather than repeating.remove_emoji_scriptsandremove_feed_authorsboth flipped through the ability and reportedcompleted. Both restored afterwards.manual; invalid timezone →progress_planner_invalid_timezonewith nothing written; missing value → rejected.check-csclean.Guessing the AIOSEO paths would have broken one:
media-pageswritesredirectAttachmentUrlsunderdynamicOptionsas a string, not a booleanshowunderoptions. The paths were read from each provider's own submit handler.🤖 Generated with Claude Code