Skip to content

Conversation

@Ref34t
Copy link
Contributor

@Ref34t Ref34t commented Oct 7, 2025

Scaffold Initial Plugin Structure

Implements #24 – Foundational plugin structure for the WordPress AI Experiments plugin.

Overview

This work delivers the core architecture for a modular, feature-based AI plugin. Features can be
registered, toggled, and tested in isolation, and the repository now follows the conventions we’ll
reuse as we add more experiments.

What’s Included

Core Architecture

  • Plugin bootstrap (ai.php) – WordPress headers, version guards (PHP 7.4+, WP 6.7+), and
    initialization wiring.
  • Feature system – Registry refactored around a reusable Feature_Collection, maintaining enable/
    disable logic and conditional feature support.
  • PSR-4 autoloading – Composer-managed namespaces for WordPress\AI core code and feature modules.
  • Interface-driven design – Feature and Conditional_Feature interfaces ensure every experiment
    exposes the same surface.

Plugin Structure

ai/
├── ai.php
├── includes/
│ ├── Plugin.php
│ ├── Feature_Collection.php
│ ├── Feature_Registry.php
│ ├── Interfaces/
│ └── Abstracts/
├── features/
│ └── Example_Feature/
├── tests/
│ ├── bootstrap.php
│ ├── Integration/
│ │ ├── Features/
│ │ └── Includes/
│ └── Unit/
│ └── Includes/
├── docs/
│ ├── CONTRIBUTING.md
│ └── TESTING.md
├── phpcs.xml.dist
└── phpunit.xml.dist

Development Infrastructure

  • WordPress Coding Standards (PHPCS) plus PHPCompatibility checks.
  • Testing – PHPUnit configuration, bootstrap wiring, and refreshed integration/unit suites in PSR-
    4 directories.
  • Static analysis – PHPStan bootstrap and composer stan script with lightweight WordPress stubs.
  • Composer scripts – composer lint, composer format, composer stan, composer test.

Documentation

  • docs/CONTRIBUTING.md – Expanded developer guide covering setup, feature creation, testing, and
    contribution workflow.
  • docs/TESTING.md – Updated strategy with new test locations and commands.
  • README – Points to the new docs and outlines the development experience.

Key Features

Encapsulation

Every experiment lives under its own directory in features/, making reviews, toggles, and
migrations painless.

Extensibility

Third-party developers can add custom features by hooking into the registry:

  add_action( 'ai_register_features', function ( $registry ) {
      $registry->register_feature( new My_Custom_Feature() );
  } );

Feature Control

Features remain fully overridable:

  add_filter( 'ai_feature_enabled', function ( $enabled, $feature_id ) {
      return 'example-feature' === $feature_id ? false : $enabled;
  }, 10, 2 );

@jeffpaul jeffpaul added this to the 0.1.0 milestone Oct 7, 2025
@jeffpaul jeffpaul self-requested a review October 7, 2025 20:02
@jeffpaul jeffpaul linked an issue Oct 7, 2025 that may be closed by this pull request
7 tasks
@jeffpaul
Copy link
Member

jeffpaul commented Oct 7, 2025

@Ref34t thanks for picking up #24 and working on this PR! I see that the PR is still in draft, so is there more work you have pending or should this be considered ready for review?

@Ref34t
Copy link
Contributor Author

Ref34t commented Oct 8, 2025

@jeffpaul you're welcome. I'll assign it for review today after I finish my iteration on it.

@Ref34t Ref34t marked this pull request as ready for review October 8, 2025 15:09
@github-actions
Copy link

github-actions bot commented Oct 8, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.

Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jeffpaul <jeffpaul@git.wordpress.org>
Co-authored-by: swissspidy <swissspidy@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>
Co-authored-by: mindctrl <mindctrl@git.wordpress.org>
Co-authored-by: justlevine <justlevine@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@Ref34t
Copy link
Contributor Author

Ref34t commented Oct 8, 2025

@jeffpaul it's ready now

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ref34t Thank you for kick-starting this! I think a lot of it goes in the right direction, but a few pieces seem overly complicated or catering for theoretical problems that we don't know are relevant.

I encourage you to simplify the PR according to my feedback and break out all the documentation into separate PRs to streamline the review process and decouple documentation and marketing language from source code.

readme.txt Outdated
@@ -0,0 +1,89 @@
=== AI Experiments ===
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above, adding the readme.txt should be its own PR, also as it requires a completely different expertise to review.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I added the readme.txt file as some changes were made to README.md that are more properly formatted for the WPORG txt version. Happy to strip that out and roll into a separate PR if helpful.

@Ref34t Ref34t force-pushed the feature/scaffold-plugin-structure-issue-24 branch from eee2a66 to 99fe2cb Compare October 12, 2025 11:04
@jeffpaul jeffpaul requested a review from dkotter October 15, 2025 17:35
Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ref34t Getting close!

My last remaining concern is to make the feature abstraction a bit easier and also more error-proof to use.

- Add Invalid_Feature_Exception and Invalid_Feature_Metadata_Exception classes
- Implement validate_feature method in Abstract_Feature with metadata checks
- Refactor Feature_Loader to use new exceptions and validation
- Update bootstrap to handle exceptions with user-friendly notices
- Add comprehensive test coverage for validation scenarios
@Ref34t
Copy link
Contributor Author

