Skip to content

feat(contracts): add AbstractJob and the platform-extensibility abstracts - #85

Open
Adi-ty wants to merge 5 commits into
feat/ability-abstractsfrom
feat/job-and-platform-abstracts
Open

feat(contracts): add AbstractJob and the platform-extensibility abstracts#85
Adi-ty wants to merge 5 commits into
feat/ability-abstractsfrom
feat/job-and-platform-abstracts

Conversation

@Adi-ty

@Adi-ty Adi-ty commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds five shared framework contracts for background jobs and platform extensibility — each a pure seam with zero platform/vendor-specific code and zero runtime dependencies.

  • AbstractJob — a background-job base for Action Scheduler. Every scheduling method (schedule_async / schedule_at / schedule_recurring) guards on is_available() and degrades to a silent no-op when the library isn't loaded; handle() always runs off a plain WordPress action, so a job runs synchronously even without Action Scheduler. Args are always a single associative array.
  • AbstractLocalEnvironment — the seam behind a local dev environment (wp-env, a host's dev-env CLI): self-detection, debug-log location, available services, and whether the filesystem takes writes.
  • AbstractPlatformLog — reads recent host-level log entries and normalizes each to message / level / timestamp plus host file:line where the source line permits.
  • AbstractPlatformProfile — the named buckets of constraints a hosting platform imposes; every bucket defaults empty (permissive), so a profile that declares nothing imposes nothing.
  • AbstractVulnerabilityProvider — the seam for a WordPress-ecosystem CVE database; one subclass per source, sharing a single affects_version() comparison while returning unfiltered lookup results.
    The four platform abstracts are deliberately not Registrable — they register no WordPress hook and are constructed/queried on demand.

Closes

Closes rtcamp/wp-devtools#12

Changes

  • inc/Contracts/Abstracts/AbstractJob.php — new; schedule_async(), schedule_at(), schedule_recurring(), handle(), register_hooks() sync fallback, single-array args contract, Action Scheduler guarded behind is_available().
  • inc/Contracts/Abstracts/AbstractLocalEnvironment.php — new; get_name(), is_active(), get_debug_log_path(), get_available_services(), is_filesystem_writable().
  • inc/Contracts/Abstracts/AbstractPlatformLog.php — new; get_recent_entries(), is_available(), parse_error_log_line() → host file:line.
  • inc/Contracts/Abstracts/AbstractPlatformProfile.php — new; permissive default rule buckets, abstract get_slug() / get_name().
  • inc/Contracts/Abstracts/AbstractVulnerabilityProvider.php — new; plugin/theme/core lookup seams, affects_version(), is_available() default true.
  • tests/Contracts/Abstracts/ — five new TDD test classes (pure-logic on PHPUnit\Framework\TestCase).
  • tests/Fixtures/ActionSchedulerFakes.php — new; fakes for the guarded seam (global as_*() functions + toggleable ActionScheduler::$initialized).
  • .stubs/action-scheduler.stub — new, wired into phpstan.neon.dist scanFiles so PHPStan resolves the optional library.
  • tests/bootstrap.php — requires the Action Scheduler fakes.
  • Docs: docs/abstracts.md (background jobs + platform extensibility), docs/index.md, README hook-table count → seventeen; AGENTS.md and AI instruction files (ai/, .github/instructions/) synced via bin/sync-ai-instructions.js.

How I verified

❯ composer lint
DEPRECATED: Scanning CSS/JS files is deprecated and support will be removed in PHP_CodeSniffer 4.0.
The WordPressVIPMinimum.JS.Window sniff is listening for JS.
DEPRECATED: Scanning CSS/JS files is deprecated and support will be removed in PHP_CodeSniffer 4.0.
The WordPressVIPMinimum.JS.DangerouslySetInnerHTML sniff is listening for JS.
DEPRECATED: Scanning CSS/JS files is deprecated and support will be removed in PHP_CodeSniffer 4.0.
The WordPressVIPMinimum.JS.InnerHTML sniff is listening for JS.
DEPRECATED: Scanning CSS/JS files is deprecated and support will be removed in PHP_CodeSniffer 4.0.
The WordPressVIPMinimum.JS.StrippingTags sniff is listening for JS.
DEPRECATED: Scanning CSS/JS files is deprecated and support will be removed in PHP_CodeSniffer 4.0.
The WordPressVIPMinimum.JS.StringConcat sniff is listening for JS.
DEPRECATED: Scanning CSS/JS files is deprecated and support will be removed in PHP_CodeSniffer 4.0.
The WordPressVIPMinimum.JS.HTMLExecutingFunctions sniff is listening for JS.

....... 7 / 7 (100%)


Time: 609ms; Memory: 16MB

❯ composer analyse
Note: Using configuration file /Users/adi/rtproj/wp-php-toolkit/phpstan.neon.dist.
 35/35 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%


                                                                                                                        
 [OK] No errors   
 
 ❯ npm run wp-env start

> wp-framework@0.0.1 wp-env
> wp-env start

⚠ Warning: wp-env starts both development and tests environments by default.
This behavior is deprecated and will be removed in a future version.
To avoid this warning, add "testsEnvironment": false to your .wp-env.json.
The "env", "testsPort", and "testsEnvironment" options are also deprecated.
Use the --config option with a separate config file for test environments instead.

WordPress development site started at http://localhost:8888
MySQL is listening on port 51725
WordPress test site started at http://localhost:8889
MySQL for automated testing is listening on port 51726

 ✔ Done! (in 44s 993ms)
❯ npm run test:php

> wp-framework@0.0.1 pretest:php
> wp-env run tests-cli --env-cwd=wp-content/wp-framework-rt -- composer install --no-interaction

ℹ Starting 'composer install --no-interaction' on the tests-cli container. 

Composer could not detect the root package (rtcamp/wp-framework) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating optimized autoload files
Class ActionScheduler located in ./tests/Fixtures/ActionSchedulerFakes.php does not comply with psr-4 autoloading standard (rule: rtCamp\WPFramework\Tests\ => ./tests). Skipping.
Class ActionSchedulerFakeStore located in ./tests/Fixtures/ActionSchedulerFakes.php does not comply with psr-4 autoloading standard (rule: rtCamp\WPFramework\Tests\ => ./tests). Skipping.
Class rtCamp\WPFramework\Tests\Fixtures\LoaderRunner located in ./tests/Fixtures/LoaderFixtures.php does not comply with psr-4 autoloading standard (rule: rtCamp\WPFramework\Tests\ => ./tests). Skipping.
Class rtCamp\WPFramework\Tests\Fixtures\PlainRegistrable located in ./tests/Fixtures/LoaderFixtures.php does not comply with psr-4 autoloading standard (rule: rtCamp\WPFramework\Tests\ => ./tests). Skipping.
Class rtCamp\WPFramework\Tests\Fixtures\ConditionalAllowed located in ./tests/Fixtures/LoaderFixtures.php does not comply with psr-4 autoloading standard (rule: rtCamp\WPFramework\Tests\ => ./tests). Skipping.
Class rtCamp\WPFramework\Tests\Fixtures\ConditionalDenied located in ./tests/Fixtures/LoaderFixtures.php does not comply with psr-4 autoloading standard (rule: rtCamp\WPFramework\Tests\ => ./tests). Skipping.
Class rtCamp\WPFramework\Tests\Fixtures\ShareableRegistrable located in ./tests/Fixtures/LoaderFixtures.php does not comply with psr-4 autoloading standard (rule: rtCamp\WPFramework\Tests\ => ./tests). Skipping.
34 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
✔ Ran `composer install --no-interaction` in 'tests-cli'. (in 3s 320ms)

> wp-framework@0.0.1 test:php
> wp-env run tests-cli --env-cwd=wp-content/wp-framework-rt -- vendor/bin/phpunit -c phpunit.xml.dist

ℹ Starting 'vendor/bin/phpunit -c phpunit.xml.dist' on the tests-cli container. 

Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 9.6.34 by Sebastian Bergmann and contributors.

Random Seed:   1785831746
Warning:       No code coverage driver available

...............................................................  63 / 290 ( 21%)
............................................................... 126 / 290 ( 43%)
............................................................... 189 / 290 ( 65%)
............................................................... 252 / 290 ( 86%)
......................................                          290 / 290 (100%)

Time: 00:00.687, Memory: 50.50 MB

OK (290 tests, 597 assertions)
✔ Ran `vendor/bin/phpunit -c phpunit.xml.dist` in 'tests-cli'. (in 3s 406ms)

Acceptance criteria

Runtime behavior

  • AbstractJob exists; a trivial job schedules and runs via Action Scheduler when present
  • AbstractPlatformLog, AbstractPlatformProfile, the local-environment adapter, and AbstractVulnerabilityProvider exist as contracts/abstracts with no platform- or vendor-specific code, with tests for the seam
  • XHProfProfiler is available as rtCamp\WPDevTools\Support\XHProfProfiler and no-ops without the extension (already removed from this package)

Code quality

  • Lint — zero errors
  • Static analysis — zero errors
  • Tests pass

@Adi-ty
Adi-ty force-pushed the feat/job-and-platform-abstracts branch from 0a78af5 to ca4bb81 Compare August 4, 2026 08:36
@Adi-ty
Adi-ty marked this pull request as ready for review August 4, 2026 08:38
Copilot AI balanced review requested due to automatic review settings August 4, 2026 08:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds new shared, dependency-free framework contracts for background jobs and platform/extensibility seams, along with tests, PHPStan stubs, and documentation updates.

Changes:

  • Introduces AbstractJob (Action Scheduler–guarded scheduling with synchronous WP-hook fallback)
  • Adds four platform/extensibility abstract contracts (local env, platform logs, platform profiles, vulnerability providers)
  • Adds PHPUnit tests + Action Scheduler fakes + PHPStan stub wiring; updates docs and AI instruction references

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
inc/Contracts/Abstracts/AbstractJob.php New background-job base class that schedules via Action Scheduler when available.
inc/Contracts/Abstracts/AbstractLocalEnvironment.php New contract for local dev-environment detection and capabilities.
inc/Contracts/Abstracts/AbstractPlatformLog.php New contract for reading/parsing host log entries with a shared parser helper.
inc/Contracts/Abstracts/AbstractPlatformProfile.php New contract for platform constraint “buckets” with permissive defaults.
inc/Contracts/Abstracts/AbstractVulnerabilityProvider.php New contract for WP ecosystem vulnerability lookup + shared affects_version().
tests/bootstrap.php Loads Action Scheduler fakes for tests.
tests/Fixtures/ActionSchedulerFakes.php Provides global Action Scheduler API fakes used by AbstractJob tests.
tests/Contracts/Abstracts/AbstractJobTest.php Covers AbstractJob scheduling + hook registration behavior against fakes.
tests/Contracts/Abstracts/AbstractLocalEnvironmentTest.php Covers default behavior + overrides for local environment contract.
tests/Contracts/Abstracts/AbstractPlatformLogTest.php Covers parsing and availability behavior for platform log contract.
tests/Contracts/Abstracts/AbstractPlatformProfileTest.php Verifies permissive defaults and override propagation for profiles.
tests/Contracts/Abstracts/AbstractVulnerabilityProviderTest.php Tests affects_version() logic and default availability behavior.
phpstan.neon.dist Adds scanFiles for Action Scheduler analysis stub.
.stubs/action-scheduler.stub Declares Action Scheduler symbols for PHPStan without adding a dependency.
docs/index.md Updates abstract count and clarifies queried-on-demand abstracts.
docs/abstracts.md Documents AbstractJob and platform-extensibility abstracts.
README.md Updates abstract list/count to include new contracts.
AGENTS.md Documents optional-dependency seam approach for Action Scheduler.
ai/framework-php.instructions.md Updates agent guidance to prefer new abstracts/contracts.
.github/instructions/php.instructions.md Updates framework rules to include the new abstracts and their shape.
Suppressed comments (1)

docs/abstracts.md:1

  • Subject/verb agreement: the parenthetical refers to multiple abstracts, so it should read “follow the same shape” rather than “follows the same shape”.
# Abstracts — the base-class cookbook

Comment thread inc/Contracts/Abstracts/AbstractJob.php Outdated
Comment thread inc/Contracts/Abstracts/AbstractJob.php Outdated
Comment thread inc/Contracts/Abstracts/AbstractJob.php Outdated
Comment thread tests/Fixtures/ActionSchedulerFakes.php
Comment thread tests/Fixtures/ActionSchedulerFakes.php
Adi-ty added 3 commits August 5, 2026 12:08
…nd jobs

Lifts a background-job contract into wp-framework per rtCamp/wp-devtools#12.
Action Scheduler is never a dependency of this package — every scheduling
method guards on is_available() and no-ops when the library isn't loaded.
register_hooks() always attaches a plain WordPress action, so handle() runs
synchronously via do_action() even without Action Scheduler installed.

PHPStan resolves the as_*() functions via a hand-written analysis-only stub
(.stubs/action-scheduler.stub); tests exercise both branches deterministically
against fakes in tests/Fixtures/ActionSchedulerFakes.php rather than the real
library, since the real dependency belongs on the consuming plugin, not here.
Four vendor-neutral seams that later packages fill in with concrete
implementations (rtCamp/wp-devtools#12, item 3): AbstractPlatformLog reads
recent host log entries and normalizes them to level/message/file:line,
AbstractPlatformProfile carries the ruleset a readiness lens applies,
AbstractLocalEnvironment sits behind wp-env and other local stacks, and
AbstractVulnerabilityProvider fronts a CVE database.

None implement Registrable: they register no WordPress hook and are queried
on demand, so the Loader has nothing to drive. Every rule bucket and seam
defaults to empty or permissive, which keeps a bare subclass useful and lets
new buckets land later without breaking existing ones.

The base classes own the two pieces of shared logic worth centralising:
parse_error_log_line() handles both PHP error-log formats (FILE:LINE and
"on line LINE") and returns null for stack frames, and affects_version()
resolves a vulnerability's fixed_in against an installed version.
Abstracts cookbook gains a Background jobs section (Action Scheduler as an
optional seam, the single-array args contract, the schedule_*() surface) and
a Platform extensibility section covering the four queried-on-demand
contracts, each with a consumer example. Hook table, README, and docs index
updated; the count is now seventeen.

The AI-instruction class lists pick up all five, rule 5 gains the as_*()
scheduling functions, and AGENTS.md records that an optional third-party
library is a guarded seam rather than a dependency.
@Adi-ty
Adi-ty force-pushed the feat/job-and-platform-abstracts branch from ca4bb81 to 198ed44 Compare August 5, 2026 06:38
Copilot AI review requested due to automatic review settings August 5, 2026 06:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

tests/Fixtures/ActionSchedulerFakes.php:22

  • These global-namespace classes trigger Composer PSR-4 warnings during composer install (as shown in the PR's verification output). To avoid noisy installs (and potential CI strictness), consider either (1) moving the implementation under rtCamp\\WPFramework\\Tests\\Fixtures\\... and class_alias()-ing into the global names when bootstrapped, or (2) excluding this file from autoload-dev classmap generation (e.g., exclude-from-classmap) since it's manually require_once'd anyway.
if ( ! class_exists( 'ActionScheduler', false ) ) {
	final class ActionScheduler {
		/**
		 * Toggle to simulate the library being unavailable; reset via as_fakes_reset().
		 */
		public static bool $initialized = true;

inc/Contracts/Abstracts/AbstractJob.php:52

  • The hook callback parameter is strictly typed as array, which will throw a TypeError if the action is fired with a non-array argument (easy to do accidentally because this is a plain WP action). If you want the job hook to be robust, accept mixed $args = [] here and normalize to an array before calling handle() (while still keeping handle(array $args) as the contract).
		add_action(
			static::get_hook(),
			function ( array $args = [] ): void {
				$this->handle( $args );
			},
			$this->get_priority(),
			1
		);

inc/Contracts/Abstracts/AbstractJob.php:192

  • The uniqueness behavior description is inaccurate/incomplete: Action Scheduler uniqueness is based on hook + args + group (not just hook + group). Since this class intentionally wraps args as [ $args ], it would be clearer to document uniqueness in terms of the exact hook/group/wrapped-args tuple used by this abstraction.
	 * Whether a scheduled action should be unique.
	 *
	 * When true, Action Scheduler skips scheduling if a pending or running
	 * action already exists with the same hook and group.
	 *
	 * @return bool
	 */

tests/Fixtures/ActionSchedulerFakes.php:99

  • The recurring-action fake ignores $interval_in_seconds entirely, which can make future tests misleading (e.g., any logic that inspects the schedule cadence can't be exercised). Consider storing interval_in_seconds in ActionSchedulerFakeStore::$actions for recurring actions (even if nothing consumes it today) to keep the fake closer to the real API.
if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
	function as_schedule_recurring_action( int $timestamp, int $interval_in_seconds, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int {
		return ActionSchedulerFakeStore::add( $hook, $args, $group, $unique, $timestamp );
	}
}

Comment thread tests/Fixtures/ActionSchedulerFakes.php
Copilot AI review requested due to automatic review settings August 5, 2026 08:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Suppressed comments (2)

inc/Contracts/Abstracts/AbstractJob.php:49

  • The hook callback hard-types the first parameter as array. In WordPress, do_action() callers can pass any scalar/object; if a consumer accidentally calls the hook with a non-array first argument, this will throw a TypeError at runtime. To keep the contract (single associative array) without hard-failing, consider accepting an untyped $args and normalizing (e.g., treat non-arrays as []) before passing into handle().
		add_action(
			static::get_hook(),
			function ( array $args = [] ): void {
				$this->handle( $args );
			},

composer.json:38

  • Composer's PSR-4 compliance warnings in the PR output suggest these global-namespace fixture classes are still being scanned. exclude-from-classmap is not consistently honored under autoload-dev across Composer versions/configs; the more reliable fix is to ensure these fixtures are not under a PSR-4 scanned path (e.g., move them into a dedicated non-PSR-4 directory loaded via require_once), or place the exclude where Composer will actually apply it for the generated optimized autoloader in this repo’s setup.
	"autoload-dev": {
		"psr-4": {
			"rtCamp\\WPFramework\\Tests\\": "tests/"
		},
		"exclude-from-classmap": [
			"tests/Fixtures/ActionSchedulerFakes.php",
			"tests/Fixtures/LoaderFixtures.php"
		]

Comment thread tests/bootstrap.php Outdated
Comment thread tests/Fixtures/ActionSchedulerFakes.php Outdated
Copilot AI review requested due to automatic review settings August 5, 2026 09:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (4)

tests/Fixtures/ActionSchedulerFakes.php:45

  • These global-namespace classes live under the tests/ PSR-4 autoload-dev tree, which is what triggers the Composer PSR-4 compliance warnings shown in the PR’s verification output. To eliminate the warnings (and keep the fakes), consider moving the classes into the rtCamp\\WPFramework\\Tests\\Fixtures namespace and then class_alias()-ing them into the global names (ActionScheduler, ActionSchedulerFakeStore) when fakes are active, or alternatively relocating this file outside the PSR-4-mapped tests/ directory and continuing to require_once it from tests/bootstrap.php.
if ( ! class_exists( 'ActionScheduler', false ) ) {
	final class ActionScheduler {
		/**
		 * Toggle to simulate the library being unavailable; reset via as_fakes_reset().
		 */
		public static bool $initialized = true;

		public static function is_initialized( ?string $function_name = null ): bool {
			return self::$initialized;
		}
	}
}

if ( ! class_exists( 'ActionSchedulerFakeStore', false ) ) {
	/**
	 * In-memory scheduled-action store backing the as_*() fakes below.
	 */
	final class ActionSchedulerFakeStore {
		/** @var array<int, array{hook: string, args: array<mixed>, group: string, timestamp: ?int, priority: int}> */
		public static array $actions = [];

inc/Contracts/Abstracts/AbstractVulnerabilityProvider.php:58

  • The lookup return-shape docblocks require a fixed_in key (fixed_in: ?string), but affects_version() explicitly supports it being omitted (fixed_in?: ?string) and the tests cover the missing-key case. To keep the contract consistent for implementers, either update the return-shape annotations to make fixed_in optional (fixed_in?: ?string) or tighten affects_version()/tests to require the key.
	/**
	 * Return every known vulnerability for a plugin.
	 *
	 * @param string $slug Plugin slug, e.g. "akismet".
	 *
	 * @return array<int, array{title: string, type: string, fixed_in: ?string, references: array{url: string[], cve: string[]}, cvss: ?array{score: float, vector: string}}> Vulnerabilities.
	 */
	abstract public function get_plugin_vulnerabilities( string $slug ): array;

	/**
	 * Return every known vulnerability for a theme.
	 *
	 * @param string $slug Theme slug, e.g. "twentytwentyfour".
	 *
	 * @return array<int, array{title: string, type: string, fixed_in: ?string, references: array{url: string[], cve: string[]}, cvss: ?array{score: float, vector: string}}> Vulnerabilities.
	 */
	abstract public function get_theme_vulnerabilities( string $slug ): array;

	/**
	 * Return every known vulnerability for a WordPress core version.
	 *
	 * @param string $version Core version, e.g. "6.5.2".
	 *
	 * @return array<int, array{title: string, type: string, fixed_in: ?string, references: array{url: string[], cve: string[]}, cvss: ?array{score: float, vector: string}}> Vulnerabilities.
	 */
	abstract public function get_core_vulnerabilities( string $version ): array;

tests/Fixtures/ActionSchedulerFakes.php:47

  • The recurring-action fake accepts $interval_in_seconds but drops it on the floor (it’s neither stored nor used), which makes the fake diverge from the real API and can hide bugs in code that depends on interval behavior. Consider extending ActionSchedulerFakeStore::$actions to include an interval_in_seconds (or similar) field and passing it through from as_schedule_recurring_action() so future tests can assert on it accurately.
		/** @var array<int, array{hook: string, args: array<mixed>, group: string, timestamp: ?int, priority: int}> */
		public static array $actions = [];

		private static int $next_id = 1;

tests/Fixtures/ActionSchedulerFakes.php:109

  • The recurring-action fake accepts $interval_in_seconds but drops it on the floor (it’s neither stored nor used), which makes the fake diverge from the real API and can hide bugs in code that depends on interval behavior. Consider extending ActionSchedulerFakeStore::$actions to include an interval_in_seconds (or similar) field and passing it through from as_schedule_recurring_action() so future tests can assert on it accurately.
if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
	function as_schedule_recurring_action( int $timestamp, int $interval_in_seconds, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int {
		return ActionSchedulerFakeStore::add( $hook, $args, $group, $unique, $timestamp, $priority );
	}
}

@Adi-ty
Adi-ty requested a review from AnuragVasanwala August 5, 2026 09:52
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.

2 participants