Skip to content

Prepare the plugin for the WordPress.org directory - #6

Merged
henry-mosh merged 5 commits into
mainfrom
feat/wordpress-plugin-publish-763c29
Aug 6, 2026
Merged

Prepare the plugin for the WordPress.org directory#6
henry-mosh merged 5 commits into
mainfrom
feat/wordpress-plugin-publish-763c29

Conversation

@henry-mosh

Copy link
Copy Markdown
Collaborator

Everything standing between this plugin and a WordPress.org review submission.

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.
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 end
until 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_sync tests now have to connect first — which is itself the
evidence 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 that
are 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-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. 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-fix and will install into that directory. 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. 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 URI at the repository that actually exists.

Verification

  • phpcs (WordPress-Core + WordPress-Docs + PHPCompatibilityWP) clean across all files
  • composer run lint clean
  • bin/build-plugin-zip.sh produces citecue-ai-auto-fix.zip: 15 files, one correctly-named top-level directory, no dev files
  • CodeRabbit review: 0 findings
  • The WordPress integration suite was not run locally — the Homebrew MySQL on this machine has no initialized datadir, and setting one up seemed heavier than warranted. CI is the check on that, and it is the main thing to watch on this PR, particularly the test-lifecycle cases and the WP 7.0 resolution.

Still on the human, before submitting

  • Contributors: citecue points at nothing — https://profiles.wordpress.org/citecue/ is a 404. Needs a real WordPress.org username.
  • Run the Plugin Check plugin against the built zip; the review team runs it.

🤖 Generated with Claude Code

henry-idingo 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.
@henry-mosh
henry-mosh merged commit df1855e into main Aug 6, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant