Repository navigation
fix: two template bugs that shipped in every scaffolded plugin - #4
Merged
Merged
Conversation
This was referenced Sep 16, 2026
Both found by playground-verifier running against the kit's own template. 1. Fresh clone fatals on activation. pl-example.php required vendor/autoload.php unconditionally, while .gitignore:10 excludes vendor/. The plugin as cloned -- which is what a git-based deploy installs -- died at require before anything else ran. Guarded with is_readable(). composer.json has no runtime requires at all; every package is in require-dev. So that line was buying nothing and costing a fatal. The guard is damage control; composer install --no-dev in the deploy lane is the real fix once a runtime dependency exists. 2. Every scaffolded plugin ships a class named PL_Example_Plugin. The CLI's replacement keys are case-sensitive and mutually disjoint, and PL_Example -- underscore-separated PascalCase, WordPress's convention for global class names -- matched none of the four existing entries. Two kit-scaffolded plugins active on the same site collide with "Cannot declare class PL_Example_Plugin". Fixed by deriving a classPrefix, and generalised by a test that scans every file the CLI rewrites and asserts each casing present has a replacement entry -- so a sixth casing fails here rather than in someone else's plugin. Also drops composer.json's autoload classmap. It covered the same classes as the resolver in the main file and registered first, so the resolver the plugin actually depends on might never run -- and a classmap that quietly covers for a broken resolver fails on someone else's machine, after they add a class and forget to re-dump. One loader, always exercised. Suite: 99 -> 109 assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pluginslab
force-pushed
the
fix/template-activation-and-substitution
branch
from
September 21, 2026 16:42
079721e to
b0b3fb5
Compare
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.
Stacked on #3 — base is
feat/playground-verifier, since both bugs were found by that agent and the justification below cites its output. Merge #3 first.Both of these shipped in every plugin the kit has ever scaffolded.
1. A fresh clone fatals on activation
pl-example.php:27requiredvendor/autoload.phpunconditionally..gitignore:10excludesvendor/. So the plugin as cloned — which is what a git-based deploy installs — died at require before anything else ran.get_option('active_plugins')after the attempt:array(). Activation aborted.The scaffolder runs
composer installimmediately, so the happy path always worked — which is why this survived this long. The failure is specifically deploying the generated repo via git without acomposer installstep, i.e. Ploi's default if nobody edits the deploy script.What makes it pure liability:
composer.json'srequireblock is{"php": ">=8.2"}. Every package isrequire-dev. The plugin has zero third-party runtime dependencies, and the verifier confirmed it runs completely with that line gone.Now guarded with
is_readable(). The guard is damage control;composer install --no-devin the deploy lane is the actual fix once a runtime dependency exists, and the comment says so.2. Every scaffolded plugin ships a class named
PL_Example_PluginThe CLI's replacement keys are case-sensitive and mutually disjoint.
PL_EXAMPLEdoes not matchPL_Example; neither matchesPLExample. The template's bootstrap class uses underscore-separated PascalCase — WordPress's convention for global class names — which had no entry, so it shipped verbatim.Cosmetic in isolation. Fatal when two kit-scaffolded plugins are active on the same site:
Cannot declare class PL_Example_Plugin.Fixed by deriving a
classPrefix(acme-order-tracker→Acme_Order_Tracker). More usefully, generalised:tests/cli/test-substitutions.shscans every file the CLI will rewrite, collects each casing of the example identity present, and asserts each has a replacement entry. A sixth casing introduced by a future template edit fails there instead of in someone else's plugin.Negative control: removing the entry turns the suite red and names the offending file.
Also: dropping the Composer
autoloadclassmapNot a bug report — a judgement call, and the one I'd most like challenged.
The v1.0.2 fix added both a
classmapincomposer.jsonand the WordPress-style resolver in the main file. Composer's registers first, so the resolver the plugin actually depends on may never execute. The verifier had to give a scratch copy a 56-byte no-opautoload.phpto establish the resolver works at all, because the classmap would have covered for a broken one.A classmap that quietly covers for a broken resolver is worse than no classmap: it fails on someone else's machine, after they add a class and forget to re-dump. One loader now, always exercised.
Argument against: a
composer.jsonwith noautoloadkey looks wrong to a WordPress developer skimming the template, and this file's job is partly to teach. There's a comment explaining the absence, but a comment explaining an absence is weaker than one explaining a presence. Happy to revert this hunk if you'd rather keep belt-and-braces.Verification
./scripts/quality.shgreen, 109 assertions.composer validatepasses.composer dump-autoloadconfirms the plugin's classes are no longer in the classmap, so the resolver is genuinely the only path.Caveat:
phpunitstill doesn't run in this repo (nophpunit.xml), so dropping the classmap is unverified against the test suite. The runtime evidence is stronger than a unit test would be, but it isn't the same thing.🤖 Generated with Claude Code