Point the licence block at the address the FSF actually uses Sixteen plugins told a reader to write to the Free Software Foundation at 59 Temple Place, Suite 330 -- an address the FSF left in 2005, and which survives across most of the WordPress plugin directory purely because the block gets copied rather than read. Three said 51 Franklin St: the right building, with the street name abbreviated. Neither spelling is what the FSF publishes with GPL-2.0 today. Nineteen files have to agree either way, so converging costs the same in both directions and only one direction is also true. This is a postal address the paragraph directly instructs a reader to write to. Nothing else in the block differed anywhere in the collection -- it was this line and only this line, which is what made it so easy to miss. §3.1 now reproduces the block in full instead of eliding its tail with a "…", and bin/verify.py compares it byte for byte. The elision is how this drifted: the header check that was meant to cover the licence read only the "License:" field, three lines above the part that was wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N1KNA2t61SoWKZamqPF5sx
Recent Commits to wp-pluginsused:master (6)
-
Point the licence block at the address the FSF actually uses
-
Read the raw row, not one register_setting() can synthesise
Read the raw row, not one register_setting() can synthesise On the admin_init path -- the path every real update takes, because activation hooks do not fire when a plugin is updated -- the migration deleted the owner's hidden-plugins list and wrote nothing in its place. register_setting() is passed a 'default', which installs a default_option_wp_pluginsused_options filter, and register_settings() is hooked to admin_init ahead of maybe_upgrade(). So by the time migrate() runs, a bare get_option() answers with the defaults array and never with false. The "there is no current row yet" branch was therefore never taken, while the delete_option() a few lines below ran regardless. Passing an explicit default defeats the registered one: filter_default_option() returns early when a default was passed. Reactivating repaired it, which is exactly why it survived. WP-CLI never runs register_setting(), so the branch is taken there and every existing test -- all of which reach the migration through activation -- passed on the bug. The new test registers the setting first, which is the only difference between the two paths. Verified by putting the bare get_option() back: the new test fails with "The migration wrote no settings row at all." Severity is bounded by history rather than by the code: 1.50 stored nothing at all, so LEGACY_OPTION only exists on an install that ran an unreleased 2.0.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N1KNA2t61SoWKZamqPF5sx
-
Point AGENTS.md at CLAUDE.md rather than copying it
Point AGENTS.md at CLAUDE.md rather than copying it A tool looking for AGENTS.md now finds its way to the briefing instead of finding nothing. It is a pointer and stays one: guidance copied into two files is two files that disagree, and the copy is always the one nobody updates.
-
Write down what the source cannot say about this plugin
Write down what the source cannot say about this plugin The briefing a competent stranger needs before touching it: what it does, what it stores, what couples it to its siblings, and the things that look wrong and are deliberate. Everything derivable from reading the code is left out, and so is anything STANDARDS.md already says -- restating a collection-wide rule per plugin only creates nineteen places for it to drift.
-
Extend one shared metadata base instead of nineteen copies
Extend one shared metadata base instead of nineteen copies Nineteen files of 298 to 666 lines, 14 to 33 tests each, eleven tests in common and four that nine plugins had never received. Each read reasonably on its own, which is why nobody noticed that wp-polls -- about to ship -- had never been checked for a licence file. They now extend Plugin_Metadata_TestCase, wired by two lines in bootstrap.php: a class_alias, because the copies must be byte-identical while each plugin's base class has a different name. Every copy hashes the same, and that check belongs in the workflow rather than in somebody's memory: three defects were found in the base AFTER it had been copied to eighteen plugins, including an assertion that quietly depended on the slug sorting after uninstall.php -- true of every wp-* plugin and false of freemyinternet. Reading the nineteen as one turned up three plugins mishandling the shared WP-Stats rows, two of them on the uninstall path. None was visible from inside its own plugin.
-
Add a Playwright suite, UNVERIFIED
Add a Playwright suite, UNVERIFIED Written by an agent that hit a session limit before running it. Nothing here has been seen green: treat every assertion as a claim, not a fact, and run bin/test-e2e.sh before believing any of it. Committed rather than left loose only so the work survives. It must not be pushed until it has passed, because the last time unverified specs went out they took CI red across five repositories.
-
Say what changed and stop explaining WordPress to the reader
Say what changed and stop explaining WordPress to the reader The Upgrade Notice sections had grown a preamble apiece -- "a major release", "five things are worth knowing before you update" -- and then opened with a paragraph explaining that an old site will not be offered the update, which is WordPress's behaviour rather than anything this plugin does, in seven different phrasings across the collection. What a reader wants from this section is the list of things that will break and what to do about each; everything else was standing between them and it. Every section now opens with one line naming the requirement and goes straight into the changes. The reassurance, the closing "there is nothing to do", and the instructions on how to ask a host about PHP are gone. The technical content is unchanged: every renamed hook, moved option row, replaced template and changed URL is still named, because that is the part somebody actually needs. wp-polls gains a line it should always have had: the stored XSS is the reason to take that release, and it was recorded only in the changelog. 9,600 words to 5,900.
-
Keep the changelog to the current major
Keep the changelog to the current major Nineteen READMEs carried up to thirty entries each, back to 2006 in places: 308 entries across the collection describing point releases of versions nobody is running. What a reader needs from a changelog is what changed in the version they are about to install, and what will break for them -- and the second of those has lived in Upgrade Notice since the standard was written. Nothing else in the file moved; the diffs are deletions only. test-metadata.php also learns to skip artifacts/, where Playwright writes traces and screenshots. Without it the first plugin to gain an e2e suite starts failing the every-directory-has-an-index.php test, and only for whoever has run that suite locally -- so CI stays green and the failure looks like it came from whatever was touched that day.