Imported from vahid162/pinova (
.agents/skills/pinova-development/SKILL.md). Install upstream withnpx skills add vahid162/pinova --skill pinova-development. Copyright stays with the author.
Pinova Development
Work from a repository checkout or disposable worktree, never from an installed production copy of the plugin. Also establish compute isolation: a separate path, worktree, or Docker project still shares host resources. If the checkout is on a production or multi-tenant host, do not run dependency installation, full PHPStan, full wp-env/integration matrices, reproducible builds, or other unbounded PHP/Composer jobs there; use GitHub Actions or a dedicated resource-isolated development host. Limit local work on a shared live host to lightweight, bounded source checks after confirming headroom.
Start every task
- Read the root
AGENTS.md. Inspectgit status --short --branch, the current branch, recent commits, tags, and GitHub release state. Verify snapshots against Git. - Read the references needed for the task:
references/project-map.mdfor architecture, legacy identifier behavior, persistence, and source routing.references/quality-and-release.mdfor CI, tests, packaging, staging, and releases.references/logging.mdfor structured events, privacy, retention, diagnostics, correlation IDs, and operational review.- If identity migration documentation exists on the active 1.3 development branch, read it completely before migration, merge, resume, or rollback work.
- Classify the request as read-only review, diagnosis, implementation, migration, release, or production deployment. Permission for one class does not authorize another.
- Preserve unrelated changes. Never clean, reset, overwrite, migrate, publish, or deploy to simplify inspection.
- Establish the requested delivery boundary before reporting completion. For this project, a new version intended for installation or site testing is a GitHub-delivered release: local worktree changes and local ZIPs are intermediate evidence only.
Non-negotiable invariants
- A WordPress User ID is the account anchor. Never rewrite
wp_users.user_loginas a side effect of OTP login, profile/mobile editing, fallback resolution, or migration. - The Illuminate connection must be initialized on every fresh WordPress request before any Eloquent-backed service runs. Keep
utils/class-database.phpin Composerautoload.filesas well as the legacy classmap, keep its side-effect initialization guarded by a real WordPress database context, and havepinova.phpidempotently initialize it after loading Composer when the resolver is still absent. Assert the resolver immediately after plugin load in integration bootstrap and make the release build fail when the eager autoload entry is absent. - In 1.2.3, a valid physical
pinova_mobileuser-meta value is an explicit override and takes precedence over every older fallback alias, including a mobile-shaped legacy username and Digits metadata. Read that physical row with a prepared database query; callingget_user_meta()orget_metadata_raw()from the virtualget_user_metadatapath recurses. Once an override exists, an older mobile value must not remain a Pinova login alias. - If one mobile deterministically matches multiple legacy User IDs, fail closed and emit
pinova/identity_conflict_detected; never choose the first row. Multi-ID merge belongs to the separately tested 1.3 identity workflow. - Password authentication must resolve the User ID first, then call
wp_signon()with the realuser_login, preserving 2FA, Wordfence, and standard login hooks. - Public authentication and recovery responses must not reveal whether an identifier exists, has a password, or belongs to a native-only role. Native-only roles still use
wp-login.php, but that policy is not exposed through account-specific REST output. - The shared browser REST helper must return valid non-2xx Pinova/WordPress JSON responses to its callers instead of replacing them with a transport error. Prefer the field-specific WordPress validation message, preserve HTTP status,
Retry-After, and Pinova correlation metadata, and show a generic helper-level notification only for network or malformed responses. Do not emit a duplicate notification from both the helper and its caller. - Role allowlists must be revalidated after filters. A filter cannot admit roles with administrative, user-management, plugin-management, WooCommerce-management, or order-edit capabilities.
- Legacy upgrade methods are additive/no-op against WordPress core tables. Do not alter/drop core columns or indexes, rewrite core identity data, or add foreign keys to core tables.
- Spreadsheet formatting must stay within populated ranges. Never style whole worksheet columns such as
A:Z, because PhpSpreadsheet materializes millions of cells and can exhaust PHP memory even for a tiny export. - A
pinova_blocksrow withblocked_until = NULLis permanent. Expired cleanup must leave permanent blocks intact. - Blocked List writes are type-authoritative: accept only
mobile,email,username, orip, normalize by that selected type before persistence and lookup, and never allow a manual request to convert or delete a system-managed block. Recheck both the client IP and identifier during OTP verification so a block added after code issuance still prevents authentication and user creation. - Release ZIPs require the repository-pinned Composer 2.10.3.
tools/build.shmust fail on a different Composer version, exportTZ=UTC, and normalize staged directories/files to0755/0644, because Composer-generated autoload formatting, ZIP DOS timestamps, and checkout umasks otherwise vary across build hosts. - Root
CHANGELOG.mdand the WordPress.orgreadme.txtChangelog section are synchronized release-history views. Preserve identical version order and entry text, update both together, and runphp tools/check-changelog-sync.php; CI must reject drift. Keep the repository-only Markdown file out of the installable ZIP becausereadme.txtalready ships the same history. - A version bump requires a matching
Version::update_XYZ()method, even when no schema changes are needed, so the installed-version option advances and the upgrader does not repeat on every request. - Integration tests must load the configured WooCommerce checkout before Pinova and exercise real WooCommerce classes. Forward
PINOVA_TEST_HPOS=yes|nointotests-cli; the bootstrap must set the HPOS option in the PHPUnit database, not the separate development database. The disposabletests-clicontainer also needspdo_mysqlbecause Pinova's Illuminate database layer uses PDO even when WordPress itself uses mysqli. When installing it through in-containersudo, passPHP_INI_DIR=/usr/local/etc/phpexplicitly because sudo does not preserve that image environment variable. - Do not instantiate
WC_Checkoutfrom the earlywoocommerce_loadedbootstrap merely to read whether registration is required. Derive the same filtered option value without constructing checkout, and keep the integration regression assertion that Pinova load does not trigger WooCommerce just-in-time translation warnings. - Checkout phone validation must consume the already-sanitized data and
WP_Errorsupplied bywoocommerce_after_checkout_validation. Never callWC()->checkout()->get_posted_data()fromwoocommerce_checkout_process: WooCommerce parses the payload immediately after that action, and doing it inside the action re-enters all checkout-field filters early and can produce repeated lifecycle warnings from extensions. - Never log raw identifiers, OTPs, passwords, reset keys, tokens, or full proxy headers. Use IDs, masked values, event types, and correlation IDs.
- Runtime logs use stable event codes and the PSR-3 levels through
Pinova\Logging\Logger. Context is deny-by-default: add a documented allowlist key or keyed fingerprint instead of arbitrary prose. Never persist exception messages or traces. Debug requires an expiring diagnostic window; normal retention is bounded and cleanup is batched. - Logging is permanent plugin infrastructure, but individual records are temporary operational data. A logging failure must never interrupt login, OTP, REST, export, or another user request. Repeated attacker-controlled failures must be transition-logged or otherwise throttled to prevent log amplification.
- Pinova's structured event table does not replace WordPress, WooCommerce, PHP, or web-server diagnostics. A clean Pinova event stream does not rule out lifecycle/deprecation warnings. Inspect those streams during compatibility investigations; do not mirror every global
doing_it_wrong_runevent into persistent Pinova logs because unrelated extensions and repeated public requests can amplify them. - Treat the authorized administrator SMS test as a bounded audit action: its success and failure events must persist even when the configured minimum level is
error. CatchThrowableat this REST boundary, return a generic message for non-provider PHP errors, and never persist the throwable message or trace. - Trust only
REMOTE_ADDRby default. Proxy headers require an immediate peer in an explicitly configured trusted CIDR.
Workflows
Review or diagnose
- Keep the task read-only unless the user explicitly asks for a fix.
- Establish claims with source, tests, logs, or a disposable WordPress environment. Separate plugin defects from server rewrites, provider failures, or unsupported WordPress/WooCommerce pairs.
- Report affected version, identifier type, role policy, login method, HPOS mode, and whether failure happens before or after WordPress hooks.
Change code
- Branch from the exact affected ref in a separate worktree; do not develop in
<production-wordpress-root>/wp-content/plugins/pinova, and do not hard-code the real host path in public documentation. - Add a regression test when practical. Authentication, identifier, database, exports, proxy, redirect, and WooCommerce changes require integration coverage. Checkout validation coverage must prove the early process action does not parse the full payload and that the later validated-data hook accepts normalized mobile input while rejecting invalid or non-string input through WooCommerce's error container.
- Make the smallest coherent change and preserve the REST envelope
{success,message,data}where compatibility requires it. - For mobile profile changes, store the normalized value in physical
pinova_mobilemeta, validate ownership/conflicts, and keepuser_loginimmutable. - For Blocked List changes, test every supported identifier type, permanent/temporary expiry, system-block immutability, capability denial, filtered pagination, privacy-safe logs, and both issuance-time and verification-time authentication denial.
- For logging changes, update the event catalogue and privacy allowlist in
references/logging.md, add a no-secret regression test, and exercise schema upgrade plus retention cleanup. - Run targeted checks and the proportional suite in the quality reference.
- Update public documentation plus both
CHANGELOG.mdand thereadme.txtChangelog section when behavior changes. - Meaningfully update this
SKILL.mdin the same change set whenever runtime code, schema, dependencies, tests, tooling, CI, commands, integrations, or release behavior changes. - Run
bash .agents/skills/pinova-development/scripts/check-skill-sync.sh --working-treebefore handoff.
Migrate or merge identities
- Identity-table migration and multi-account merge are 1.3 features, not part of the 1.2.3 hotfix. Do not backport schema or automatic merge behavior casually.
- Use a fresh staging copy, audit, and dry-run before apply. Require an immediate full database backup and explicit canonical User ID for every multi-account merge.
- Block apply on Multisite. Unsupported ownership systems require a preflight stop until an adapter exists.
- Discovery/audit must not write. Apply must be additive, batched, idempotent, resumable, and journaled, with maintenance always cleared in a
finallypath.
Package or release
- Read the quality/release reference before changing versions, tags, ZIPs, workflows, or GitHub Releases.
- Record every durable change intended for a Pinova test version through a named GitHub branch, reviewable commits, a pull request, required CI, and merge. Never describe local implementation as a completed phase or an installable version while that GitHub record is missing. If GitHub mutation has not been authorized, stop at the local candidate and report publication as an explicit incomplete gate.
- Treat a version as ready for installation only after the GitHub Release API reports native
immutable: true, the Release contains its installable ZIP and SHA-256 file, both assets have been downloaded again, and their GitHub attestations and local integrity checks pass. Never direct the user to install a worktree-local ZIP. - Release candidates remain pre-releases until staging and canary gates pass. Stable/latest publication needs explicit authorization.
- Build twice from the exact tag, compare SHA-256, publish the installable ZIP, download it again, and verify checksum, integrity, and top-level
pinova/. - Run
php tools/check-changelog-sync.phpbefore selecting a release commit; a release with missing, reordered, or divergent history is not ready. - Automated pre-release publication starts only after a successful
Qualityrun on an exactpublish/vX.Y.Z-rcNbranch. It uses only the job-scopedGITHUB_TOKEN; neither an Administration-scoped secret nor a repository Ruleset is a publication prerequisite. The repository owner must confirm native Release Immutability is enabled before the publish branch is created because the workflow cannot read that administrative setting before creating the tag. The publisher validates that the commit is reachable frommainand matches the plugin version, creates an annotated tag, builds twice, uses an explicit draft-upload-publish sequence for the ZIP and checksum, then requires the published Release API to report nativeimmutable: true, verifies both attestations, and redownloads the published assets. Release notes created before that post-publication check must describe immutability as a required gate and make installation conditional on successful workflow completion; they must not claim the gate has already passed. A rerun may resume an unpublished draft only after its server-supplied author isgithub-actions[bot]and its name, target SHA, complete publisher-marked body, and allowed asset names match this exact release; it reuploads the reproducible assets before publication, never deletes or changes an unknown draft, preserves any published immutable Release, and refuses a published mutable one. If the post-publication immutability check fails, retain that tag and Release unchanged and use a new RC tag after correcting the repository setting. Never create the publish branch until the intended commit is already reviewed and green onmain. - After publication, record the immutable tag target, Release state, asset digests, verification evidence, and next operational boundary in repository guidance through a follow-up documentation PR. Keep the provenance chain auditable by naming the implementation PR and CI, merged
mainSHA and CI, publish-branch CI, publisher workflow, annotated tag object and target, Release ID/state, asset digests, attestation result, and independent redownload result separately. Do not edit the immutable Release or move its tag merely to synchronize prose. - GitHub publication never authorizes installation, configuration changes, or database writes on the production site.
Documentation synchronization gate
The repository enforces Skill review with scripts/check-skill-sync.sh. For project-affecting changes:
- update this entrypoint with decision-changing guidance;
- place detailed procedures in the relevant reference;
- update
AGENTS.mdwhen lineage, blockers, milestone, or completion gates change; - update Persian
README.md, rootCHANGELOG.md, and WordPressreadme.txtwhen public behavior, requirements, installation, or releases change; - keep the two changelog views synchronized with
php tools/check-changelog-sync.php.
Do not use a date-only or whitespace-only edit to satisfy the gate.
Completion gate
Finish only when the requested behavior is evidenced or implemented, required checks pass (or failures are accurately classified), guidance is synchronized, requested artifacts are reproducible, and no production or external state changed outside the user-authorized scope. When the requested outcome is a version ready for installation, completion additionally requires the GitHub branch/PR/CI/merge record, a GitHub pre-release or release whose API reports immutable: true, successful GitHub attestation checks, and successful redownload verification of its ZIP and checksum. Handoff must name branch, PR, merge commit, tag, Release URL, asset checksum, checks, remaining risk, and the separately authorized installation step.