Skip to content

Developing the WordPress plugin + Drupal module

The Cinatra WordPress plugin and Drupal module live in their own repositories (the source of truth), not in this monorepo:

  • cinatra-ai/wordpress-plugin → WordPress.org slug cinatra (GPL-2.0-or-later)
  • cinatra-ai/drupal-module → Drupal.org machine name cinatra (GPL-2.0-or-later)

For local development the dev docker stack consumes them as clones inside this tree. The dev-provisioning commands (cinatra instance setup dev, cinatra instance branch setup, cinatra instance clone new) clone / fast-forward them from the config in package.json (cinatra.devApps) into:

  • dev/wordpress-plugin/ — the WordPress plugin (repo root maps to the plugin dir; docker bind-mounts it to /wp-content/plugins/cinatra/)
  • dev/drupal-module/cinatra/ — the Drupal module (docker bind-mounts it to /modules/custom/cinatra/)

Both clone paths are gitignored in the cinatra repo (they sit under dev/ alongside the other dev-only clones; nothing is tracked). See LINKING.md for the Apache-2.0 ↔ GPL-2.0-or-later HTTP-only boundary.

You changed… Commit in… PR in…
Plugin/module PHP, YAML, readme the clone (dev/wordpress-plugin/ or dev/drupal-module/cinatra/) the extracted repo — no cinatra PR needed
Cinatra core (bundle route, contracts, setup, docs) the cinatra repo the cinatra repo
The wire contract (both sides) both — see “Contract-version bumps” two coordinated PRs

git status makes it obvious which tree you’re in: run it inside dev/wordpress-plugin/ (or dev/drupal-module/cinatra/) and you’ll see the extracted repo’s status (its own origin, its own main). Run it at the cinatra root and the clone paths don’t appear at all (they’re gitignored).

  1. cd dev/wordpress-plugin (or dev/drupal-module/cinatra).
  2. Edit, commit, push to the extracted repo’s main via a PR there.
  3. Back in cinatra, cinatra instance setup dev fast-forwards the local clone to the new main. No cinatra PR is involved.

Contract-version bumps (cross-cutting changes)

Section titled “Contract-version bumps (cross-cutting changes)”

When a change touches the wire contract (contracts/wp-drupal-assistant/), use the two-PR protocol so a plugin built against the old contract never silently breaks:

  1. cinatra first: add contracts/wp-drupal-assistant/v2/ (new schemas + fixtures), extend SUPPORTED_CONTRACT_VERSIONS in src/lib/wp-drupal-contract.ts (keep v1), ship the v2-aware backend. Merge it.
  2. plugin/module next: bump the client’s contractVersion to v2 in the extracted repo, ship it.
  3. Keep v1 supported until a deliberate deprecation PR removes it.

The contract test suite (tests/contracts/wp-drupal/) + the wp-drupal-contract.yml gate enforce schema/fixture conformance on every change under contracts/wp-drupal-assistant/.

cinatra instance setup dev never destroys local work. If a clone has uncommitted changes it is skipped with a warning naming the dirty repo. To proceed:

  • Keep your work: cd dev/wordpress-plugin && git commit -am "wip" (or git stash), then re-run cinatra instance setup dev.
  • Discard your work: cd dev/wordpress-plugin && git checkout . && git clean -fd, then re-run.
  • Force fast-forward (stashes first): cinatra instance setup dev --force-dev-apps — stashes local changes, then hard-resets the clone to origin/main. A clone pointing at the wrong origin or branch is never auto-reset, even with --force; fix the remote or move the dir aside.
  • Skip the sync entirely: cinatra instance setup dev --skip-dev-apps (e.g. on CI / contributors without access to the private repos pre-launch).

Per-repo URL overrides (for a fork or an SSH remote) without editing package.json: CINATRA_WORDPRESS_PLUGIN_REPO_URL, CINATRA_DRUPAL_MODULE_REPO_URL (HTTPS or SSH; origin comparison normalizes the two forms).

error_log() (WordPress) and \Drupal::logger('cinatra')->debug() (Drupal) changes you add while debugging live in the clone and are local-only unless you intentionally commit + push them to the extracted repo. cinatra instance setup dev treats them as a dirty tree and skips (see above) — so they won’t be silently fast-forwarded away, but they also won’t reach anyone else until you push.

Recovery when cinatra instance setup dev skips a dirty clone

Section titled “Recovery when cinatra instance setup dev skips a dirty clone”

The warning names the dirty repo and the exact next command. Pick one of the dirty-tree recipes above. If a clone is in a non-git or wrong-origin state, cinatra instance setup dev fails loud (non-zero exit) with remediation text rather than touching it — move the directory aside and re-run to get a fresh clone.

Volume-scoped dev migration (post-cutover, one-time)

Section titled “Volume-scoped dev migration (post-cutover, one-time)”

Existing dev installs have the pre-rename (widget-era) plugin/module baked into the WordPress/Drupal docker volumes. To pick up the renamed cinatra plugin/module, drop only the WP/Drupal volumes (not unrelated cinatra volumes) and re-setup:

Terminal window
docker compose stop wordpress wordpress-db drupal drupal-db
docker volume rm cinatra_cinatra-wordpress cinatra_cinatra-wordpress-db \
cinatra_cinatra-drupal cinatra_cinatra-drupal-db
cinatra instance setup dev

The renamed plugin/module each run a one-shot migration that copies the pre-rename WordPress option keys / Drupal settings config into the new cinatra_* / cinatra.settings keys (the exact legacy identifiers live in the extracted repos’ migration code), so a real (non-volume-dropped) upgrade preserves the configured URL + API key. Browser-side localStorage chat history resets (the history key was renamed).

PRs that touch the contract / plugin-integration paths trigger the Playwright UAT hard gate (wp-drupal-uat.yml). To land a planned migration that knowingly breaks a UAT, add the override-wp-drupal-uat label — see the override process in contributing.md. Running the UATs locally: pnpm test:e2e:wp-drupal (see tests/e2e/wp-drupal-uat/README.md).

Like all contributions, plugin/module work is planned with GSD (“Git. Ship. Done”, the open-gsd spec-driven development framework) — see Contributing.

Docs content licensed under CC-BY-4.0; embedded code snippets under Apache-2.0.