Prepare the plugin for the WordPress.org directory - #6
Merged
Conversation
added 5 commits
August 4, 2026 19:18
Three things stood between this and a review approval. An unconnected install was not silent. `citecue_daily_sync` is scheduled at activation and refreshed the crawler registry from app.citecue.com whether or not anyone had connected the site — and activating a plugin is not consent to talk to a third party. The refresh now waits for a connection. Nothing is lost by waiting: an unconnected site is not serving crawlers, and the bundled token list is exactly what the registry would have refreshed. readme.txt did not disclose the service. Guidelines 6 and 7 want a SaaS-backed plugin to say what the service is, what leaves the site, when, and where the terms and privacy policy are — so `== External services ==` now walks through each endpoint, including the two that are easy to overlook: the daily keyless registry poll, and the loopback verification that requests the site's own llms.txt. The header claimed WordPress 6.8, three majors behind 7.0. The claim is now 7.0, and the test matrix genuinely reaches it: roots/wordpress-no-content was capped at ^6.8 while wp-phpunit already floated on ^7.0, so "latest" in CI was running a 7.0 test library against 6.9 core. Also points Plugin URI at the repository that actually exists.
WordPress.org will name this plugin citecue-ai-auto-fix, derived from the Plugin Name header, and that is the directory it installs into. The zip built here unpacked to citecue/, so a site that installed from a GitHub release and later moved to the directory would have run two copies of the plugin out of two directories instead of upgrading one. Only packaging and documentation are affected — the runtime resolves its own paths through plugin_basename() and never named a directory.
Left behind by the slug change: the build script's guard checks for citecue-ai-auto-fix/, not citecue/.
Two consequences of packaging under citecue-ai-auto-fix that the slug
change alone did not carry.
WordPress.org keys language packs by slug: it ships them as
citecue-ai-auto-fix-{locale}.mo, while every gettext call here asked for
the domain "citecue", so no translation would ever have loaded. The domain
now matches the slug in the header, in load_plugin_textdomain() and across
all 144 gettext calls. The seven other 'citecue' literals are deliberately
untouched — the menu slug, the settings group and the two $_GET['page']
comparisons are an internal identifier that the ?page=citecue URL and
settings_fields() both depend on, and the ingest payload's 'plugin' key is
read by CiteCue. The PrefixAllGlobals prefix stays "citecue" too: a PHP
class or constant name cannot contain a hyphen.
The second is the transition itself. A site that installed the v1.0.0
GitHub asset has this plugin at citecue/citecue.php; the directory's copy
arrives as citecue-ai-auto-fix/citecue.php and sits beside it rather than
upgrading it. require_once is no help — two directories are two paths — so
both copies would run their requires and the second would redeclare every
class, taking the site down. The main file now stands down when it finds
CITECUE_VERSION already defined, and names both directories in an admin
notice so the fix is obvious rather than a white screen.
No data migration is needed alongside it: settings, the connection and the
cron are keyed to the database, not to the directory, so deleting the old
copy leaves a working install.
…ices The two new tests called do_action( 'admin_notices' ), which runs every listener on the hook, not just the guard's. With a real WooCommerce installed one of those listeners reads get_current_screen() and dereferences it — null outside a genuine admin request — so the WooCommerce job failed inside FeaturesController while the seven other jobs passed. Emptying the hook before loading the second copy leaves the guard's callback as the only listener, which is what these tests are about. The test case restores $wp_filter wholesale in tear_down, so the removal does not outlive the test. Verified against a local MySQL in all three modes CI runs — plain, CITECUE_STUB_WOOCOMMERCE and CITECUE_WITH_WOOCOMMERCE with WooCommerce 11 installed — after first reproducing the exact failure.
This was referenced Aug 6, 2026
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.
Everything standing between this plugin and a WordPress.org review submission.
An unconnected install was not silent
citecue_daily_syncis scheduled at activation and refreshed the crawlerregistry from
app.citecue.comwhether or not anyone had connected the site.Activating a plugin is not consent to talk to a third party, and guideline 7
treats this as contacting an external server without authorization — it is a
common rejection reason.
The refresh now waits for a connection. Nothing is lost by waiting: an
unconnected site is not serving crawlers, and the bundled token list is exactly
what the registry would have refreshed. Both serving paths were already gated
on
is_delivery_configured(), so with this the plugin is silent end to enduntil an administrator connects it.
The new test leans on the HTTP mock throwing for any unqueued outbound call, so
a regression fails at the call site rather than as a downstream assertion. Two
existing
daily_synctests now have to connect first — which is itself theevidence that they were exercising the unconnected path before.
readme.txt did not disclose the service
Guidelines 6 and 7 want a SaaS-backed plugin to say what the service is, what
leaves the site, when, and where the terms and privacy policy live. The new
== External services ==section walks each endpoint, including the two thatare easy to overlook: the daily keyless registry poll, and the loopback
verification that requests the site's own llms.txt. It states explicitly that a
crawler lookup carries no visitor data — no IP, no cookies.
The tested-up-to claim was three majors stale, and untestable
The header said WordPress 6.8; current is 7.0.2. It now says 7.0, and the test
matrix genuinely reaches it:
roots/wordpress-no-contentwas capped at^6.8while
wp-phpunitalready floated on^7.0, so "latest" in CI was running a7.0 test library against 6.9 core. Bumping the constraint makes the two agree
and makes the readme claim true rather than aspirational.
Packaging follows the WordPress.org slug
WordPress.org derives the permanent slug from the Plugin Name header, so this
plugin will be
citecue-ai-auto-fixand will install into that directory. Thezip built here unpacked to
citecue/, so a site that installed from a GitHubrelease and later moved to the directory would have run two copies of the
plugin out of two directories instead of upgrading one. The builder, release
workflow and docs now use the wp.org slug.
Only packaging and documentation are affected — the runtime resolves its own
paths through
plugin_basename()and never named a directory.Also points
Plugin URIat the repository that actually exists.Verification
phpcs(WordPress-Core + WordPress-Docs + PHPCompatibilityWP) clean across all filescomposer run lintcleanbin/build-plugin-zip.shproducescitecue-ai-auto-fix.zip: 15 files, one correctly-named top-level directory, no dev filestest-lifecyclecases and the WP 7.0 resolution.Still on the human, before submitting
Contributors: citecuepoints at nothing — https://profiles.wordpress.org/citecue/ is a 404. Needs a real WordPress.org username.🤖 Generated with Claude Code