This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 2b0e3e1afefd2e97b796f4ccef989c0c727b5ed4
Author: Andrus Adamchik <[email protected]>
AuthorDate: Thu May 21 15:45:09 2026 -0400

    CAY-2946 Claude Code "plugin" for agentic coding with Cayenne
---
 RELEASE-NOTES.txt                                  |   1 +
 ai-plugin/.claude-plugin/plugin.json               |   6 +
 ai-plugin/README.md                                |  61 ++++++
 ai-plugin/references/cgen-config.md                | 116 ++++++++++
 ai-plugin/references/datamap-schema.md             | 233 +++++++++++++++++++++
 ai-plugin/references/dbimport-config.md            |  93 ++++++++
 ai-plugin/references/mcp-tools.md                  |  81 +++++++
 ai-plugin/references/project-descriptor-schema.md  |  83 ++++++++
 ai-plugin/references/project-layout.md             |  52 +++++
 ai-plugin/references/query-api.md                  | 224 ++++++++++++++++++++
 ai-plugin/references/runtime-api.md                | 143 +++++++++++++
 ai-plugin/skills/cayenne-cgen/SKILL.md             |  77 +++++++
 ai-plugin/skills/cayenne-modeler/SKILL.md          |  61 ++++++
 ai-plugin/skills/cayenne-modeling/SKILL.md         |  74 +++++++
 ai-plugin/skills/cayenne-query/SKILL.md            | 105 ++++++++++
 ai-plugin/skills/cayenne-reverse-engineer/SKILL.md |  80 +++++++
 ai-plugin/skills/cayenne-runtime/SKILL.md          |  86 ++++++++
 17 files changed, 1576 insertions(+)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index c5ad64fdc..5f298bc8b 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -43,6 +43,7 @@ CAY-2940 Upgrade to SLF4J version 2.0.17
 CAY-2942 CayenneModeler MCP: cgen_run tool
 CAY-2943 CayenneModeler MCP: open_project tool
 CAY-2945 CayenneModeler universal "main"
+CAY-2946 Claude Code "plugin" for agentic coding with Cayenne
 
 Bug Fixes:
 
