- WordPress: 6.9 or higher
| Component | Version | Policy |
| --- | --- | --- |
| PHP | 8.2 minimum, 8.3 target | CI tests both. Code must pass on both. |
| WordPress | 6.9 floor (Abilities API), current stable ceiling | Rolling: N and N-1 minors supported. Update CI when WordPress releases. |
| OS | Ubuntu 24 LTS | CI runners use Ubuntu 24. Do not assume Ubuntu 22 or Debian. |
| MariaDB | Current stable | Provider-agnostic. No provider-specific extensions without abstraction layer. |
| Node.js | 20 LTS | |
| TypeScript | 5.0+ strict | Required for all new JS. JavaScript-only files are legacy. |
| React | 18 | Ships with
@wordpress/scripts. Do not upgrade independently. |
Version numbers in this file are updated when the system is updated. The policy --- current stable, rolling window --- does not change.
src/ ← all authored source code (PHP, TypeScript)
src/ts/ ← TypeScript source
assets/ ← compiled/built output only (JS bundles, CSS, images)
tests/ ← mirrors src/ structure exactly
docs/ ← documentation only
mu-plugins/ ← WordPress loader entry point only
engines/ ← JSON runtime engine schemas (where applicable)
acf-json/ ← ACF field group JSON (where applicable)
Compiled output goes to assets/ --- never into src/.
Starisian\Sparxstar\{RepoAbbreviation}\{Subdirectory}
| Repository | Root Namespace | Hook Prefix |
|---|---|---|
| sparxstar-ouroboros-integrity | Starisian\Sparxstar\Infrastructure |
sparxstar_ouroboros_ |
| sparxstar-helios-trust | Starisian\Sparxstar\Helios |
sparxstar_helios_ |
| sparxstar-sirus-context | Starisian\Sparxstar\Sirus |
sparxstar_sirus_ |
| sparxstar-sky-eshu | Starisian\Sparxstar\Sky |
sparxstar_sky_ |
| sparxstar-mehns-dve-core | Starisian\Sparxstar\Mehns |
sparxstar_mehns_ |
| sparxstar-dheghom-dve-core | Starisian\Sparxstar\Dheghom |
sparxstar_dheghom_ |
PSR-4 --- directory structure under src/ maps exactly to namespace. One class per file. File name matches class name exactly.
Every repository maintains ai_manifest.json in the root. Check it before creating any symbol. Update it when any symbol is added, removed, or renamed. Format: { repository, version, namespace, symbols: [{ symbol, type, owner, path }] }
| Tool | Config file | Command |
|---|---|---|
| PHPStan | phpstan.neon |
composer run analyse |
| PHPCS | phpcs.xml |
composer run lint / composer run lint:fix |
| PHPUnit | phpunit.xml |
composer run test:unit |
| ESLint | .eslintrc.js |
pnpm run lint:js |
| Prettier | .prettierrc.js |
pnpm run format |
| Jest | jest.config.js |
pnpm test |
| @wordpress/scripts | webpack.config.js |
pnpm run build / pnpm run start |
Package manager: pnpm --- pnpm-lock.yaml is the lockfile. Do not use npm or yarn. Presence of package-lock.json or yarn.lock is a CI failure.
PHPStan levels by repo type:
- Infrastructure repos (Ouroboros, Helios, Sirus): level 5
- Application/DVE repos (Sky, Mehns, Dheghom): level 4 minimum, level 5 target
Sirus is the cross-repo authority layer. No repo may independently determine authority, context, or applicable rules.
Before any governed action:
context = Sirus::resolveContext(request)
authority = Sirus::resolveAuthority(caller)
if context is null OR authority is null:
FAIL CLOSED
return error
do NOT execute action
do NOT guess
do NOT fallback
Sirus calls per request: 1 preferred, 2 hard cap. Response cache TTL: 30s maximum. Cross-user context reuse: forbidden. Long-lived authority caching: forbidden.
Sirus provisional stub pattern --- use this when Sirus package is not yet available:
// PROVISIONAL --- replace with Sirus package import when available
// This interface must be satisfied exactly --- it is the contract Sirus implements
interface SirusClientInterface {
public function resolveContext(mixed $request): ?SirusContext;
public function resolveAuthority(mixed $caller): ?AuthorityRecord;
}
Mark all provisional Sirus stubs with // PROVISIONAL so they are findable.
| FAIL | Governed action without preceding Sirus call |
|---|---|
| FAIL | Sirus output modified or overridden downstream |
| FAIL | Local permission check without Sirus delegation |
Every PHP file must begin with:
<?php
declare(strict_types=1);
declare(strict_types=1) before any docblock. Intentional PSR-12 deviation --- enforced in phpcs.xml.
| FAIL | Condition |
|---|---|
| FAIL | PHP file missing declare(strict_types=1) |
| FAIL | Function missing typed parameters or return type |
| FAIL | Raw superglobal access without sanitization |
| FAIL | Direct SQL string interpolation |
| FAIL | SELECT * in any query |
| FAIL | Unbounded query without LIMIT |
| FAIL | Governed action without Sirus call |
| FAIL | Governed action without ability check (current_user_can()) |
| FAIL | Governed action without consent verification |
| FAIL | empty() --- use === null, === '', or count() === 0 |
| FAIL | Loose comparison == or != --- use === and !== |
| FAIL | @phpstan-ignore without fixing the code |
| FAIL | var_dump(), print_r(), or error_log() in committed code |
| FAIL | Script or style enqueued globally without conditional guard |
| FAIL | Cache invalidation or event emission before DB commit confirmed |
| FAIL | Client-supplied timestamp used for ordering or conflict resolution |
| FAIL | Direct provider-specific API call without abstraction layer |
| FAIL | wp_die() in platform boot code --- use exit(1) |
Additional PHP rules:
- All classes
finalunless designed for extension - Constructor injection only --- no service locators, no
global - No
mixedexcept where a third-party contract forces it - No static state except constants
- Write order on every write: DB commit → Redis invalidation → edge purge → event emission
Config files:
- ESLint:
@wordpress/eslint-pluginbase config - Prettier:
@wordpress/prettier-config - TypeScript:
strict: true,target: ES2020,outDir: assets/ - Build:
@wordpress/scripts--- do not configure webpack independently
| FAIL | Condition |
|---|---|
| FAIL | var used anywhere |
| FAIL | Default export --- use named exports everywhere |
| FAIL | any in TypeScript --- use unknown and narrow |
| FAIL | Raw fetch or XMLHttpRequest for WordPress REST --- use @wordpress/api-fetch |
| FAIL | API call without timeout (AbortSignal.timeout(5000) minimum) |
| FAIL | Event listener without throttle or debounce |
| FAIL | Continuous interval without bounded execution |
| FAIL | JS bundle exceeds 150 KB gzipped |
| FAIL | Blob in memory exceeds 5 MB |
| FAIL | Sensor active beyond 5000ms without auto-disable |
| FAIL | Infinite retry loop --- max 3 attempts with exponential backoff |
| FAIL | UI blocked during network operation |
| FAIL | pnpm-lock.yaml absent when package.json present |
| FAIL | package-lock.json or yarn.lock present |
WordPress JS standard:
@wordpress/datafor cross-component state --- not Redux directly@wordpress/i18nfor all user-visible strings --- no hardcoded English@wordpress/api-fetchfor all REST API calls- React 18 --- do not upgrade independently of WordPress core
Execution budget --- hard caps:
| Metric | Limit |
|---|---|
| Max main-thread block | 50ms |
| Max event handler rate | 10 Hz (production), 20 Hz (development) |
| Concurrent media streams | 1 |
| Blob in memory | 5 MB |
| Media buffers | 2 max |
Pattern: global CSS with BEM naming. No CSS Modules. CSS custom properties use --spx- prefix. All design values via custom properties only.
| FAIL | Condition |
|---|---|
| FAIL | CSS bundle exceeds 50 KB |
| FAIL | Physical direction properties used (see table) |
| FAIL | outline: none or outline: 0 without replacement focus indicator |
| FAIL | Hardcoded colour, font size, or spacing value |
| FAIL | Blur filter or heavy shadow in production CSS |
Logical properties --- RTL compliance:
| Not permitted | Required instead |
|---|---|
margin-left / margin-right |
margin-inline-start / margin-inline-end |
padding-left / padding-right |
padding-inline-start / padding-inline-end |
left / right (position) |
inset-inline-start / inset-inline-end |
border-left / border-right |
border-inline-start / border-inline-end |
BEM: .spx-component__element--modifier
- All interactive elements keyboard navigable
- All form inputs have visible label or
aria-label - All images have meaningful
altoralt=""if decorative - Colour is never the sole means of conveying information
- Focus order matches visual order
- Dynamic content updates announced via
aria-liveor focus management
Bandwidth is a financial cost. These are billing constraints, not quality preferences. These constraints apply at the layer that processes or validates media --- not at every layer. Sky Eshu enforces them at the pipeline orchestration boundary. Starmus enforces them at capture.
| FAIL | Condition |
|---|---|
| FAIL | Audio sampleRate > 16000 |
| FAIL | Audio channels > 1 |
| FAIL | Audio bitrate > 32 kbps |
| FAIL | Audio format is WAV or uncompressed PCM --- Opus or AAC-LC only |
| FAIL | Video width > 640 or height > 480 |
| FAIL | Video fps > 15 |
| FAIL | Video bitrate > 800 kbps |
| FAIL | Video codec is not H.264 Baseline |
| FAIL | Recording starts automatically without explicit user action |
| FAIL | Condition |
|---|---|
| FAIL | Upload chunk > 512 KB |
| FAIL | Upload without chunk checksum verification |
| FAIL | Upload without UUID |
| FAIL | Full-file upload endpoint present |
Atomicity required --- upload and DB write succeed together or both roll back.
| FAIL | Condition |
|---|---|
| FAIL | Query depth > 5 |
| FAIL | N+1 query pattern in resolver |
| FAIL | Governed resolver without Sirus call |
| FAIL | Unbounded list query without explicit limit |
| Limit | Value |
|---|---|
| Max request CPU time | 2 seconds |
| Max request size | 5 MB |
| Max API response | 100 KB |
| Max concurrent ops per user | 1 mutation, 1 upload |
| Max JS bundle | 150 KB gzipped |
| Max CSS bundle | 50 KB |
| FAIL | Condition |
|---|---|
| FAIL | Async job without retry policy (max 3, exponential backoff) |
| FAIL | Async job without defined timeout |
| FAIL | Synchronous media processing > 2 seconds |
| FAIL | Failed job silently discarded --- must move to dead-letter queue |
| FAIL | Condition |
|---|---|
| FAIL | Breaking schema change deployed without feature flag |
| FAIL | DB migration that is not rollback-safe |
| FAIL | IndexedDB usage without defined eviction policy (20 MB max, LRU) |
| FAIL | localStorage used without TTL or explicit cleanup (5 MB max) |
| FAIL | Cache invalidation before DB commit confirmed |
| FAIL | Client-supplied timestamp used for ordering |
PHP: PHPUnit 11+ --- class names end Test, files match class names, methods test_snake_case, no test depends on execution order, no DB state persists between tests, mock only what you own.
TypeScript: Jest 29+, ts-jest preset, pattern **/tests/**/*.test.ts.
1\. composer validate --strict
2. composer install --prefer-dist --no-interaction --no-progress
3. composer run lint (PHPCS --- errors block merge, warnings pass)
4. composer run analyse (PHPStan level 4/5 --- must pass)
5. composer run test:unit (PHPUnit --- must pass)
6. pnpm install --frozen-lockfile (if package.json present)
7. pnpm run lint:js (ESLint --- errors block merge)
8. pnpm run build (must produce output in assets/)
9. pnpm test (Jest --- must pass if present)
PHP matrix: 8.2 and 8.3 --- both must pass.
- No direct commits to
main - PR title:
{type}({scope}): {description}Types:featfixrefactortestdocschore - All CI checks must pass before merge
- One logical change per PR
Read this file before writing or reviewing any code. Apply every rule to every line. Do not apply conventions from other repositories or training data that conflict with this file. When a rule is ambiguous, apply the stricter interpretation. Check ai_manifest.json before creating any new symbol.