From 14e7eb25f9ded8eb38de7427510fb36c1de8ae21 Mon Sep 17 00:00:00 2001 From: opencode Date: Mon, 24 Aug 2026 02:05:07 -0300 Subject: [PATCH] docs: add incompatibleWith authoring rules (fixes #119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #119. Documents incompatibleWith contributor recipe: when to use (same file/type — Dockerfile, compose.yml, .env.example, pyproject.toml overlay e.g. celery-docker vs flower-docker for celery-worker), symmetric requirement, same-type restriction, example JSON, validation via scripts/ci/validate-registry.py and templates.schema.json, L2 failure on combination. Related to #91. --- docs/AUTHORING.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/AUTHORING.md b/docs/AUTHORING.md index 96dc7e5..0751d09 100644 --- a/docs/AUTHORING.md +++ b/docs/AUTHORING.md @@ -397,22 +397,30 @@ Full reference: [create-python-app `docs/PYPROJECT_MERGE.md`](https://github.com - An extension has a `type` string **or array** of strings. - An extension is compatible when `template.type` appears in `[extension.type].flat()`. -### `incompatibleWith` +### `incompatibleWith` — when and how -Declare mutually exclusive extensions in `templates.json`. CPA validates selected combinations at scaffold time. +Use `incompatibleWith` when two extensions would write the same file for the same `type` — e.g. `Dockerfile`, `compose.yml`, `.env.example`, or `pyproject.toml` overlay. Example: `celery-docker` vs `flower-docker` both target `celery-worker` and ship a Compose stack for the same worker (similarly, two FastAPI middleware extensions that both patch `app/core/providers.py`). + +- **Symmetric (required):** if `A` lists `B`, then `B` must list `A`. Validated by [`scripts/ci/validate-registry.py`](../scripts/ci/validate-registry.py) (symmetry + existence). +- **Same `type` only:** both extensions must share the same `type` (e.g. `celery-worker`). Cross-type is rare and needs justification. +- **Slugs, not names:** reference the `slug` field. ```json { - "name": "Example A", - "slug": "example-a", - "incompatibleWith": ["example-b"], - "...": "..." + "slug": "flower-docker", + "incompatibleWith": ["celery-docker"] +}, +{ + "slug": "celery-docker", + "incompatibleWith": ["flower-docker"] } ``` -When two extensions logically conflict (two middleware choices, two container runtimes), add `incompatibleWith` on **both** entries. Use this for logical conflicts; use semver or dependency constraints for softer peer restrictions. +- **Validation:** `python scripts/ci/validate-registry.py` fails on unknown or asymmetric slugs. +- **Testing:** L2 fails if both extensions are selected together (combination rejected at scaffold time). +- **Schema:** [`templates.schema.json`](../templates.schema.json) → `extensions[].incompatibleWith`. -Schema: `templates.schema.json` → `extensions[].incompatibleWith`. +See also the [path-collision rules](#incompatiblewith-path-collisions) above. ## Generation order