diff --git a/ai-plugin/.claude-plugin/plugin.json 
b/ai-plugin/.claude-plugin/plugin.json
new file mode 100644
index 000000000..94108acea
--- /dev/null
+++ b/ai-plugin/.claude-plugin/plugin.json
@@ -0,0 +1,6 @@
+{
+  "name": "apache-cayenne",
+  "description": "Apache Cayenne ORM — design DataMaps, reverse-engineer 
schemas, generate classes, and write runtime code. Drives the cayenne MCP 
server when present.",
+  "version": "5.0.0",
+  "author": { "name": "Apache Cayenne" }
+}
diff --git a/ai-plugin/README.md b/ai-plugin/README.md
new file mode 100644
index 000000000..9305ba23b
--- /dev/null
+++ b/ai-plugin/README.md
@@ -0,0 +1,61 @@
+# apache-cayenne — Claude Code plugin
+
+Apache Cayenne ORM workflows for Claude Code.
+
+This plugin teaches Claude how to:
+
+- Edit Cayenne DataMap (`*.map.xml`) and project descriptor (`cayenne-*.xml`) 
files a-la-carte (add entities, relationships, queries, embeddables).
+- Reverse-engineer a database schema into a DataMap by driving CayenneModeler.
+- Regenerate Java entity classes from a DataMap.
+- Bootstrap `CayenneRuntime` in a Java application and write `ObjectSelect` / 
`SQLSelect` queries.
+
+The plugin assumes downstream **users of Cayenne** writing their own Java 
apps. It does *not* cover contributor workflows for hacking on Cayenne itself.
+
+## First-time setup
+
+> **Cayenne 5.0+ only.** The MCP server ships with Cayenne 5.0. Skills that 
depend on it (`cayenne-cgen`, `cayenne-modeler`, `cayenne-reverse-engineer`) 
will not work against earlier Cayenne versions — there is no MCP server to 
register. The XML-editing skills (`cayenne-modeling`) and runtime/query skills 
target Cayenne 5.0 idioms too; for older Cayenne, this plugin is not the right 
tool.
+
+1. **Register the Cayenne MCP server.** Class generation (`cgen_run`) and 
opening the Modeler GUI (`open_project`) both go through this server.
+TODO: Full instructions will be on the website shorly.
+
+```bash
+claude mcp add cayenne --scope user -- java -jar 
/path/to/cayenne-mcp-server-<VERSION>.jar
+```
+
+2. **Verify** the server is registered with `claude mcp list` — you should see 
an entry named `cayenne` (the MCP server alias from the command above, separate 
from this plugin's `apache-cayenne` name) showing as connected.
+
+That's it. The skills detect the MCP server at runtime; if it isn't connected 
they point back at this README instead of falling back to Maven or Gradle goals.
+
+## What's in here
+
+```
+ai-plugin/
+├── .claude-plugin/plugin.json   # manifest
+├── README.md                    # this file
+├── skills/                      # auto-triggering workflows
+│   ├── cayenne-modeling/        # edit *.map.xml and cayenne-*.xml
+│   ├── cayenne-reverse-engineer/# import a DB schema (Modeler GUI)
+│   ├── cayenne-cgen/            # regenerate Java classes via MCP
+│   ├── cayenne-modeler/         # open the GUI on a project
+│   ├── cayenne-runtime/         # bootstrap CayenneRuntime in an app
+│   └── cayenne-query/           # write ObjectSelect / SQLSelect queries
+└── references/                  # source-of-truth docs loaded by skills
+    ├── project-layout.md
+    ├── datamap-schema.md
+    ├── project-descriptor-schema.md
+    ├── dbimport-config.md
+    ├── cgen-config.md
+    ├── runtime-api.md
+    ├── query-api.md
+    └── mcp-tools.md
+```
+
+Each skill is a thin trigger that loads the right reference docs and walks 
Claude through the workflow.
+
+ORM workflows go through:
+
+1. Direct XML edits — primary path for a-la-carte model changes.
+2. MCP `cgen_run` — class generation.
+3. MCP `open_project` → CayenneModeler GUI — full DB sync and visual editing.
+
+and will NOT use Maven or Gradle plugins
diff --git a/ai-plugin/references/cgen-config.md 
b/ai-plugin/references/cgen-config.md
new file mode 100644
index 000000000..f085e9de0
--- /dev/null
+++ b/ai-plugin/references/cgen-config.md
@@ -0,0 +1,116 @@
+# Class generation config (`<cgen>`)
+
+Reference for the embedded `<cgen>` block inside a DataMap that drives 
`mcp__cayenne__cgen_run`.
+
+## XML shape (embedded in a DataMap)
+
+```xml
+<data-map xmlns="http://cayenne.apache.org/schema/12/modelMap"; ...>
+    ...entities, relationships, etc...
+
+    <cgen xmlns="http://cayenne.apache.org/schema/12/cgen";>
+        <destDir>../java</destDir>
+        <mode>entity</mode>
+        <excludeEntities></excludeEntities>
+        <excludeEmbeddables></excludeEmbeddables>
+        <outputPattern>*.java</outputPattern>
+        <makePairs>true</makePairs>
+        <usePkgPath>true</usePkgPath>
+        <overwrite>false</overwrite>
+        <createPropertyNames>false</createPropertyNames>
+        <createPKProperties>true</createPKProperties>
+    </cgen>
+</data-map>
+```
+
+Note the **different namespace** for `<cgen>` 
(`http://cayenne.apache.org/schema/12/cgen`) — it is a separate schema embedded 
in the modelMap schema.
+
+## Field reference
+
+### Required
+
+| Field | Default | Meaning |
+|---|---|---|
+| `<destDir>` | — | Output directory, **relative to the project XML 
directory** (the directory containing `cayenne-*.xml`). Typical values: `.`, 
`../java`, `../../main/java`. Absolute paths also work. |
+| `<mode>` | `entity` | What to generate. `entity` = per-entity classes. `all` 
= per-entity classes + a single DataMap class with named-query helpers. |
+
+### Pairs vs single class
+
+| Field | Default | Meaning |
+|---|---|---|
+| `<makePairs>` | `true` | When `true`, generate two files per entity: 
`_Artist.java` (regenerated, never edit) and `Artist.java` (user subclass, edit 
freely). When `false`, generate a single `Artist.java` — overwritten each run. 
**Almost always leave at `true`.** |
+| `<overwrite>` | `false` | Only meaningful when `makePairs=false`. When 
`true`, the single subclass is overwritten on each run. Dangerous if user code 
lives there. |
+
+### Filtering
+
+| Field | Default | Meaning |
+|---|---|---|
+| `<excludeEntities>` | (empty) | Comma-separated ObjEntity names to skip. 
Inverse — what's *included* is everything else. |
+| `<excludeEmbeddables>` | (empty) | Comma-separated embeddable class names to 
skip. |
+
+### Output
+
+| Field | Default | Meaning |
+|---|---|---|
+| `<outputPattern>` | `*.java` | File name pattern. `*` is replaced by the 
class name. Change to `*.kt` for Kotlin templates. |
+| `<encoding>` | platform default | Character encoding for generated files. |
+| `<usePkgPath>` | `true` | When `true`, generated files go into 
subdirectories matching their Java package (`com/example/Artist.java`). When 
`false`, all files are flat in `destDir`. |
+| `<superPkg>` | (empty) | Override package for generated superclasses 
(`_Artist`). By default, `<defaultPackage>.auto`. |
+
+### Templates
+
+| Field | Default | Meaning |
+|---|---|---|
+| `<template>` | built-in `subclass.vm` | Velocity template for the user 
subclass (`Artist.java`). Use a CDATA path to a custom `.vm` file. |
+| `<superTemplate>` | built-in `superclass.vm` | Template for the 
auto-generated superclass (`_Artist.java`). |
+| `<embeddableTemplate>` | built-in | Template for embeddable subclass. |
+| `<embeddableSuperTemplate>` | built-in | Template for embeddable superclass. 
|
+| `<dataMapTemplate>` | built-in | Template for the DataMap helper class (only 
used when `<mode>` is `all`). |
+| `<dataMapSuperTemplate>` | built-in | Superclass template for the DataMap 
helper. |
+
+Custom templates are rare. Skip unless the user explicitly asks for them.
+
+### Properties
+
+| Field | Default | Meaning |
+|---|---|---|
+| `<createPropertyNames>` | `false` | When `true`, also generate `public 
static final String` constants for each attribute name. Legacy — most code uses 
the typed `Property<>` constants instead. |
+| `<createPKProperties>` | `true` | When `true`, generate `Property<>` 
constants for PK columns too. |
+| `<externalToolConfig>` | (empty) | Free-form config consumed by external 
tools (e.g., Kotlin generator extensions). |
+
+## Modeler equivalent
+
+In CayenneModeler: select the DataMap → "Class Generation" tab. Every field 
above has a UI control. Saving the project persists them back as `<cgen>` in 
the DataMap.
+
+## How `mcp__cayenne__cgen_run` uses this
+
+The MCP tool:
+
+1. Loads the project at `projectPath`.
+2. Finds the named DataMap (`dataMap` argument).
+3. Reads its embedded `<cgen>` block.
+4. Resolves `destDir` relative to the project file.
+5. Runs the generator. Returns a JSON object listing files `written`, files 
`skipped` (already up-to-date), and any `errors`.
+
+If the DataMap has no `<cgen>` block, the tool returns an error and the user 
must add one — typically via the `cayenne-modeling` skill or the Modeler GUI.
+
+## Recommended starting config
+
+
+```xml
+<cgen xmlns="http://cayenne.apache.org/schema/12/cgen";>
+    <destDir>../java</destDir>
+    <mode>entity</mode>
+    <makePairs>true</makePairs>
+    <usePkgPath>true</usePkgPath>
+    <createPKProperties>true</createPKProperties>
+</cgen>
+```
+
+Assuming the project XML lives at `src/main/resources/cayenne-mydb.xml`, this 
writes to `src/main/java/<package>/`.
+
+## Anti-patterns
+
+- Setting `<makePairs>false</makePairs>` to "simplify" — you lose the ability 
to add user code without it being overwritten on the next cgen run.
+- Editing `_<Entity>.java` files (the superclasses). They are regenerated. 
Customize `<Entity>.java` (the subclass) instead.
+- `<destDir>` as an absolute path checked into source — breaks reproducibility 
for other developers. Use a relative path.
diff --git a/ai-plugin/references/datamap-schema.md 
b/ai-plugin/references/datamap-schema.md
new file mode 100644
index 000000000..3c670d2c1
--- /dev/null
+++ b/ai-plugin/references/datamap-schema.md
@@ -0,0 +1,233 @@
+# DataMap XML schema (`*.map.xml`)
+
+Reference for editing Cayenne DataMap files directly.
+
+- **Namespace**: `http://cayenne.apache.org/schema/12/modelMap`
+- **XSD**: `http://cayenne.apache.org/schema/12/modelMap.xsd`
+- **Project version**: `12` (Cayenne 5.0)
+- **Working example**: `cayenne-ant/src/test/resources/testmap.map.xml`
+
+## Root element
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<data-map xmlns="http://cayenne.apache.org/schema/12/modelMap";
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+          xsi:schemaLocation="http://cayenne.apache.org/schema/12/modelMap 
http://cayenne.apache.org/schema/12/modelMap.xsd";
+          project-version="12">
+    ...
+</data-map>
+```
+
+## DataMap properties
+
+Top-level `<property>` children of `<data-map>` configure DataMap-wide 
settings. The two used in practice:
+
+```xml
+<property name="defaultPackage" value="com.example.model"/>
+<property name="defaultSuperclass" 
value="org.apache.cayenne.GenericPersistentObject"/>
+```
+
+- `defaultPackage` — Java package for generated classes when an `obj-entity` 
`className` is a short name.
+- `defaultSuperclass` — superclass for generated `_<Name>` superclasses. 
Default is `BaseDataObject`.
+
+## Element order
+
+The schema enforces this order inside `<data-map>`:
+
+1. `<property>` *
+2. `<procedure>` *
+3. `<embeddable>` *
+4. `<db-entity>` *
+5. `<obj-entity>` *
+6. `<db-relationship>` *
+7. `<obj-relationship>` *
+8. `<query>` *
+9. `<cgen>` ? (different namespace, embedded)
+10. `<dbImport>` ? (different namespace, embedded)
+
+When inserting elements by hand, respect this order or Cayenne's parser will 
reject the file.
+
+## `<db-entity>` — database table
+
+```xml
+<db-entity name="ARTIST" catalog="public" schema="public">
+    <db-attribute name="ARTIST_ID" type="BIGINT" isPrimaryKey="true" 
isMandatory="true"/>
+    <db-attribute name="ARTIST_NAME" type="VARCHAR" length="254" 
isMandatory="true"/>
+    <db-attribute name="DATE_OF_BIRTH" type="DATE"/>
+</db-entity>
+```
+
+Attribute fields:
+
+| Attribute | Meaning |
+|---|---|
+| `name` | Column name (case-preserved as-is from the DB) |
+| `type` | JDBC type name: `INTEGER`, `BIGINT`, `VARCHAR`, `CHAR`, `DATE`, 
`TIMESTAMP`, `BOOLEAN`, `BIT`, `NUMERIC`, `DECIMAL`, `FLOAT`, `DOUBLE`, `BLOB`, 
`CLOB`, `VARBINARY`, etc. |
+| `length` | Column length (for VARCHAR, CHAR, VARBINARY, NUMERIC) |
+| `scale` | Decimal scale (NUMERIC, DECIMAL) |
+| `isPrimaryKey` | `true` for PK columns |
+| `isMandatory` | `true` for NOT NULL |
+| `isGenerated` | `true` for DB-generated columns (identity, sequence) |
+
+## `<obj-entity>` — Java object mapped to a DbEntity
+
+```xml
+<obj-entity name="Artist" className="com.example.Artist" dbEntityName="ARTIST">
+    <obj-attribute name="artistName" type="java.lang.String" 
db-attribute-path="ARTIST_NAME"/>
+    <obj-attribute name="dateOfBirth" type="java.util.Date" 
db-attribute-path="DATE_OF_BIRTH"/>
+</obj-entity>
+```
+
+Element attributes:
+
+| Attribute | Meaning |
+|---|---|
+| `name` | Object name (used in queries) |
+| `className` | Fully-qualified Java class. If using DataMap `defaultPackage`, 
a short name works. |
+| `superClassName` | Optional explicit superclass |
+| `dbEntityName` | Matching `<db-entity name="...">` |
+| `superEntityName` | For inheritance — name of parent ObjEntity |
+| `readOnly` | `true` to disallow writes |
+| `abstract` | `true` for abstract entities (single-table inheritance) |
+
+`obj-attribute`:
+
+| Attribute | Meaning |
+|---|---|
+| `name` | Java property name |
+| `type` | Java type, FQN — `java.lang.String`, `java.lang.Integer`, 
`java.util.Date`, `java.math.BigDecimal`, `byte[]`, `boolean`, etc. |
+| `db-attribute-path` | Column name. May be a dotted path through a 
`db-relationship` for derived attributes: `toArtist.ARTIST_NAME`. |
+
+PK columns are not normally mapped as `obj-attribute` — they're handled 
implicitly. Map a PK column only if `meaningfulPK` (you want to expose the PK 
value to the Java side).
+
+## `<db-relationship>` — FK join in the DB layer
+
+```xml
+<db-relationship name="paintingArray" source="ARTIST" target="PAINTING" 
toMany="true">
+    <db-attribute-pair source="ARTIST_ID" target="ARTIST_ID"/>
+</db-relationship>
+```
+
+| Attribute | Meaning |
+|---|---|
+| `name` | Identifier; referenced by `obj-relationship`'s 
`db-relationship-path` |
+| `source` | Owning DbEntity name |
+| `target` | Target DbEntity name |
+| `toMany` | `true` for one-to-many or many-to-many side |
+| `toDependentPK` | `true` when the target row's PK depends on this FK 
(typical for one-to-one or master/detail) |
+
+Each `<db-attribute-pair>` is one column of the join. Compound joins use 
multiple `<db-attribute-pair>` elements.
+
+**Symmetry.** Most FKs need *two* `<db-relationship>` entries, one from each 
side. They are not auto-derived.
+
+## `<obj-relationship>` — object-layer view of a db-relationship
+
+```xml
+<obj-relationship name="paintings"
+                  source="Artist"
+                  target="Painting"
+                  deleteRule="Cascade"
+                  db-relationship-path="paintingArray"/>
+```
+
+| Attribute | Meaning |
+|---|---|
+| `name` | Java property name (typically plural for to-many) |
+| `source` | Owning ObjEntity name |
+| `target` | Target ObjEntity name |
+| `deleteRule` | `Nullify`, `Cascade`, `Deny`, or `NoAction` |
+| `db-relationship-path` | One or more `<db-relationship>` names, 
dot-separated for flattened (many-to-many) relationships: 
`artistGroupArray.toGroup` |
+
+Every `obj-relationship` requires a matching `db-relationship` (or chain) — 
never add one without the backing DB-layer relationship.
+
+## `<embeddable>` — value object without identity
+
+```xml
+<embeddable className="com.example.Address">
+    <embeddable-attribute name="street" type="java.lang.String" 
db-attribute-name="STREET"/>
+    <embeddable-attribute name="city"   type="java.lang.String" 
db-attribute-name="CITY"/>
+</embeddable>
+```
+
+To embed it inside an ObjEntity, use `<embedded-attribute>`:
+
+```xml
+<obj-entity name="User" className="com.example.User" dbEntityName="USER">
+    <embedded-attribute name="homeAddress" type="com.example.Address"/>
+    <embedded-attribute name="workAddress" type="com.example.Address">
+        <embeddable-attribute-override name="street" 
db-attribute-path="WORK_STREET"/>
+        <embeddable-attribute-override name="city"   
db-attribute-path="WORK_CITY"/>
+    </embedded-attribute>
+</obj-entity>
+```
+
+`<embeddable-attribute-override>` is only needed when the host columns differ 
from the embeddable's default `db-attribute-name`.
+
+## `<procedure>` — stored procedure
+
+```xml
+<procedure name="search_artists">
+    <procedure-parameter name="name_filter" type="VARCHAR" length="254" 
direction="in"/>
+    <procedure-parameter name="result_count" type="INTEGER" direction="out"/>
+</procedure>
+```
+
+`direction` is `in`, `out`, or `in_out`.
+
+## `<query>` — named query
+
+Three flavors, all named with `<query name="...">` and selected by `type=`:
+
+### SelectQuery
+
+```xml
+<query name="ArtistsByName" type="SelectQuery" root="obj-entity" 
root-name="Artist">
+    <property name="cayenne.GenericSelectQuery.cacheStrategy" 
value="LOCAL_CACHE"/>
+    <qualifier><![CDATA[artistName like $name]]></qualifier>
+    <ordering descending="true"><![CDATA[dateOfBirth]]></ordering>
+    <prefetch>paintings</prefetch>
+</query>
+```
+
+### SQLTemplate
+
+```xml
+<query name="LowercasedArtists" type="SQLTemplate" root="data-map" 
root-name="testmap">
+    <property name="cayenne.SQLTemplate.columnNameCapitalization" 
value="LOWER"/>
+    <sql><![CDATA[select * from ARTIST]]></sql>
+    <sql 
adapter-class="org.apache.cayenne.dba.postgres.PostgresAdapter"><![CDATA[select 
* from artist]]></sql>
+</query>
+```
+
+Use a second `<sql>` with `adapter-class=` to vary by DB adapter. SQLTemplate 
placeholders use Velocity syntax — `#bind($paramName)` for parameters.
+
+### EJBQLQuery
+
+```xml
+<query name="ArtistByName" type="EJBQLQuery">
+    <ejbql><![CDATA[select a from Artist a where a.artistName = ?1]]></ejbql>
+</query>
+```
+
+### ProcedureQuery
+
+```xml
+<query name="SearchArtists" type="ProcedureQuery" root="procedure" 
root-name="search_artists" result-entity="Artist"/>
+```
+
+## `<cgen>` — embedded code-gen config
+
+A separate namespace, embedded directly in the DataMap. See `cgen-config.md` 
for fields.
+
+## `<dbImport>` — embedded reverse-engineering config
+
+A separate namespace, used by the Modeler's reverse-engineering wizard to 
persist its options. See `dbimport-config.md` for fields.
+
+## Anti-patterns to avoid
+
+- Adding an `obj-relationship` without a backing `db-relationship` — Cayenne 
will validate-fail at runtime load.
+- Setting `db-attribute` `type` without `length` for VARCHAR/CHAR
+- Using a Java primitive (`int`, `long`) for an `obj-attribute` `type` when 
the column is nullable — primitives can't represent NULL; use the wrapper 
(`java.lang.Integer`).
+- Reordering top-level elements — the schema requires the order listed above.
+- Hand-editing `_<Entity>` superclass `.java` files — they are regenerated by 
cgen and will be overwritten. Edit the user subclass instead.
diff --git a/ai-plugin/references/dbimport-config.md 
b/ai-plugin/references/dbimport-config.md
new file mode 100644
index 000000000..5675ec487
--- /dev/null
+++ b/ai-plugin/references/dbimport-config.md
@@ -0,0 +1,93 @@
+# Reverse-engineering config (`<dbImport>`)
+
+Reference for the options shown by the CayenneModeler reverse-engineering 
wizard, persisted as a `<dbImport>` block inside a DataMap.
+
+## XML shape (persisted inside a DataMap)
+
+```xml
+<dbImport xmlns="http://cayenne.apache.org/schema/12/dbimport";>
+    <catalog>
+        <name>public</name>
+        <schema>
+            <name>app</name>
+            <includeTable>
+                <pattern>CUSTOMER.*</pattern>
+            </includeTable>
+            <excludeTable>
+                <pattern>TMP_.*</pattern>
+            </excludeTable>
+        </schema>
+    </catalog>
+    <tableTypes>
+        <tableType>TABLE</tableType>
+        <tableType>VIEW</tableType>
+    </tableTypes>
+    <defaultPackage>com.example.model</defaultPackage>
+    <forceDataMapCatalog>false</forceDataMapCatalog>
+    <forceDataMapSchema>false</forceDataMapSchema>
+    <meaningfulPkTables></meaningfulPkTables>
+    
<namingStrategy>org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator</namingStrategy>
+    <skipPrimaryKeyLoading>false</skipPrimaryKeyLoading>
+    <skipRelationshipsLoading>false</skipRelationshipsLoading>
+    <stripFromTableNames></stripFromTableNames>
+    <useJava7Types>false</useJava7Types>
+</dbImport>
+```
+
+## Field reference
+
+### Filtering
+
+| Field | Type | Meaning |
+|---|---|---|
+| `<catalog><name>` | string | DB catalog to import from. Many databases have 
a single default catalog. |
+| `<schema><name>` | string | DB schema (Postgres `public`, MSSQL `dbo`, 
etc.). |
+| `<includeTable><pattern>` | regex | Tables to include. Multiple 
`<includeTable>` elements union. Default: include all. |
+| `<excludeTable><pattern>` | regex | Tables to skip. Multiple 
`<excludeTable>` elements union. |
+| `<includeColumn><pattern>` | regex | Columns to include (rarely needed). |
+| `<excludeColumn><pattern>` | regex | Columns to skip — useful for system 
columns (`CREATED_AT`, `ROWVERSION`). |
+| `<includeProcedure><pattern>` | regex | Stored procedures to include. By 
default, none are imported. |
+| `<excludeProcedure><pattern>` | regex | Stored procedures to skip. |
+| `<tableTypes><tableType>` | enum | Which JDBC table types to import. Typical 
values: `TABLE`, `VIEW`, `SYSTEM TABLE`, `GLOBAL TEMPORARY`, `LOCAL TEMPORARY`, 
`ALIAS`, `SYNONYM`. If empty, defaults to `TABLE`. |
+
+### Generation
+
+| Field | Type | Default | Meaning |
+|---|---|---|---|
+| `<defaultPackage>` | string | (empty) | Java package for generated ObjEntity 
classes. If the DataMap already has `defaultPackage`, that wins. |
+| `<namingStrategy>` | FQN string | 
`org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator` | Class 
implementing `ObjectNameGenerator` that converts `CUSTOMER_ORDER` → 
`CustomerOrder`, `customer_id` → `customerId`. Plug in a custom one for 
non-default conventions. |
+| `<stripFromTableNames>` | regex | (empty) | Regex applied to each table name 
*before* naming. E.g. `^TBL_` strips a `TBL_` prefix so `TBL_CUSTOMER` → 
`Customer`. |
+| `<meaningfulPkTables>` | comma-separated regex | (empty) | Tables whose PK 
columns should be mapped as ObjAttributes (visible on the Java side). `*` 
matches all. By default PKs are hidden. |
+| `<useJava7Types>` | boolean | `false` | When `true`, generate 
`java.util.Date` instead of `java.time.LocalDate`/`LocalDateTime`. Use only 
when targeting legacy JVMs. |
+
+### Behavior toggles
+
+| Field | Type | Default | Meaning |
+|---|---|---|---|
+| `<skipPrimaryKeyLoading>` | boolean | `false` | When `true`, PK metadata is 
not read from the DB. Rare. |
+| `<skipRelationshipsLoading>` | boolean | `false` | When `true`, FKs are not 
read. Useful when FKs are missing in the schema and modeled manually. |
+| `<forceDataMapCatalog>` | boolean | `false` | When `true`, every imported 
DbEntity is tagged with the DataMap's catalog, overriding the DB's reported 
value. Use only when the DB catalog reported by JDBC is wrong or noisy. |
+| `<forceDataMapSchema>` | boolean | `false` | Same as above for schema. |
+
+## How the Modeler wizard maps to these fields
+
+Walking the **Tools → Reengineer Database Schema** wizard, the screens 
correspond to:
+
+1. **Datasource** — JDBC connection (adapter, driver, URL, user/password). Not 
stored in `<dbImport>`; it's a one-shot connection.
+2. **Configure** — filter tables/columns/procedures. Maps to `<includeTable>`, 
`<excludeTable>`, `<tableTypes>`, etc.
+3. **Naming** — `<namingStrategy>`, `<stripFromTableNames>`, 
`<defaultPackage>`, `<meaningfulPkTables>`.
+4. **Other options** — checkboxes for `skipPrimaryKeyLoading`, 
`skipRelationshipsLoading`, `forceDataMapCatalog`, `forceDataMapSchema`, 
`useJava7Types`.
+
+After running the wizard, the chosen settings are persisted as a `<dbImport>` 
block in the DataMap so subsequent re-imports re-use them.
+
+## Re-running an import
+
+A second import against the same DataMap is *merge-based*: existing 
entities/attributes/relationships that match the DB are preserved; new ones are 
added; the user is offered a chance to drop ones that no longer exist in the DB.
+
+User-edited names, types, and custom attributes generally survive re-import. 
Custom relationships that don't correspond to a DB FK do not survive.
+
+## Anti-patterns
+
+- Setting `forceDataMapCatalog` / `forceDataMapSchema` "just to be safe" — 
they suppress useful DB metadata and cause subtle bugs in multi-catalog setups.
+- Using `useJava7Types=true` on a new project — `java.time` types are strictly 
better. Only enable for legacy.
+- Importing without any `<includeTable>` filter on a large schema — you get 
every system table and view.
diff --git a/ai-plugin/references/mcp-tools.md 
b/ai-plugin/references/mcp-tools.md
new file mode 100644
index 000000000..6e9ec2f56
--- /dev/null
+++ b/ai-plugin/references/mcp-tools.md
@@ -0,0 +1,81 @@
+# Cayenne MCP tools
+
+The Cayenne MCP server (`cayenne-mcp-server` module) exposes Cayenne 
operations to AI agents over stdio.
+
+**Availability: Cayenne 5.0+ only.** The MCP server is a new component shipped 
alongside CayenneModeler starting with the 5.0 release. There is no MCP server 
for Cayenne 4.x or earlier — skills that depend on these tools (`cayenne-cgen`, 
`cayenne-modeler`, `cayenne-reverse-engineer`) cannot be used against pre-5.0 
projects.
+
+Setup is documented in `cayenne-mcp-server/README.md` at the repo root. Quick 
form for Claude Code:
+
+```bash
+claude mcp add cayenne --scope user -- java -jar 
/path/to/cayenne-mcp-server-<VERSION>.jar
+```
+
+Verify the server is registered with `claude mcp list` — you should see an 
entry named `cayenne` (the MCP server alias from the `claude mcp add cayenne` 
command above) showing as connected. This is unrelated to the plugin name 
`apache-cayenne`.
+
+## Tool: `cgen_run`
+
+Runs Cayenne's class generator for one DataMap, using the `<cgen>` config 
block embedded in that DataMap.
+
+**Arguments:**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `projectPath` | string | yes | Absolute path to the top-level Cayenne 
project descriptor (`cayenne-*.xml`), **not** a DataMap file. |
+| `dataMap` | string | yes | Name of the target DataMap as it appears in the 
`<map name="...">` element of the project descriptor. Not a file path. |
+
+**Returns:** JSON object with fields:
+
+```json
+{
+  "status": "ok" | "error",
+  "summary": { "writtenCount": 3, "skippedCount": 12, "errorCount": 0 },
+  "resolvedConfig": { "destDir": "...", "mode": "entity", ... },
+  "writtenFiles": [{ "path": "...", "size": 1234 }],
+  "skippedFiles": [{ "path": "...", "reason": "up-to-date" }],
+  "errors": []
+}
+```
+
+Surface the `summary` to the user verbatim. List the first few `writtenFiles` 
paths; full list is informational. If `errors` is non-empty, those are blocking 
— show them.
+
+**Failure modes:**
+
+- DataMap has no `<cgen>` block → error; user must add one (see 
`cgen-config.md` for the XML to insert).
+- `projectPath` not readable or not a valid Cayenne project → validation error.
+- `dataMap` doesn't match any `<map name="...">` → validation error.
+
+**Source:** 
`cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/CgenRunTool.java`.
+
+## Tool: `open_project`
+
+Launches CayenneModeler with a project file pre-loaded.
+
+**Arguments:**
+
+| Name | Type | Required | Description |
+|---|---|---|---|
+| `projectPath` | string | yes | Absolute path to the top-level Cayenne 
project descriptor (`cayenne-*.xml`). |
+
+**Behavior:**
+
+1. Locates the bundled CayenneModeler installation alongside the running MCP 
jar.
+2. Spawns it with `--mcp-handshake <nonce>`.
+3. Waits up to ~15 seconds for the Modeler to write a 
`java.util.prefs.Preferences` handshake confirming successful project load.
+4. Returns once the handshake fires or times out.
+
+**Returns:** JSON object with `status` (`ok` / `error`), and on error a `code` 
indicating the failure (e.g., `modeler_not_found`, `project_not_found`, 
`handshake_timeout`).
+
+**When to call:** for reverse-engineering workflows (drives the user through 
the Modeler's import wizard), bulk visual editing, or when the user explicitly 
asks to open the Modeler.
+
+**Don't call it** as a fallback for simple XML edits — direct edits via the 
`cayenne-modeling` skill are faster and don't require the user to switch 
context.
+
+**Source:** 
`cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/OpenProjectTool.java`.
+
+## Detecting whether the server is connected
+
+The MCP tools surface in this session under names `mcp__cayenne__cgen_run` and 
`mcp__cayenne__open_project`. If they are not in the available tools, the 
server is not registered.
+
+When unavailable:
+
+- Do **not** fall back to `mvn cayenne:cgen` or any Gradle equivalent — those 
build plugins are practically deprecated and are intentionally out of scope for 
this Claude Code plugin.
+- Point the user at `cayenne-mcp-server/README.md` for setup and stop.
diff --git a/ai-plugin/references/project-descriptor-schema.md 
b/ai-plugin/references/project-descriptor-schema.md
new file mode 100644
index 000000000..b8833a8e3
--- /dev/null
+++ b/ai-plugin/references/project-descriptor-schema.md
@@ -0,0 +1,83 @@
+# Project descriptor XML schema (`cayenne-*.xml`)
+
+Reference for editing the top-level Cayenne project descriptor.
+
+- **Namespace**: `http://cayenne.apache.org/schema/12/domain`
+- **XSD**: `http://cayenne.apache.org/schema/12/domain.xsd`
+- **Project version**: `12` (Cayenne 5.0)
+
+## Minimal descriptor
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<domain xmlns="http://cayenne.apache.org/schema/12/domain";
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+        xsi:schemaLocation="http://cayenne.apache.org/schema/12/domain 
https://cayenne.apache.org/schema/12/domain.xsd";
+        project-version="12">
+    <map name="mydb"/>
+</domain>
+```
+
+This references a sibling `mydb.map.xml`.
+
+## With a DataNode (DB connection)
+
+Most production setups configure the DataSource in code via 
`CayenneRuntimeBuilder` (see `runtime-api.md`) rather than the descriptor — 
keeps secrets out of source control. But the descriptor *can* hold a DataNode:
+
+```xml
+<domain ...>
+    <map name="mydb"/>
+    <node name="mydb-node"
+          
factory="org.apache.cayenne.configuration.runtime.XMLPoolingDataSourceFactory"
+          
schema-update-strategy="org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy">
+        <map-ref name="mydb"/>
+        <data-source>
+            <driver value="org.postgresql.Driver"/>
+            <url value="jdbc:postgresql://localhost:5432/mydb"/>
+            <connectionPool min="1" max="10"/>
+            <login userName="cayenne" password="secret"/>
+        </data-source>
+    </node>
+</domain>
+```
+
+| Element/attribute | Meaning |
+|---|---|
+| `<map name="...">` | References a sibling DataMap file. Must match the 
`*.map.xml` file name minus the extension. |
+| `<node name="...">` | A DataNode (database). The `factory=` selects the 
DataSource factory. |
+| `factory=` | `XMLPoolingDataSourceFactory` reads the inline `<data-source>` 
block. `DBCPDataSourceFactory`, `JNDIDataSourceFactory` are alternatives. |
+| `schema-update-strategy=` | What to do on startup when DB schema disagrees 
with the model. `SkipSchemaUpdateStrategy` is the safe default. |
+| `<map-ref name="...">` | Which DataMap this node serves. A node can serve 
multiple maps. |
+
+## Multi-map projects
+
+A descriptor can reference multiple DataMaps:
+
+```xml
+<domain ...>
+    <map name="customers"/>
+    <map name="orders"/>
+    <node name="primary">
+        <map-ref name="customers"/>
+        <map-ref name="orders"/>
+        ...
+    </node>
+</domain>
+```
+
+All referenced `*.map.xml` files must sit in the same directory as the 
descriptor.
+
+## Recommended pattern
+
+For a typical app:
+
+- Descriptor lists DataMaps with `<map>` only; **no** `<node>` element.
+- Wire the DataSource in code via `CayenneRuntimeBuilder.dataSource(...)` or 
`.url(...).jdbcDriver(...)`. See `runtime-api.md`.
+
+This keeps DB credentials out of XML and lets the app pick the DataSource per 
environment (test, dev, prod).
+
+## Anti-patterns
+
+- Putting JDBC credentials in `cayenne-*.xml` and committing them to source 
control. Use code-level configuration.
+- Referencing a DataMap that doesn't exist as a sibling file — the runtime 
will fail to load.
+- Forgetting `project-version="12"` — older versions are silently rejected by 
Cayenne 5.0.
diff --git a/ai-plugin/references/project-layout.md 
b/ai-plugin/references/project-layout.md
new file mode 100644
index 000000000..4fba3528a
--- /dev/null
+++ b/ai-plugin/references/project-layout.md
@@ -0,0 +1,52 @@
+# Cayenne project layout
+
+How a Cayenne project lays out on disk and how to locate the relevant files in 
a user's repo.
+
+## Two file types
+
+A Cayenne project consists of two kinds of XML files:
+
+1. **Project descriptor** — one per project. File name pattern: 
`cayenne-*.xml` (e.g. `cayenne-mydb.xml`). Root element `<domain>` in namespace 
`http://cayenne.apache.org/schema/12/domain`. Lists DataMaps and DataNodes.
+2. **DataMap files** — one or more per project. File name pattern: `*.map.xml` 
(commonly `<name>.map.xml`, where `<name>` matches the `<map name="...">` 
reference in the project descriptor). Root element `<data-map>` in namespace 
`http://cayenne.apache.org/schema/12/modelMap`. Contains entities, 
relationships, queries.
+
+The descriptor and its DataMaps live in the **same directory**. The descriptor 
references DataMaps by name only (no path), so they must be siblings on the 
classpath.
+
+## Where to look
+
+Common locations, in order of likelihood:
+
+1. **`src/main/resources/`** of the module that bootstraps `CayenneRuntime`. 
This is the typical placement for a runtime-bundled project. Scan for 
`cayenne-*.xml` here first.
+2. **`src/main/resources/<package>/`** — a subdirectory if the user wants to 
keep mapping files namespaced.
+3. **Repo root or `cayenne/` subdir** — small projects sometimes keep mapping 
at the top.
+
+To find them programmatically:
+
+```bash
+find . -name "cayenne-*.xml" -not -path "*/target/*" -not -path "*/build/*"
+find . -name "*.map.xml" -not -path "*/target/*" -not -path "*/build/*"
+```
+
+Filter out `target/`, `build/`, and any test-resource paths unless the user is 
explicitly editing test fixtures.
+
+## Identifying the active project
+
+If multiple `cayenne-*.xml` files exist, identify the one the application 
actually uses:
+
+- Search the Java code for `CayenneRuntime.builder().addConfig("...")` — the 
string argument is the descriptor file name (e.g. `"cayenne-mydb.xml"`).
+- Or look for `addConfigs("...")` for multi-config setups.
+- If none is found, the runtime may rely on auto-loading 
(`cayenne-project.xml` is *not* a Cayenne convention — Cayenne does not 
auto-load by a default name).
+
+When ambiguous, ask the user which project they mean. Cache the answer for the 
rest of the session.
+
+## What goes where
+
+| Change | File to edit |
+|---|---|
+| Add/remove an `ObjEntity`, `DbEntity`, attribute, relationship, embeddable, 
named query | DataMap (`*.map.xml`) |
+| Change a DataMap's `defaultPackage` or `defaultSuperclass` | DataMap 
(`*.map.xml`) — top-level `<property>` |
+| Add/remove a DataMap from the project | Project descriptor (`cayenne-*.xml`) 
— `<map>` element |
+| Add/configure a DataNode (DB connection) | Project descriptor 
(`cayenne-*.xml`) — `<node>` element |
+| Embedded code-generation config (`<cgen>`) | DataMap (`*.map.xml`) |
+| Embedded reverse-engineering config (`<dbImport>`) | DataMap (`*.map.xml`) |
+
+Schema details live in `datamap-schema.md` and `project-descriptor-schema.md`.
diff --git a/ai-plugin/references/query-api.md 
b/ai-plugin/references/query-api.md
new file mode 100644
index 000000000..1808c73cd
--- /dev/null
+++ b/ai-plugin/references/query-api.md
@@ -0,0 +1,224 @@
+# Query API reference
+
+How to fetch and filter persistent objects with Cayenne 5.0.
+
+## Three query mechanisms
+
+| API | When to use |
+|---|---|
+| `ObjectSelect` | Default for selecting Java entities. Fluent, type-safe. |
+| `SQLSelect` / `SQLExec` | Raw SQL with Cayenne parameter binding. |
+| Named queries (XML) | When the same query is reused many places; lives in 
the DataMap. |
+
+## ObjectSelect — primary fluent query API
+
+`org.apache.cayenne.query.ObjectSelect`. Factory methods:
+
+```java
+import org.apache.cayenne.query.ObjectSelect;
+
+// Fetch all
+List<Artist> all = ObjectSelect.query(Artist.class).select(ctx);
+
+// Single by criteria
+Artist picasso = ObjectSelect.query(Artist.class)
+        .where(Artist.ARTIST_NAME.eq("Picasso"))
+        .selectOne(ctx);
+
+// First matching, null if none
+Artist maybe = ObjectSelect.query(Artist.class)
+        .where(Artist.ARTIST_NAME.eq("Picasso"))
+        .selectFirst(ctx);
+
+// Iterate large result without loading all
+try (ResultIterator<Artist> it = 
ObjectSelect.query(Artist.class).iterator(ctx)) {
+    while (it.hasNextRow()) {
+        Artist a = it.nextRow();
+        ...
+    }
+}
+```
+
+### Filtering with Property constants
+
+cgen generates `Property<T>` constants on each entity superclass: 
`Artist.ARTIST_NAME`, `Artist.DATE_OF_BIRTH`, `Artist.PAINTINGS`. These are the 
recommended way to express filters because they are type-checked.
+
+```java
+ObjectSelect.query(Artist.class)
+        .where(Artist.ARTIST_NAME.likeIgnoreCase("p%"))
+        .and(Artist.DATE_OF_BIRTH.between(d1, d2))
+        .or(Artist.ARTIST_NAME.eq("Anonymous"))
+        .select(ctx);
+```
+
+Common predicate methods on `Property<T>`:
+
+- `.eq(value)`, `.ne(value)`
+- `.lt(value)`, `.lte(value)`, `.gt(value)`, `.gte(value)`
+- `.like(pattern)`, `.likeIgnoreCase(pattern)`, `.contains(s)`, 
`.startsWith(s)`, `.endsWith(s)`
+- `.in(values...)`, `.nin(values...)`
+- `.isNull()`, `.isNotNull()`
+- `.between(min, max)`
+
+### Ordering
+
+```java
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.SortOrder;
+
+ObjectSelect.query(Artist.class)
+        .orderBy(Artist.ARTIST_NAME.asc())
+        .orderBy(Artist.DATE_OF_BIRTH.desc())
+        .select(ctx);
+```
+
+### Pagination
+
+```java
+ObjectSelect.query(Artist.class)
+        .pageSize(50)              // server-side pagination
+        .limit(1000)
+        .offset(0)
+        .select(ctx);
+```
+
+`pageSize` enables on-demand loading: the result list contains all PKs but 
only the requested page's full objects are materialized when accessed.
+
+### Prefetching (avoiding N+1)
+
+```java
+// Joint: same query, single SQL
+ObjectSelect.query(Artist.class)
+        .prefetch(Artist.PAINTINGS.joint())
+        .select(ctx);
+
+// Disjoint: separate query per relationship
+ObjectSelect.query(Artist.class)
+        .prefetch(Artist.PAINTINGS.disjoint())
+        .select(ctx);
+
+// Disjoint-by-id: separate query keyed by source PKs
+ObjectSelect.query(Artist.class)
+        .prefetch(Artist.PAINTINGS.disjointById())
+        .select(ctx);
+```
+
+Rule of thumb: `joint()` for to-one, `disjoint()` for to-many that fans out 
modestly, `disjointById()` for large to-many.
+
+### Caching
+
+```java
+ObjectSelect.query(Artist.class)
+        .localCache()                    // per-context cache
+        .select(ctx);
+
+ObjectSelect.query(Artist.class)
+        .sharedCache("artistGroup")      // cross-context, named cache group
+        .select(ctx);
+```
+
+Use `sharedCache` for reference data. Invalidate by group via 
`cayenne-cache-invalidation` if installed.
+
+### Aggregates and column-only queries
+
+```java
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.exp.property.PropertyFactory;
+
+// Single column
+List<String> names = ObjectSelect.columnQuery(Artist.class, 
Artist.ARTIST_NAME).select(ctx);
+
+// Multiple columns
+List<Object[]> rows = ObjectSelect.columnQuery(Artist.class,
+        Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH).select(ctx);
+
+// Aggregate
+long count = ObjectSelect.query(Artist.class).selectCount(ctx);
+```
+
+### Selecting by primary key
+
+```java
+import org.apache.cayenne.query.SelectById;
+
+Artist a = SelectById.query(Artist.class, 42).selectOne(ctx);
+```
+
+## SQLSelect / SQLExec — raw SQL
+
+```java
+import org.apache.cayenne.query.SQLSelect;
+import org.apache.cayenne.query.SQLExec;
+
+// Select into entities
+List<Artist> artists = SQLSelect.query(Artist.class,
+        "SELECT * FROM ARTIST WHERE ARTIST_NAME LIKE #bind($name)")
+        .params(Map.of("name", "P%"))
+        .select(ctx);
+
+// Select into raw rows
+List<Object[]> rows = SQLSelect.scalarQuery(Object[].class,
+        "SELECT ARTIST_ID, ARTIST_NAME FROM ARTIST")
+        .select(ctx);
+
+// Execute (insert/update/delete)
+int affected = SQLExec.query(
+        "UPDATE ARTIST SET ARTIST_NAME = UPPER(ARTIST_NAME)")
+        .update(ctx);
+```
+
+Parameter binding uses Velocity-like syntax: `#bind($name)`, 
`#bindEqual($name)`, `#bindNotEqual($name)`, `#chain('AND' 'condition' 
"$param")`. Never concatenate user input into SQL strings — use bindings.
+
+## Expression — programmatic predicates
+
+`Expression` is Cayenne's predicate AST. Most queries don't construct one 
directly — they let `Property` constants do it.
+
+### Preferred: build expressions from `Property` constants
+
+The `Property<T>` constants on each entity superclass (`Artist.ARTIST_NAME`, 
`Artist.DATE_OF_BIRTH`, `Artist.PAINTINGS`, etc., declared on the generated 
`_Artist` superclass and inherited by the user `Artist` subclass) return 
`Expression` objects from every predicate method. Compose them with `andExp` / 
`orExp`:
+
+```java
+import org.apache.cayenne.exp.Expression;
+
+Expression e = Artist.ARTIST_NAME.eq("Picasso")
+        .andExp(Artist.DATE_OF_BIRTH.gt(someDate));
+
+ObjectSelect.query(Artist.class).where(e).select(ctx);
+```
+
+This is the canonical pattern — see Cayenne's own test suites for many 
real-world examples (`Artist.ARTIST_NAME.eq(...)`, 
`Painting.TO_ARTIST.eq(artist)`, etc.). It's type-checked, refactor-safe, and 
survives column renames in the model.
+
+For relationship traversal, chain `Property` constants with `.dot(...)`:
+
+```java
+Expression hasGuernica = 
Artist.PAINTINGS.dot(Painting.PAINTING_TITLE).eq("Guernica");
+```
+
+### Fallback: `ExpressionFactory` (dynamic field names)
+
+Only when field selection is dynamic at runtime (e.g., the user picks a column 
from a UI dropdown):
+
+```java
+import org.apache.cayenne.exp.ExpressionFactory;
+
+String runtimeField = pickField();   // not known at compile time
+Expression e = ExpressionFactory.matchExp(runtimeField, value);
+```
+
+Avoid `ExpressionFactory.*` for static queries — `Property` constants are 
strictly better.
+
+### Fallback: `Expression.fromString(...)` (parsed string form)
+
+```java
+Expression e = Expression.fromString("artistName = 'Picasso' and dateOfBirth > 
$cutoff");
+e = e.params(Map.of("cutoff", someDate));
+```
+
+Supports `=`, `!=`, `<`, `>`, `<=`, `>=`, `like`, `likeIgnoreCase`, `in`, 
`between`, `and`, `or`, `not`, `null`. Dotted paths traverse relationships: 
`paintings.gallery.galleryName = 'MoMA'`. Useful when expressions are stored as 
strings (e.g. in config); otherwise prefer Property constants.
+
+## Anti-patterns
+
+- **String concatenation in raw SQL.** Always use `#bind($name)`. 
Concatenation is a SQL injection vector.
+- **Using `selectOne` when multiple may match.** It throws. Use `selectFirst` 
if "any one" is okay.
+- **Loading large result sets without `pageSize` or `iterator()`.** Both 
methods avoid materializing the full list.
+- **N+1 from missing prefetch.** If you iterate `artists` and access 
`artist.getPaintings()` per artist, you'll fire one query per artist. Use 
`.prefetch(...)`.
diff --git a/ai-plugin/references/runtime-api.md 
b/ai-plugin/references/runtime-api.md
new file mode 100644
index 000000000..5ccc20b97
--- /dev/null
+++ b/ai-plugin/references/runtime-api.md
@@ -0,0 +1,143 @@
+# Runtime API reference
+
+How to bootstrap Cayenne in a Java application and use `ObjectContext` for 
CRUD.
+
+**Availability: Cayenne 5.0+ only.** `CayenneRuntime` and 
`CayenneRuntimeBuilder` are 5.0 names — they were called `ServerRuntime` and 
`ServerRuntimeBuilder` (in package `org.apache.cayenne.configuration.server`) 
in Cayenne 4.x. If the user's project is on 4.x, the patterns below won't 
compile — this plugin is targeted at 5.0.
+
+## CayenneRuntime — top-level container
+
+`org.apache.cayenne.runtime.CayenneRuntime` is the entry point. One per 
application lifetime (or per data domain in multi-DB setups).
+
+Two creation paths:
+
+### 1. With a built-in connection pool
+
+```java
+import org.apache.cayenne.runtime.CayenneRuntime;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+
+CayenneRuntime runtime = CayenneRuntime.builder()
+        .addConfig("cayenne-mydb.xml")
+        .url("jdbc:postgresql://localhost:5432/mydb")
+        .jdbcDriver("org.postgresql.Driver")
+        .user("cayenne")
+        .password("secret")
+        .minConnections(1)
+        .maxConnections(10)
+        .build();
+```
+
+### 2. With an external DataSource (Spring, HikariCP, Bootique, etc.)
+
+```java
+import javax.sql.DataSource;
+
+DataSource ds = ...; // created elsewhere in your app
+
+CayenneRuntime runtime = CayenneRuntime.builder()
+        .addConfig("cayenne-mydb.xml")
+        .dataSource(ds)
+        .build();
+```
+
+This path is preferred in production — your existing DI framework manages the 
pool, and Cayenne uses it.
+
+### CayenneRuntimeBuilder methods
+
+From 
`cayenne/src/main/java/org/apache/cayenne/runtime/CayenneRuntimeBuilder.java`:
+
+| Method | Purpose |
+|---|---|
+| `addConfig(String)` | Add a Cayenne project descriptor (`cayenne-*.xml`) 
location. Resolved via classpath. |
+| `addConfigs(String...)` | Add multiple descriptors. |
+| `dataSource(DataSource)` | Use an existing DataSource. Overrides anything in 
the XML. |
+| `url(String)` | Built-in pool: JDBC URL. |
+| `jdbcDriver(String)` | Built-in pool: driver class name. |
+| `user(String)` / `password(String)` | Built-in pool: credentials. |
+| `minConnections(int)` / `maxConnections(int)` | Built-in pool: bounds. |
+| `validationQuery(String)` | Built-in pool: pool validation query (e.g. 
`SELECT 1`). |
+| `maxQueueWaitTime(long)` | Built-in pool: max ms to wait for a connection. |
+| `addModule(Module)` | Add a Cayenne DI module to override defaults. |
+| `disableModulesAutoLoading()` | Skip the 
`META-INF/services/CayenneRuntimeModuleProvider` auto-load — for 
tightly-controlled DI. |
+| `build()` | Construct the `CayenneRuntime`. |
+
+## Lifecycle
+
+`CayenneRuntime` is **expensive** to create — it parses XML, validates the 
model, sets up the connection pool. Create it **once**, share it for the 
application lifetime, and call `shutdown()` on app exit:
+
+```java
+runtime.shutdown();
+```
+
+Never create a new `CayenneRuntime` per request.
+
+## ObjectContext — the unit of work
+
+`org.apache.cayenne.ObjectContext` is the per-request handle for CRUD. Get one 
from the runtime:
+
+```java
+ObjectContext ctx = runtime.newContext();
+```
+
+Each `ObjectContext`:
+
+- Has its own session-level cache of loaded objects.
+- Tracks modifications until you call `commitChanges()`.
+- While ObjectContext is thread-safe, objects within in are **not 
thread-safe**. So read-only contexts can be used across threads, whereas 
read/write ones (those that change object state) would require a new context 
per request/thread.
+
+### Core methods
+
+```java
+// Insert
+Artist a = ctx.newObject(Artist.class);
+a.setArtistName("Picasso");
+ctx.commitChanges();
+
+// Read by ID
+Artist a = SelectById.query(Artist.class, 42).selectOne(ctx);
+
+// Update
+a.setArtistName("Pablo Picasso");
+ctx.commitChanges();
+
+// Delete
+ctx.deleteObject(a);
+ctx.commitChanges();
+
+// Rollback uncommitted changes
+ctx.rollbackChanges();
+```
+
+### Transactions
+
+`commitChanges()` runs inside an implicit transaction by default. For 
multi-step transactions, use `Cayenne.transactional`:
+
+```java
+import org.apache.cayenne.tx.BaseTransaction;
+import org.apache.cayenne.tx.TransactionalOperation;
+
+runtime.performInTransaction(() -> {
+    Artist a = ctx.newObject(Artist.class);
+    a.setArtistName("Picasso");
+    ctx.commitChanges();
+
+    Painting p = ctx.newObject(Painting.class);
+    p.setArtist(a);
+    p.setTitle("Guernica");
+    ctx.commitChanges();
+    return null;
+});
+```
+
+If any step throws, the whole transaction rolls back.
+
+## DI: integrating with external containers
+
+Cayenne ships its own lightweight DI (`cayenne-di`). It does not require 
Spring, etc. To use Cayenne inside an application DI container (Spring, 
Bootique), bind `CayenneRuntime` as a singleton and decide how you want to 
create `ObjectContext` (request-scoped, method scoped, etc.)
+
+## Common mistakes
+
+- **Creating `CayenneRuntime` per request.** It's a heavyweight singleton. 
Cache it.
+- **Sharing `ObjectContext` that change object state across threads.** Not 
safe. New context per thread.
+- **Mixing objects across contexts.** A `Painting` loaded in context A cannot 
be assigned to an `Artist` in context B. Use `ctx.localObject(otherObject)` to 
copy across.
+- **Forgetting `commitChanges()`.** Mutations to persistent objects stay in 
memory until commit.
diff --git a/ai-plugin/skills/cayenne-cgen/SKILL.md 
b/ai-plugin/skills/cayenne-cgen/SKILL.md
new file mode 100644
index 000000000..64c9a3133
--- /dev/null
+++ b/ai-plugin/skills/cayenne-cgen/SKILL.md
@@ -0,0 +1,77 @@
+---
+name: cayenne-cgen
+description: "Use this skill whenever the user wants to (re)generate Cayenne 
entity Java classes from a DataMap. Trigger on phrases like 'generate Java 
classes', 'regenerate entities', 'run cgen', 'create the entity classes', 'why 
is the Artist class missing fields', 'where did the `_Abstract*` classes come 
from', 'sync the entity classes with the model', or any request to materialize 
Java from the DataMap. Also trigger as a follow-up after modeling changes 
(someone added an entity, attr [...]
+---
+
+# cayenne-cgen
+
+Run Cayenne's class generator on a DataMap via the `mcp__cayenne__cgen_run` 
MCP tool. Reads the embedded `<cgen>` block in the DataMap to determine 
destination, mode, templates, etc.
+
+## Required reading
+
+- `${CLAUDE_PLUGIN_ROOT}/references/mcp-tools.md` — `cgen_run` tool reference 
(arguments, return shape, failure modes).
+- `${CLAUDE_PLUGIN_ROOT}/references/cgen-config.md` — every `<cgen>` field; 
needed when the user has to add or tweak the config block.
+- `${CLAUDE_PLUGIN_ROOT}/references/project-layout.md` — locate the project 
descriptor.
+
+## Step 1 — Resolve project and DataMap
+
+The MCP tool needs two arguments:
+
+- `projectPath` — **absolute path** to the top-level project descriptor 
(`cayenne-*.xml`). **Not** a DataMap file.
+- `dataMap` — the **name** as it appears in `<map name="...">` in the 
descriptor. **Not** a file path.
+
+Locate the descriptor via `project-layout.md`. If multiple descriptors exist, 
ask which one. Open the descriptor to extract the DataMap names from `<map>` 
elements.
+
+If the user named the DataMap directly (e.g. "regenerate classes for the 
customers DataMap"), use that. If they said "regenerate everything" and there 
are multiple DataMaps, run `cgen_run` once per DataMap in sequence (the tool 
generates per-DataMap).
+
+## Step 2 — Verify `<cgen>` block exists
+
+Before calling the tool, check the target DataMap for a `<cgen 
xmlns="http://cayenne.apache.org/schema/12/cgen";>` block. If missing:
+
+1. Don't call `cgen_run` blindly — it will return an error.
+2. Either:
+   - **Add a minimal block** by delegating to the `cayenne-modeling` skill. 
Use the starter config from `cgen-config.md`:
+     ```xml
+     <cgen xmlns="http://cayenne.apache.org/schema/12/cgen";>
+         <destDir>../java</destDir>
+         <mode>entity</mode>
+         <makePairs>true</makePairs>
+         <usePkgPath>true</usePkgPath>
+         <createPKProperties>true</createPKProperties>
+     </cgen>
+     ```
+     Confirm `destDir` with the user — it's relative to the project XML 
directory and decides where generated `.java` files land.
+   - Or, if the user prefers the GUI, suggest the `cayenne-modeler` skill and 
tell them to configure the DataMap's "Class Generation" tab.
+
+## Step 3 — Call `cgen_run`
+
+```
+mcp__cayenne__cgen_run({
+  "projectPath": "<absolute path to cayenne-*.xml>",
+  "dataMap": "<map name from the descriptor>"
+})
+```
+
+If the tool is not available (MCP server not registered), surface 
`cayenne-mcp-server/README.md` and stop. **Do not** suggest `mvn cayenne:cgen` 
or the Gradle cgen task.
+
+## Step 4 — Surface the result
+
+The tool returns structured JSON. Report:
+
+- `summary.writtenCount`, `summary.skippedCount`, `summary.errorCount` 
verbatim — these are the headline.
+- The first few entries in `writtenFiles` (relative paths). Full list is 
informational; offer to dump it if the user asks.
+- Any entries in `errors` — these are blocking. Read the messages and explain 
in user terms (a missing entity class name, a bad template path, an invalid 
`<destDir>`, etc.).
+
+If `writtenCount` is 0 and `skippedCount` covers everything, say so — it means 
everything is already up-to-date and no work was needed.
+
+## Step 5 — Next steps
+
+- If new `_<Entity>.java` superclass files were generated, gently remind the 
user not to edit those — they will be overwritten next run. User code goes in 
the matching `<Entity>.java` subclass.
+- If the user just ran reverse engineering, this skill is the natural 
follow-up. Cross-link back to `cayenne-modeling` if they need to tweak 
names/types before regenerating.
+
+## Anti-patterns
+
+- **Do not** call `cgen_run` without verifying the `<cgen>` block exists — the 
tool returns an error and confuses the user.
+- **Do not** suggest Maven (`mvn cayenne:cgen`) or Gradle (`cayenneCgen`) 
goals when MCP is unavailable. Those build plugins are out of scope. Point at 
MCP setup instead.
+- **Do not** edit `_<Entity>.java` files. Generated superclasses. Edit 
`<Entity>.java` subclasses.
+- **Do not** confuse `projectPath` and `dataMap` arguments. `projectPath` is a 
file system path to `cayenne-*.xml`. `dataMap` is a logical name (e.g. `mydb`), 
not a path to `mydb.map.xml`.
diff --git a/ai-plugin/skills/cayenne-modeler/SKILL.md 
b/ai-plugin/skills/cayenne-modeler/SKILL.md
new file mode 100644
index 000000000..76d52e3f1
--- /dev/null
+++ b/ai-plugin/skills/cayenne-modeler/SKILL.md
@@ -0,0 +1,61 @@
+---
+name: cayenne-modeler
+description: "Use this skill when the user explicitly wants to open 
CayenneModeler (the GUI) on a Cayenne project, or when the modeling task is 
inherently visual — reverse engineering (delegated to 
cayenne-reverse-engineer), bulk relationship layout, multi-entity visual 
refactoring. Trigger on phrases like 'open the Modeler', 'open in 
CayenneModeler', 'launch the GUI', 'edit visually', 'show me the project in the 
Modeler'. Do NOT trigger as a fallback for ordinary a-la-carte XML edits —  
[...]
+---
+
+# cayenne-modeler
+
+Launch CayenneModeler with a Cayenne project file pre-loaded, via the 
`mcp__cayenne__open_project` MCP tool.
+
+## Required reading
+
+- `${CLAUDE_PLUGIN_ROOT}/references/mcp-tools.md` — `open_project` tool 
reference and failure modes.
+- `${CLAUDE_PLUGIN_ROOT}/references/project-layout.md` — locate the project 
descriptor.
+
+## Step 1 — Resolve the project path
+
+The MCP tool needs an **absolute path** to a top-level project descriptor 
(`cayenne-*.xml`). Use `project-layout.md` to find it. If multiple descriptors 
exist, ask which one. Cache for the rest of the session.
+
+If no descriptor exists yet, the user is starting from scratch. Two options:
+
+1. Create a minimal descriptor first (use `cayenne-modeling` to scaffold it), 
then open.
+2. Open the Modeler on *any* path it'll accept and use **File → New Project** 
inside.
+
+For starting fresh, option 2 is simpler — but `open_project` does require an 
existing readable file.
+
+## Step 2 — Call `open_project`
+
+```
+mcp__cayenne__open_project({ "projectPath": "<absolute path to cayenne-*.xml>" 
})
+```
+
+If the tool is not available (MCP server not registered), surface 
`cayenne-mcp-server/README.md` and stop. Do not attempt to launch 
CayenneModeler by any other means — the bundled launcher relies on the MCP 
server's discovery of the Modeler installation.
+
+The tool spawns the Modeler asynchronously, then waits up to ~15 seconds for a 
startup handshake. Possible return codes:
+
+| Code | Meaning |
+|---|---|
+| `ok` | Modeler launched and loaded the project. |
+| `modeler_not_found` | The MCP jar is not co-located with a Modeler 
installation. User needs to install CayenneModeler properly. |
+| `project_not_found` | The `projectPath` doesn't exist or isn't readable. |
+| `handshake_timeout` | The Modeler started but didn't confirm load within the 
timeout. Often this means it's still loading — the user can check the GUI 
directly. |
+| `launch_failed` | Process spawn failed. Surface the error message. |
+
+## Step 3 — Hand off to the user
+
+Once `open_project` returns `ok`, the user is in the GUI. From here, depending 
on intent:
+
+- **Reverse engineering**: that's the `cayenne-reverse-engineer` skill's job 
to walk them through. Do not duplicate that workflow here — just open and step 
out.
+- **Visual layout / bulk editing**: tell the user what tab to navigate to 
(e.g., DataMap → ObjEntity for entity-level edits, DataMap → Class Generation 
for cgen config) and let them work. Don't try to script GUI actions.
+- **Just wanted to see the project**: nothing more to do.
+
+## When NOT to use this skill
+
+- **A-la-carte XML edits.** Adding one entity, tweaking one attribute, 
renaming a relationship — all faster as direct XML edits via 
`cayenne-modeling`. Don't context-switch the user to the GUI for trivial work.
+- **Class generation.** That's `cayenne-cgen`'s job, fully scripted via MCP. 
No GUI needed.
+
+## Anti-patterns
+
+- **Do not** trigger as a generic fallback for "Cayenne tasks." This skill is 
for explicit user requests or inherently visual workflows. The 
`cayenne-modeling` skill handles most modeling intent.
+- **Do not** retry `open_project` on `handshake_timeout` — the Modeler may 
still be loading. Tell the user to check the GUI window directly.
+- **Do not** attempt to launch CayenneModeler outside the MCP tool (no `java 
-jar` direct calls, no opening a `.dmg`, no `open -a CayenneModeler`). The MCP 
tool's launcher knows where the installation lives.
diff --git a/ai-plugin/skills/cayenne-modeling/SKILL.md 
b/ai-plugin/skills/cayenne-modeling/SKILL.md
new file mode 100644
index 000000000..91735e8e0
--- /dev/null
+++ b/ai-plugin/skills/cayenne-modeling/SKILL.md
@@ -0,0 +1,74 @@
+---
+name: cayenne-modeling
+description: "Use this skill whenever the user wants to edit, inspect, or 
extend the Cayenne ORM model in a project — adding or modifying entities, 
attributes, relationships, embeddables, named queries, stored procedures, or 
DataNodes. Trigger on phrases like 'add an ObjEntity', 'add a DbEntity', 'add a 
relationship', 'expose this column as an attribute', 'create a new DataMap', 
'add a named query', 'create an embeddable', 'add a stored procedure', 'change 
the attribute type', 'mark this [...]
+---
+
+# cayenne-modeling
+
+Edit Cayenne DataMap (`*.map.xml`) and project descriptor (`cayenne-*.xml`) 
files directly. This is the **default path** for granular model changes — 
adding one entity, one relationship, one query. Use the Modeler GUI 
(`cayenne-modeler` skill) only for inherently visual work like reverse 
engineering.
+
+## Required reading
+
+Read these before making any edit — they describe the XML formats this skill 
operates on:
+
+- `${CLAUDE_PLUGIN_ROOT}/references/project-layout.md` — locate the project 
descriptor and DataMaps in the user's repo.
+- `${CLAUDE_PLUGIN_ROOT}/references/datamap-schema.md` — every element shape 
for `*.map.xml`.
+- `${CLAUDE_PLUGIN_ROOT}/references/project-descriptor-schema.md` — 
`cayenne-*.xml` shape.
+
+## Step 1 — Locate the right file
+
+Follow `project-layout.md`. If the user has multiple Cayenne projects and the 
request is ambiguous, ask which one. Cache the answer for the rest of the 
session.
+
+Decide whether the change belongs in a DataMap or the project descriptor 
(table at the end of `project-layout.md`):
+
+- Entity/relationship/query/embeddable/procedure → DataMap (`*.map.xml`).
+- DataMap list, DataNode (DB connection) → project descriptor 
(`cayenne-*.xml`).
+
+## Step 2 — Make the edit
+
+Apply the change following the schema in `datamap-schema.md` or 
`project-descriptor-schema.md`.
+
+### Critical rules
+
+1. **Element order matters.** The DataMap schema requires this order inside 
`<data-map>`: `<property>`, `<procedure>`, `<embeddable>`, `<db-entity>`, 
`<obj-entity>`, `<db-relationship>`, `<obj-relationship>`, `<query>`, `<cgen>`, 
`<dbImport>`. Insert at the right place — don't append blindly.
+
+2. **Cross-link consistently.**
+   - An `<obj-entity>` references a `<db-entity>` by `dbEntityName="..."`. 
Make sure that DbEntity exists.
+   - An `<obj-attribute>` references a DbAttribute by 
`db-attribute-path="COLUMN_NAME"`. Make sure that column exists on the DbEntity 
(or on a related one if using a dotted path through a relationship).
+   - An `<obj-relationship>` requires a backing `<db-relationship>` (or chain 
of them) via `db-relationship-path`. Never add an ObjRelationship without the 
DB-layer counterpart.
+   - Most FKs need **two** `<db-relationship>` entries, one per direction. 
They are not auto-derived.
+
+3. **Use the right `type` on attributes.**
+   - `db-attribute type` — JDBC type names: `VARCHAR`, `INTEGER`, `BIGINT`, 
`DATE`, `TIMESTAMP`, `BOOLEAN`, `NUMERIC`, `BLOB`, `CLOB`, `VARBINARY`, etc. 
VARCHAR/CHAR/VARBINARY also need `length`. NUMERIC/DECIMAL also need `scale`.
+   - `obj-attribute type` — Java FQN: `java.lang.String`, `java.lang.Integer`, 
`java.util.Date`, `java.math.BigDecimal`, `byte[]`, etc. Use wrapper types 
(`Integer`, not `int`) for nullable columns.
+
+4. **PK handling.** PK columns get `isPrimaryKey="true" isMandatory="true"` on 
the DbAttribute. They are normally **not** mirrored as ObjAttributes — Cayenne 
handles them implicitly. Map a PK as an ObjAttribute only if the user wants a 
"meaningful PK" (visible on the Java side).
+
+5. **Preserve formatting.** Match the indentation and quote style of the file 
(the standard Cayenne style uses tabs and double quotes, but follow what's 
actually in the file you're editing).
+
+## Step 3 — Validate the edit conceptually
+
+After writing the XML, mentally walk through:
+
+- Does every `obj-entity.dbEntityName` resolve to an existing `<db-entity>`?
+- Does every `obj-attribute.db-attribute-path` resolve to a column on the 
right DbEntity?
+- Does every `obj-relationship.db-relationship-path` resolve to a chain of 
existing `<db-relationship>` entries?
+- Are PK and FK columns marked `isMandatory="true"` where the DB enforces NOT 
NULL?
+
+If anything fails, fix it before reporting done.
+
+## Step 4 — Offer the right next step
+
+- **If you modified entities and the DataMap has a `<cgen>` block:** suggest 
invoking the `cayenne-cgen` skill to regenerate Java classes. Mention which 
entities are affected.
+- **If the user added a new entity and there's no Java class yet:** same — 
recommend `cayenne-cgen`.
+- **If the user is asking about a full DB sync** (importing many tables, 
syncing with a changed schema): hand off to `cayenne-reverse-engineer`. Do not 
try to script this via XML edits.
+- **If the change is structurally messy** (bulk renaming relationships, visual 
graph rework): suggest the `cayenne-modeler` skill. Otherwise do not.
+
+## Anti-patterns
+
+- **Don't hand-edit `_<Entity>.java` superclass files.** They are regenerated 
by cgen and your changes will be overwritten. Edit the user `<Entity>.java` 
subclass instead.
+- **Don't add an `obj-relationship` without a `db-relationship`.** Cayenne 
will validate-fail at runtime.
+- **Don't use primitive Java types (`int`, `long`, `boolean`) as 
`obj-attribute type` for nullable columns.** Primitives can't represent NULL. 
Use wrappers.
+- **Don't reorder existing elements.** The schema requires the order 
documented above; reordering existing elements may also create noisy diffs.
+- **Don't run cgen yourself.** That's `cayenne-cgen`'s job — invoke that skill 
instead of calling the MCP tool directly here.
+- **Don't suggest `mvn cayenne:cdbimport` or any Maven/Gradle plugin goal.** 
Those are explicitly out of scope for this plugin.
diff --git a/ai-plugin/skills/cayenne-query/SKILL.md 
b/ai-plugin/skills/cayenne-query/SKILL.md
new file mode 100644
index 000000000..ef3c98ed7
--- /dev/null
+++ b/ai-plugin/skills/cayenne-query/SKILL.md
@@ -0,0 +1,105 @@
+---
+name: cayenne-query
+description: "Use this skill whenever the user wants to write or modify a 
Cayenne query — fetching entities by criteria, joining, prefetching to avoid 
N+1, ordering, paginating, aggregating, or running raw SQL through Cayenne. 
Trigger on phrases like 'query for X', 'fetch all artists where ...', 'write an 
ObjectSelect', 'use SQLSelect', 'use SelectById', 'add a prefetch', 'get 
distinct values', 'count rows', 'find by ID', 'load by primary key', 'build a 
Cayenne expression', 'why am I get [...]
+---
+
+# cayenne-query
+
+Write idiomatic Cayenne 5.0 queries — `ObjectSelect`, `SelectById`, 
`SQLSelect`, expressions, prefetch, pagination, aggregates.
+
+## Required reading
+
+- `${CLAUDE_PLUGIN_ROOT}/references/query-api.md` — every query mechanism with 
examples (ObjectSelect, SQLSelect/SQLExec, Expression, ColumnSelect, 
SelectById).
+
+## Step 1 — Identify the query shape
+
+| Intent | Use |
+|---|---|
+| Fetch one or more entities matching criteria | 
`ObjectSelect.query(Cls.class).where(...).select(ctx)` |
+| Fetch by primary key | `SelectById.query(Cls.class, pk).selectOne(ctx)` |
+| Aggregate (count, sum) | `ObjectSelect.query(Cls.class).selectCount(ctx)` or 
`ColumnSelect` with aggregate functions |
+| One or a few columns only (DTO-style) | `ObjectSelect.columnQuery(Cls.class, 
Cls.NAME, Cls.AGE).select(ctx)` |
+| Raw SQL with parameter binding | `SQLSelect.query(Cls.class, "SELECT 
...").params(...).select(ctx)` |
+| Insert/update/delete bulk | `SQLExec.query("UPDATE ...").update(ctx)` |
+| Reused named query stored in DataMap | XML `<query>` (see 
`${CLAUDE_PLUGIN_ROOT}/references/datamap-schema.md`) loaded via `NamedQuery` |
+
+### Primary-key lookups: prefer `SelectById`
+
+When the user is fetching a single entity by its PK, use `SelectById` rather 
than `ObjectSelect.where(PK.eq(...))`:
+
+```java
+Artist a = SelectById.query(Artist.class, 42).selectOne(ctx);
+```
+
+`SelectById` can hit the `ObjectContext`'s session cache without firing a SQL 
query when the row is already loaded. `ObjectSelect.where(...)` always goes to 
the database. For composite PKs, pass a `Map<String, Object>` of PK column → 
value.
+
+## Step 2 — Filter with `Property` constants
+
+cgen generates `Property<T>` constants on each entity superclass. Use them — 
they're type-checked at compile time:
+
+```java
+ObjectSelect.query(Artist.class)
+    .where(Artist.ARTIST_NAME.likeIgnoreCase("p%"))
+    .and(Artist.DATE_OF_BIRTH.between(d1, d2))
+    .select(ctx);
+```
+
+Only fall back to `ExpressionFactory.matchExp("artistName", ...)` or 
`Expression.fromString(...)` when:
+
+- The filter is dynamic (field name comes from runtime input), or
+- The user explicitly wants string-based expressions.
+
+`query-api.md` lists all common predicate methods on `Property`.
+
+## Step 3 — Handle relationships (prefetch)
+
+When the query traverses a relationship in a loop, **always** add a prefetch 
to avoid N+1:
+
+```java
+// Bad — fires one query per artist when paintings are accessed
+for (Artist a : artists) { a.getPaintings().size(); }
+
+// Good
+List<Artist> artists = ObjectSelect.query(Artist.class)
+    .prefetch(Artist.PAINTINGS.disjoint())
+    .select(ctx);
+```
+
+Pick:
+
+- `.joint()` — to-one relationships, or to-many with small fan-out. Single SQL 
join.
+- `.disjoint()` — to-many, modest size. Two queries.
+- `.disjointById()` — to-many, very large parent set. Two queries, keyed by 
PKs.
+
+## Step 4 — Apply ordering, paging, and caching as needed
+
+```java
+ObjectSelect.query(Artist.class)
+    .orderBy(Artist.ARTIST_NAME.asc())
+    .pageSize(50)             // server-side pagination
+    .limit(1000)
+    .localCache()             // per-context cache, or 
.sharedCache("group-name")
+    .select(ctx);
+```
+
+Use `sharedCache` for reference data (rarely changes, read often). Use 
`pageSize` to avoid loading entire result sets.
+
+## Step 5 — Raw SQL when needed
+
+```java
+List<Artist> hits = SQLSelect.query(Artist.class,
+        "SELECT * FROM ARTIST WHERE ARTIST_NAME LIKE #bind($pattern)")
+    .params(Map.of("pattern", userInput + "%"))
+    .select(ctx);
+```
+
+**Always** use `#bind($name)` placeholders. Never concatenate user input into 
SQL. Cayenne's SQLTemplate is Velocity-based — see `query-api.md` for `#bind`, 
`#bindEqual`, `#chain`, and adapter-specific SQL with `<sql 
adapter-class="...">`.
+
+## Anti-patterns
+
+- **Parameter String concatenation in raw SQL.** Use `#bind($name)`. 
Concatenation is a SQL injection vector and may also result in invalid syntax.
+- **Using `selectOne` when multiple may match.** It throws. Use `selectFirst` 
if "any one" is okay.
+- **Loading large result sets without pagination.** Use `.pageSize(n)` or 
`iterator()` and process incrementally.
+- **N+1 from missing prefetch.** If iterating entities and accessing 
relationships per-entity, add `.prefetch(...)`.
+- **Using `Expression.fromString(...)` or 
`ExpressionFactory.matchExp("fieldName", ...)` for static queries.** Prefer 
typed `Property` constants (`Artist.ARTIST_NAME.eq(...)`) — they catch typos at 
compile time and survive model refactors.
+- **Mutating fetched objects without committing.** 
`ObjectContext.commitChanges()` is required for changes to persist.
diff --git a/ai-plugin/skills/cayenne-reverse-engineer/SKILL.md 
b/ai-plugin/skills/cayenne-reverse-engineer/SKILL.md
new file mode 100644
index 000000000..4f22c628a
--- /dev/null
+++ b/ai-plugin/skills/cayenne-reverse-engineer/SKILL.md
@@ -0,0 +1,80 @@
+---
+name: cayenne-reverse-engineer
+description: "Use this skill whenever the user wants to import database schema 
metadata into a Cayenne DataMap — full-schema sync from a live DB. Trigger on 
phrases like 'reverse engineer the database', 'import the schema', 'generate a 
DataMap from my DB', 'sync the model with the database', 'add the new tables 
from the DB', 'import the customer table', 'pick up the latest schema changes', 
'create entities from these tables', or any request that involves reading 
database metadata to popu [...]
+---
+
+# cayenne-reverse-engineer
+
+Import a database schema into a Cayenne DataMap by driving the 
CayenneModeler's reverse-engineering wizard through MCP. Cayenne 5.0 does not 
yet expose reverse engineering as a direct MCP tool, so the workflow launches 
the GUI and walks the user through the wizard.
+
+## Required reading
+
+- `${CLAUDE_PLUGIN_ROOT}/references/project-layout.md` — locate or create the 
project descriptor.
+- `${CLAUDE_PLUGIN_ROOT}/references/dbimport-config.md` — the field semantics 
behind every wizard screen, so you can explain options in user terms.
+- `${CLAUDE_PLUGIN_ROOT}/references/mcp-tools.md` — `open_project` tool 
reference and behavior when MCP is not connected.
+
+## Step 1 — Confirm scope
+
+Ask the user (one question, only if not obvious from the request):
+
+- Are they reverse-engineering into a **new DataMap**, or **adding/updating** 
entities in an **existing DataMap**?
+
+## Step 2 — Locate or create the project
+
+Follow `project-layout.md` to find the existing `cayenne-*.xml`. If none 
exists, the user is starting from scratch:
+
+- Either generate a minimal descriptor first (use `cayenne-modeling`'s 
patterns — namespace `http://cayenne.apache.org/schema/12/domain`, 
`project-version="12"`, one empty `<map name="..."/>` plus a sibling empty 
`*.map.xml`), or
+- Tell the user to use **File → New Project** inside the Modeler once it's 
open.
+
+The descriptor path needs to be **absolute** when passed to `open_project`.
+
+## Step 3 — Launch the Modeler
+
+Call the MCP tool:
+
+```
+mcp__cayenne__open_project({ "projectPath": "<absolute path to cayenne-*.xml>" 
})
+```
+
+If the tool is not available (server not registered), surface 
`cayenne-mcp-server/README.md` and stop. **Do not** suggest `mvn 
cayenne:cdbimport` or any Gradle equivalent — those build plugins are out of 
scope.
+
+If `open_project` returns a non-`ok` status, surface the error code and 
message; common ones are `modeler_not_found`, `project_not_found`, 
`handshake_timeout`. Don't retry blindly — diagnose first.
+
+## Step 4 — Walk the user through the wizard
+
+The user now has the Modeler open. Give them the exact GUI sequence:
+
+1. **DataMap → DB Import tab**
+2. **Data Source** dialog:
+   - **JDBC Driver** — e.g., `org.postgresql.Driver`
+   - **DB URL** — full JDBC URL
+   - **User Name / Password** — credentials
+   - **Cayenne Adapter** (optional, autodetected for known DBs) — e.g., 
`org.apache.cayenne.dba.postgres.PostgresAdapter`
+   - Click **Test Connection**, then **Continue**.
+3. **Configure** screen — set table/column/procedure filters:
+   - Include/exclude regex patterns under the relevant catalog/schema.
+   - **Table Types** — usually leave at `TABLE`; add `VIEW` if the user wants 
views imported.
+4. **Naming** screen:
+   - **Naming Strategy** — usually the default (`DefaultObjectNameGenerator`).
+   - **Strip from Table Names** — regex to strip a prefix like `^TBL_`.
+   - **Default Package** — Java package for generated ObjEntity classes.
+   - **Meaningful PK Tables** — regex (or `*`) for tables whose PK columns 
should be exposed as ObjAttributes. Leave empty unless the user needs PK 
visibility.
+5. **Other Options** — toggles for `skipPrimaryKeyLoading`, 
`skipRelationshipsLoading`, `forceDataMapCatalog`, `forceDataMapSchema`, 
`useJava7Types`. See `dbimport-config.md` for what each does; defaults are 
usually right.
+6. Click **Save** then **Run Import**. The Modeler runs the import and reports 
a diff (added/changed/removed entities).
+
+Explain options as the user asks — `dbimport-config.md` has the semantics. 
When unsure, recommend the default.
+
+## Step 5 — Confirm and follow up
+
+Once the user reports the import is done and they've saved the project:
+
+- Tell them to **save the project** in the Modeler (File → Save). The Modeler 
persists wizard settings as a `<dbImport>` block inside the DataMap for repeat 
runs.
+- Hand off to `cayenne-cgen` to regenerate Java classes for the new/changed 
entities. Quote the DataMap name so the cgen skill can pass it to `cgen_run`.
+- If the DB has columns that don't follow the user's preferred naming, 
recommend tweaking the naming strategy and re-running.
+
+## Anti-patterns
+
+- **Do not** suggest `mvn cayenne:cdbimport`, the Gradle `cdbimport` task, or 
hand-running the `cayenne-dbsync` Java APIs. The Modeler GUI is the only 
supported execution path here.
+- **Do not** try to hand-write a DataMap from a DB schema description as a 
substitute for the wizard — the wizard handles JDBC types, PK detection, FK 
relationships, and naming consistently. Hand-rolling produces subtle mistakes.
+- **Do not** enable `forceDataMapCatalog` / `forceDataMapSchema` defensively. 
They suppress legitimate DB metadata and cause hard-to-debug issues in 
multi-catalog setups.
+- **Do not** offer reverse engineering without MCP — if the server isn't 
connected, point at `cayenne-mcp-server/README.md` and stop.
diff --git a/ai-plugin/skills/cayenne-runtime/SKILL.md 
b/ai-plugin/skills/cayenne-runtime/SKILL.md
new file mode 100644
index 000000000..e46ccfadc
--- /dev/null
+++ b/ai-plugin/skills/cayenne-runtime/SKILL.md
@@ -0,0 +1,86 @@
+---
+name: cayenne-runtime
+description: "Use this skill whenever the user wants to bootstrap Cayenne in a 
Java application — constructing a CayenneRuntime, wiring a DataSource, 
configuring connection pools, creating ObjectContexts, or integrating Cayenne 
with a DI container (Bootique, Spring, plain Java). Trigger on phrases like 
'set up Cayenne in my app', 'configure CayenneRuntime', 'wire Cayenne into 
Bootique/Spring', 'create an ObjectContext', 'configure a DataSource for 
Cayenne', 'connect Cayenne to Postgres/M [...]
+---
+
+# cayenne-runtime
+
+Bootstrap `CayenneRuntime` and use `ObjectContext` for CRUD in a Java 
application.
+
+## Required reading
+
+- `${CLAUDE_PLUGIN_ROOT}/references/runtime-api.md` — `CayenneRuntimeBuilder` 
methods, `ObjectContext` API, transaction patterns, lifecycle rules.
+
+## Step 1 — Find or create the bootstrap site
+
+Where does the user's app construct `CayenneRuntime`? Common patterns:
+
+- **Plain Java / CLI** — a `main()` method or a singleton holder.
+- **Bootique app** — typically a `@Provides` method in a module 
(`provideCayenneRuntime(@Inject DataSource ds)`). If editing a Bootique app, 
mention that the `java-dev:bootique-config` skill (if available) can help with 
the surrounding config wiring.
+- **Spring app** — a `@Bean CayenneRuntime cayenneRuntime(DataSource ds)` 
method in a configuration class.
+- **Cayenne-only embedded** — a static field or DI singleton initialized 
lazily.
+
+If none exists yet, create one. Apply `runtime-api.md` patterns:
+
+```java
+CayenneRuntime runtime = CayenneRuntime.builder()
+        .addConfig("cayenne-mydb.xml")
+        .dataSource(externalDataSource)   // or 
.url(...).jdbcDriver(...).user(...).password(...)
+        .build();
+```
+
+## Step 2 — Decide on the DataSource path
+
+Two paths from `CayenneRuntimeBuilder`:
+
+| When | Use |
+|---|---|
+| Production app with Spring/Bootique/Hikari already managing the pool | 
`.dataSource(ds)` — Cayenne reuses the external pool. |
+| Standalone tool or test fixture with no external DI | 
`.url(...).jdbcDriver(...).user(...).password(...)` — Cayenne's built-in pool. |
+
+The `.dataSource()` path is preferred when possible. Never put JDBC 
credentials in `cayenne-*.xml` for production code — keep them in the 
application's normal config mechanism.
+
+## Step 3 — Use `ObjectContext`
+
+```java
+ObjectContext ctx = runtime.newContext();   // one per request/thread
+Artist a = ctx.newObject(Artist.class);
+a.setArtistName("Picasso");
+ctx.commitChanges();
+```
+
+Rules:
+
+- `CayenneRuntime` is a heavy singleton — **create once, share for app 
lifetime**, call `shutdown()` on exit.
+- `ObjectContext` is **not thread-safe** — new one per request/thread.
+- Don't mix objects across contexts. Use `ctx.localObject(other)` to bring an 
object into the current context.
+
+## Step 4 — Transactions
+
+`commitChanges()` is auto-transactional. For multi-step transactions:
+
+```java
+runtime.performInTransaction(() -> {
+    // multiple commits in one transaction
+    return null;
+});
+```
+
+Any throw rolls back the whole block.
+
+## Step 5 — Verify the integration
+
+Tell the user how to smoke-test:
+
+1. Build and run the app.
+2. From a request handler: `ObjectContext ctx = runtime.newContext(); long 
count = ObjectSelect.query(SomeEntity.class).selectCount(ctx);` — should return 
a number without throwing.
+
+If they hit `CayenneRuntimeException: No DataNode...`, the descriptor's 
`<map>` reference doesn't have a node — confirm the DataSource wiring went 
through `CayenneRuntimeBuilder.dataSource(...)` (Cayenne synthesizes a node 
from this).
+
+## Anti-patterns
+
+- **Creating `CayenneRuntime` per request.** It's a heavyweight singleton. 
Cache it for the application lifetime.
+- **Sharing `ObjectContext` across threads.** Not safe. Create one per thread.
+- **Putting DB credentials in `cayenne-*.xml`** for production. Use the 
application's secret management; pass a `DataSource` to the builder.
+- **Forgetting `commitChanges()`.** Mutations stay in memory until commit.
+- **Forgetting `shutdown()`** on app exit. Hangs the JVM on some pool 
implementations.

Reply via email to