Ref34t commented Oct 16, 2025

@felixarntz I've pushed a new commit covering the points you mentioned yesterday with some improvements over them. Here is what this last iteration does:

Introducing a comprehensive validation system for the plugin's feature architecture, ensuring all features have valid metadata before they can be loaded. The changes centralize metadata management in the Abstract_Feature base class and add proper exception handling throughout the feature loading pipeline.

Summary of Changes

New Exception Classes

  • Invalid_Feature_Exception - for invalid feature instances
  • Invalid_Feature_Metadata_Exception - for missing/invalid metadata

Abstract_Feature

  • Added final __construct() that validates and assigns metadata (id, label, description)
  • Added abstract load_feature_metadata() method that child classes must implement
  • Changed $enabled from protected to private
  • Made is_enabled() final

Feature_Loader

  • Wrapped feature loading in try-catch to handle validation exceptions
  • Returns WP_Error when features fail validation

Example_Feature

  • Implemented load_feature_metadata() returning feature metadata array
  • Removed direct property assignments (now handled by parent constructor)

bootstrap.php

  • Added exception handling that displays admin notices when Features fail to load

Copy link
Collaborator

@dkotter dkotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the work here @Ref34t! Overall this looks good to me, noting I'd expect there to be some changes as we actually start building things out but I feel this is a good base to start with

@jeffpaul
Copy link
Member

@dkotter I'll defer to you on merge order here between this and #46 to minimize conflicts

Move the master enable/disable hook to the `Feature_Loader` to act as a global "master switch" and simplify the `Abstract_Feature` class. This improves separation of concerns and clarifies the roles of the classes.
@dkotter
Copy link
Collaborator

dkotter commented Oct 21, 2025

@dkotter I'll defer to you on merge order here between this and #46 to minimize conflicts

I would merge this one in first as it contains all the scaffold work. And then I can rebase #46 to fix conflicts

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ref34t Thank you for the iterations, LGTM!

Just one minor thing that we can fix inline.

@felixarntz
Copy link
Member

@jeffpaul @dkotter @Ref34t Happy for this to be merged! I agree it should probably be merged before #46.

@mindctrl
Copy link
Contributor

I'm seeing a PHP notice due to translations being loaded too early.

PHP Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the ai domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.)

This happens when WordPress\AI\load(), which runs on plugins_loaded, registers the Example Feature and ends up running __() in Example_Feature::load_feature_metadata().

Maybe the feature initialization could be deferred to the init hook?

Comment on lines 161 to 176
try {
$registry = new Feature_Registry();
$loader = new Feature_Loader( $registry );
$loader->register_default_features();
$loader->initialize_features();
} catch ( \Exception $e ) {
_doing_it_wrong(
__NAMESPACE__ . '\load',
sprintf(
/* translators: %s: Error message. */
esc_html__( 'AI Plugin initialization failed: %s', 'ai' ),
esc_html( $e->getMessage() )
),
'0.1.0'
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 in regards to @mindctrl's comment, this should probably only happen on init:

  • Only before init the user gets initialized by default, and that influences translation strings. It's a best practice to not run any translation functions before the user is normally initialized.
  • It's common in WordPress for things to be registered on init, so this would tie in nicely with that practice.
  • It would enforce that no feature can break these patterns either. init is sufficiently early in the WordPress bootstrap process for probably any kind of interaction a specific feature may need to support.

Copy link
Contributor Author

@Ref34t Ref34t Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixarntz I went with John's approach, is this satisfying for you? 65139be

better readibility

Co-authored-by: Felix Arntz <flixos90@gmail.com>
* @return bool True if PHP version is sufficient, false otherwise.
*/
function check_php_version(): bool {
if ( version_compare( phpversion(), AI_MIN_PHP_VERSION, '<' ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small thing but when testing this PR against WP 6.9-beta1, using this constant set to 6.9 causes the plugin to show an admin notice and stops the bootstrap process.

AI plugin requires WordPress version 6.9 or higher. You are running WordPress version 6.9-beta1-61040-src.

I had to edit the value to make the plugin work during beta testing. Of course it's only a temporary inconvenience, but it made me wonder if we should use a different way, like checking if the wp_register_ability() function exists. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mindctrl I went with using a lower version for now until the release of 6.9, we can pump it up then

@justlevine justlevine self-requested a review October 22, 2025 21:06
Mohamed Khaled added 4 commits October 23, 2025 08:39
Move feature initialization from plugins_loaded to init hook by extracting
the try/catch block into initialize_features() function. This allows the
plugin to bootstrap dependencies on plugins_loaded for early access by
integrations, while features initialize at the appropriate init timing.
Remove the $features parameter from register_default_features() method.
This method should only register built-in default features. Custom features
should use the ai_register_features action hook instead.
Change AI_MIN_WP_VERSION from 6.9 to 6.8 to allow the plugin to work
with WordPress 6.9-beta1. Version comparison treats 6.9-beta1 as less
than 6.9, preventing the plugin from loading during testing.
Add isset() check before accessing title['site'] to prevent PHP warning
on front page where WordPress doesn't set the site key when
is_front_page() returns true.
@jeffpaul jeffpaul merged commit 188281c into WordPress:trunk Oct 23, 2025
4 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.

Scaffold the initial plugin structure

7 participants