# Contributing to DocWright

Thanks for helping build DocWright. It is FOSS (**GPL-3.0-or-later**), no Docker,
and **PHP class-first + Python + PostgreSQL**. This is the short version of how to
work in the tree; the design rationale is in [`ARCHITECTURE.md`](ARCHITECTURE.md)
and the extension model in [`PLUGINS.md`](PLUGINS.md).

## Plugin-first philosophy

DocWright is a **thin, stable core plus composable plugins.** Before adding to
the core, ask whether your feature is really a **first-party provider or plugin**
on an existing extension point. The core owns only the kernel, the canonical AST,
the project/Git store, auth/session/RBAC, the job queue, the realtime substrate,
and the extension-point registry. Almost everything else — styles, layout, math,
exporters, sharing, workflow packs, BIM, transliteration — is a bundled plugin.

- Prefer contributing to an existing extension point
  (`ExtensionPoints::CATALOGUE`) over editing the core.
- If you truly need a new seam, add a named point with a typed contract rather
  than hard-wiring behaviour.
- A new subsystem is a `ServiceProvider` (same shape as a `Plugin`:
  `register()` binds services, `boot()` contributes/registers routes). Wire it in
  `app/bootstrap.php` (guarded by `class_exists`) or ship it under `plugins/`.
- Keep the core small: no subsystem should hard-code what a plugin could
  contribute.

## Coding standards (spec §22)

**PHP**
- `declare(strict_types=1)` in every file; **class-first / OOP**, PSR-12,
  SOLID.
- Typed properties and signatures; immutable value objects for domain concepts;
  constructor **dependency injection** (resolve via the container, no static
  globals).
- Thin controllers, rich domain services; exceptions over error codes.
- **No stubs, TODOs, or placeholders on shipped paths.** If you must defer, ship
  a working minimal version and mark the extension point clearly.
- **Never build a shell string** — pass external process arguments as arrays
  (see `Support\Process` and `Git\GitStore`), so there is no injection surface.
- SQL is **parameterised**; schema changes are **migrations only** — add a new
  numbered file under `migrations/` (or a plugin's `migrations/`), never edit an
  applied one and never drift the schema by hand.

**Python** (workers)
- Typed (mypy-clean where feasible), `ruff` / `black` formatted.
- Small, composable CLI entry points; structured logging; **deterministic
  outputs** so golden-file render tests are stable.

**Frontend**
- Typed (TypeScript), componentised, accessible (ARIA, keyboard); built to
  **static assets** — **no Node at runtime.**

**Git / commits**
- **Conventional Commits** (`feat:`, `fix:`, `docs:`, `refactor:`, `chore:`, …).
- Tag milestones; keep a meaningful history.
- `.gitignore` excludes caches / venv / vendor / build output but **ships
  lockfiles**.

**Config & secrets**
- All environment-specific values live in `.env` (documented in `.env.example`);
  nothing secret in the repo; secrets are **encrypted at rest** with
  `DOCWRIGHT_SECRET_KEY`.

**Licensing**
- Every runtime dependency must be **GPL-3.0-compatible** and recorded in
  [`THIRD_PARTY.md`](../THIRD_PARTY.md); third-party plugins are listed there too.
  The core PHP app deliberately carries **no hard Composer dependency**.

## Running lint and the dev server

```bash
make lint     # php -l across app/ and plugins/ (+ py_compile of workers)
make serve    # dev server on 127.0.0.1:8080 (php bin/console serve)
make test     # PHPUnit (if vendor/bin/phpunit present) + pytest (if installed)
make doctor   # environment preflight
```

Under the hood, `make serve` runs `bin/console serve 127.0.0.1:8080` (PHP's
built-in server with docroot `app/public`). Before opening a PR, make sure
`make lint` is clean, `make doctor` passes, and any tests you touched are green.
Tests live under `tests/` (`phpunit/`, `pytest/`, `e2e/` Playwright, `contract/`
for OpenAPI/AsyncAPI/JSON-Schema validation).

## Adding a migration

Create the next-numbered SQL file (e.g. `migrations/0011_<topic>.sql`), keep it
**idempotent** where practical (`IF NOT EXISTS`, `ON CONFLICT DO NOTHING`), all in
the app's own database, then:

```bash
php bin/console migrate           # apply (core + plugin migrations)
php bin/console migrate:status    # confirm applied vs pending
```

## Adding a plugin

Scaffold a directory under `plugins/<id>/` with a `plugin.json`, a PHP entry
implementing `DocWright\Plugins\Plugin`, and (optionally) `js` / `python` entries
and a `migrations/` dir. Declare only the **capabilities** you need. Copy
`plugins/example-hello/` — it proves every seam (AST node, editor command, API
resource + OpenAPI fragment, workflow pack, webhook) and is the canonical
pattern. Verify with `bin/console plugin:list`.

## Definition of done

A change is done when it is complete (no stubs on the hot path), wired into UI +
API + RBAC as applicable, covered by a test, documented, and passes its scenario
check. When a requirement is genuinely infeasible exactly as stated, implement
the closest faithful FOSS approach, make it work, and document the trade-off in
[`ARCHITECTURE.md`](ARCHITECTURE.md) — do not silently drop scope.
