WP Manifestindependent plugin directory

Kumki

A mahout starter plugin: the durable half of a site — content types, fields, storage and admin screens — installed as the site's root of record.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/iniznet/kumki/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Kumki — a mahout starter plugin

The durable half of a WordPress site, as a starter. Content types, field groups, custom tables, admin screens, REST routes and the migrations that build the schema — installed once, and still there after a theme switch.

Stack: WordPress 7.1+ · PHP 8.4+ · Composer · PHPUnit · PHPStan max · Psalm (taint) · Rector · PHP-CS-Fixer · Vite (build only)

Generated by mahout-scaffold (mahout new <slug> --host=plugin). AGENTS.md is the working contract; it is the complete public one, and every rule in it is enforced by a gate in this repository.

Install

composer install            # the family resolves over VCS; no Packagist account needed
npm install && npm run build

Then activate the plugin. Activation runs the migrations that create the field tables, which is the plugin's equivalent of the theme's after_switch_theme path — a plugin has an activation hook, and mahout-db attaches its run to whichever one the host owns.

Before any gate will run, copy the test configuration:

cp tests/wp-tests-config.php.dist tests/wp-tests-config.php

What lives here

Path Owns
app/Bootstrap.php the composition root: every provider, named once, and the claim on the process
app/Providers/ hook attachment, assets, content registration, the field seam, the admin seam
app/Features/<Name>/ one vertical slice: schema, module, repository, mapper, data
app/Components/ typed props to markup — a block's render target, nothing else
config/content-types.php the post types, taxonomies and REST routes this plugin registers
config/fields.php the field groups and option screens, with a StorageTarget per field
config/entries.php the Vite entry list
uninstall.php the deletion policy, which is deliberately not a guess

The worked example

Offices is the feature this starter exists to demonstrate. Read it in this order, because each file answers a different question a new host asks.

File The question it answers
config/content-types.php why a plugin rather than a theme: a post type declared here survives switching the theme
config/fields.php when a field is Meta and when it is Table — the same group carries both, plus a queried repeater, and the file says why each one is where it is
app/Features/Offices/OfficesRepository.php the read path: FieldReader::prime() filed before mapping, the two-phase filtered read, and the aggregate that saturates at its ceiling
app/Features/Offices/OfficeMapper.php + OfficeData.php where a WP_Post stops existing and a typed value starts
app/Features/Offices/OfficesModule.php how a feature declares its collaborators by contract id and never reaches for a container
app/Providers/EditorProvider.php the field seam: panels and screens read from config, bound under the package's contracts, groups registered on the package's own hook
tests/Integration/OfficesTest.php the claims above, executed — including the boundary a declaration moves: a repeater that states its maximum is primed with the rest of the page, and one that states nothing pays per object

Delete the two config entries and the feature stops existing: nothing else in the starter refers to it except the module line in app/Bootstrap.php. That is the test of whether the example is a worked example or a fixture.

Gates

composer check     # format, stan, psalm taint, arch, rector, test, hooks, i18n, config, doctor

composer check must pass before every commit. composer doctor asserts the installation around this plugin as well as its own assembly, and it fails when a second host on the same site also carries the packages: one process has one composition root (mahout-kernel ADR-0007), so a theme that installs the family and a plugin that installs the family are alternative assemblies, not two layers of one site.

One finding is a date rather than a defect. config:check reports that the family is locked to a path repository, and it is right to: the lock this working copy resolves is produced by composer.dev.json, whose repositories are directories on this machine, and a committed lock must resolve over a committed repository. The lock cannot be produced until the packages carry their first 1.0 tags, because the manifests require ^1.0 over VCS and no such ref exists yet. So: develop with COMPOSER=composer.dev.json, never commit the lock that resolves, and expect this one line to go away at the first tag — which is also the moment the generated starters become installable.

Choosing between this and the theme starter

The theme owns presentation: the template hierarchy, theme.json, design tokens, navigation, and the Surfaces a request resolves to. This plugin owns what must survive that being swapped. A site installs the packages in exactly one of them.