@constructive-io/graphql-codegen 4.14.4 → 4.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,7 +35,42 @@ export declare function getReadmeFooter(): string[];
35
35
  export declare function resolveDocsConfig(docs: DocsConfig | boolean | undefined): DocsConfig;
36
36
  export declare function formatArgType(arg: CleanOperation['args'][number]): string;
37
37
  export declare function formatTypeRef(t: CleanOperation['args'][number]['type']): string;
38
- export declare function getEditableFields(table: CleanTable): CleanField[];
38
+ export declare function getEditableFields(table: CleanTable, typeRegistry?: TypeRegistry): CleanField[];
39
+ /**
40
+ * Identify search/computed fields on a table — fields present in the GraphQL
41
+ * type but NOT in the create input type. These are plugin-added fields like
42
+ * trgm similarity scores, tsvector ranks, searchScore, etc.
43
+ */
44
+ export declare function getSearchFields(table: CleanTable, typeRegistry?: TypeRegistry): CleanField[];
45
+ export interface SpecialFieldGroup {
46
+ /** Category key */
47
+ category: 'geospatial' | 'embedding' | 'search';
48
+ /** Human-readable label */
49
+ label: string;
50
+ /** One-line description of this category */
51
+ description: string;
52
+ /** Fields belonging to this category */
53
+ fields: CleanField[];
54
+ }
55
+ /**
56
+ * Categorize "special" fields on a table into PostGIS, pgvector, and
57
+ * Unified Search groups. Returns only non-empty groups.
58
+ *
59
+ * The function inspects ALL scalar fields (not just computed ones) so that
60
+ * real columns (geometry, vector, tsvector) are also surfaced with
61
+ * descriptive context in generated docs.
62
+ */
63
+ export declare function categorizeSpecialFields(table: CleanTable, typeRegistry?: TypeRegistry): SpecialFieldGroup[];
64
+ /**
65
+ * Build markdown lines describing special fields for README-style docs.
66
+ * Returns empty array when there are no special fields.
67
+ */
68
+ export declare function buildSpecialFieldsMarkdown(groups: SpecialFieldGroup[]): string[];
69
+ /**
70
+ * Build plain-text lines describing special fields for AGENTS-style docs.
71
+ * Returns empty array when there are no special fields.
72
+ */
73
+ export declare function buildSpecialFieldsPlain(groups: SpecialFieldGroup[]): string[];
39
74
  /**
40
75
  * Represents a flattened argument for docs/skills generation.
41
76
  * INPUT_OBJECT args are expanded to dot-notation fields.
@@ -6,6 +6,10 @@ exports.resolveDocsConfig = resolveDocsConfig;
6
6
  exports.formatArgType = formatArgType;
7
7
  exports.formatTypeRef = formatTypeRef;
8
8
  exports.getEditableFields = getEditableFields;
9
+ exports.getSearchFields = getSearchFields;
10
+ exports.categorizeSpecialFields = categorizeSpecialFields;
11
+ exports.buildSpecialFieldsMarkdown = buildSpecialFieldsMarkdown;
12
+ exports.buildSpecialFieldsPlain = buildSpecialFieldsPlain;
9
13
  exports.cleanTypeName = cleanTypeName;
10
14
  exports.flattenArgs = flattenArgs;
11
15
  exports.flattenedArgsToFlags = flattenedArgsToFlags;
@@ -74,12 +78,154 @@ function formatTypeRef(t) {
74
78
  }
75
79
  return t.name ?? 'unknown';
76
80
  }
77
- function getEditableFields(table) {
81
+ function getEditableFields(table, typeRegistry) {
78
82
  const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
83
+ const writableFields = (0, utils_1.getWritableFieldNames)(table, typeRegistry);
79
84
  return (0, utils_1.getScalarFields)(table).filter((f) => f.name !== pk.name &&
80
85
  f.name !== 'nodeId' &&
81
86
  f.name !== 'createdAt' &&
82
- f.name !== 'updatedAt');
87
+ f.name !== 'updatedAt' &&
88
+ // When a TypeRegistry is available, filter out computed/plugin-added
89
+ // fields (e.g. search scores, trgm similarity) that aren't real columns
90
+ (writableFields === null || writableFields.has(f.name)));
91
+ }
92
+ /**
93
+ * Identify search/computed fields on a table — fields present in the GraphQL
94
+ * type but NOT in the create input type. These are plugin-added fields like
95
+ * trgm similarity scores, tsvector ranks, searchScore, etc.
96
+ */
97
+ function getSearchFields(table, typeRegistry) {
98
+ const writableFields = (0, utils_1.getWritableFieldNames)(table, typeRegistry);
99
+ if (writableFields === null)
100
+ return [];
101
+ const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
102
+ return (0, utils_1.getScalarFields)(table).filter((f) => f.name !== pk.name &&
103
+ f.name !== 'nodeId' &&
104
+ f.name !== 'createdAt' &&
105
+ f.name !== 'updatedAt' &&
106
+ !writableFields.has(f.name));
107
+ }
108
+ function isPostGISField(f) {
109
+ const pgType = f.type.pgType?.toLowerCase();
110
+ if (pgType === 'geometry' || pgType === 'geography')
111
+ return true;
112
+ const gql = f.type.gqlType;
113
+ if (/^(GeoJSON|GeographyPoint|GeographyLineString|GeographyPolygon|GeometryPoint|GeometryLineString|GeometryPolygon|GeographyMulti|GeometryMulti|GeometryCollection|GeographyCollection)/i.test(gql))
114
+ return true;
115
+ return false;
116
+ }
117
+ function isEmbeddingField(f) {
118
+ const pgType = f.type.pgType?.toLowerCase();
119
+ if (pgType === 'vector')
120
+ return true;
121
+ if (/embedding$/i.test(f.name) && f.type.isArray && f.type.gqlType === 'Float')
122
+ return true;
123
+ return false;
124
+ }
125
+ function isTsvectorField(f) {
126
+ const pgType = f.type.pgType?.toLowerCase();
127
+ return pgType === 'tsvector';
128
+ }
129
+ function isSearchComputedField(f) {
130
+ if (f.name === 'searchScore')
131
+ return true;
132
+ if (/TrgmSimilarity$/.test(f.name))
133
+ return true;
134
+ if (/TsvectorRank$/.test(f.name))
135
+ return true;
136
+ if (/Bm25Score$/.test(f.name))
137
+ return true;
138
+ return false;
139
+ }
140
+ /**
141
+ * Categorize "special" fields on a table into PostGIS, pgvector, and
142
+ * Unified Search groups. Returns only non-empty groups.
143
+ *
144
+ * The function inspects ALL scalar fields (not just computed ones) so that
145
+ * real columns (geometry, vector, tsvector) are also surfaced with
146
+ * descriptive context in generated docs.
147
+ */
148
+ function categorizeSpecialFields(table, typeRegistry) {
149
+ const allFields = (0, utils_1.getScalarFields)(table);
150
+ const computedFields = getSearchFields(table, typeRegistry);
151
+ const computedSet = new Set(computedFields.map((f) => f.name));
152
+ const geospatial = [];
153
+ const embedding = [];
154
+ const search = [];
155
+ for (const f of allFields) {
156
+ if (isPostGISField(f)) {
157
+ geospatial.push(f);
158
+ }
159
+ else if (isEmbeddingField(f)) {
160
+ embedding.push(f);
161
+ }
162
+ else if (isTsvectorField(f)) {
163
+ search.push(f);
164
+ }
165
+ else if (computedSet.has(f.name) && isSearchComputedField(f)) {
166
+ search.push(f);
167
+ }
168
+ }
169
+ const groups = [];
170
+ if (geospatial.length > 0) {
171
+ groups.push({
172
+ category: 'geospatial',
173
+ label: 'PostGIS geospatial fields',
174
+ description: 'Geographic/geometric columns managed by PostGIS. Supports spatial queries (distance, containment, intersection) via the Unified Search API PostGIS adapter.',
175
+ fields: geospatial,
176
+ });
177
+ }
178
+ if (embedding.length > 0) {
179
+ groups.push({
180
+ category: 'embedding',
181
+ label: 'pgvector embedding fields',
182
+ description: 'High-dimensional vector columns for semantic similarity search. Query via the Unified Search API pgvector adapter using cosine, L2, or inner-product distance.',
183
+ fields: embedding,
184
+ });
185
+ }
186
+ if (search.length > 0) {
187
+ groups.push({
188
+ category: 'search',
189
+ label: 'Unified Search API fields',
190
+ description: 'Fields provided by the Unified Search plugin. Includes full-text search (tsvector/BM25), trigram similarity scores, and the combined searchScore. Computed fields are read-only and cannot be set in create/update operations.',
191
+ fields: search,
192
+ });
193
+ }
194
+ return groups;
195
+ }
196
+ /**
197
+ * Build markdown lines describing special fields for README-style docs.
198
+ * Returns empty array when there are no special fields.
199
+ */
200
+ function buildSpecialFieldsMarkdown(groups) {
201
+ if (groups.length === 0)
202
+ return [];
203
+ const lines = [];
204
+ for (const g of groups) {
205
+ const fieldList = g.fields.map((f) => `\`${f.name}\``).join(', ');
206
+ lines.push(`> **${g.label}:** ${fieldList}`);
207
+ lines.push(`> ${g.description}`);
208
+ lines.push('');
209
+ }
210
+ return lines;
211
+ }
212
+ /**
213
+ * Build plain-text lines describing special fields for AGENTS-style docs.
214
+ * Returns empty array when there are no special fields.
215
+ */
216
+ function buildSpecialFieldsPlain(groups) {
217
+ if (groups.length === 0)
218
+ return [];
219
+ const lines = [];
220
+ lines.push('SPECIAL FIELDS:');
221
+ for (const g of groups) {
222
+ lines.push(` [${g.label}]`);
223
+ lines.push(` ${g.description}`);
224
+ for (const f of g.fields) {
225
+ lines.push(` ${f.name}: ${cleanTypeName(f.type.gqlType)}`);
226
+ }
227
+ }
228
+ return lines;
83
229
  }
84
230
  function unwrapNonNull(typeRef) {
85
231
  if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
@@ -123,17 +123,19 @@ function generateHooksReadme(tables, customOperations) {
123
123
  }
124
124
  function generateHooksAgentsDocs(tables, customOperations) {
125
125
  const lines = [];
126
- lines.push('# React Query Hooks - Agent Reference');
126
+ const tableCount = tables.length;
127
+ const customOpCount = customOperations.length;
128
+ lines.push('# React Query Hooks');
127
129
  lines.push('');
128
130
  lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
129
- lines.push('> This document is structured for LLM/agent consumption.');
130
131
  lines.push('');
131
- lines.push('## OVERVIEW');
132
+ lines.push('## Stack');
132
133
  lines.push('');
133
- lines.push('React Query hooks wrapping ORM operations for data fetching and mutations.');
134
- lines.push('All query hooks return `UseQueryResult`. All mutation hooks return `UseMutationResult`.');
134
+ lines.push('- React Query hooks wrapping ORM operations (TypeScript)');
135
+ lines.push(`- ${tableCount} table${tableCount !== 1 ? 's' : ''}${customOpCount > 0 ? `, ${customOpCount} custom operation${customOpCount !== 1 ? 's' : ''}` : ''}`);
136
+ lines.push('- Query hooks return `UseQueryResult`, mutation hooks return `UseMutationResult`');
135
137
  lines.push('');
136
- lines.push('## SETUP');
138
+ lines.push('## Quick Start');
137
139
  lines.push('');
138
140
  lines.push('```typescript');
139
141
  lines.push("import { configure } from './hooks';");
@@ -144,123 +146,22 @@ function generateHooksAgentsDocs(tables, customOperations) {
144
146
  lines.push('// Wrap app in <QueryClientProvider client={queryClient}>');
145
147
  lines.push('```');
146
148
  lines.push('');
147
- lines.push('## HOOKS');
149
+ lines.push('## Resources');
150
+ lines.push('');
151
+ lines.push(`- **Full API reference:** [README.md](./README.md) — hook docs for all ${tableCount} tables`);
152
+ lines.push('- **Schema types:** [types.ts](./types.ts)');
153
+ lines.push('- **Hooks module:** [hooks.ts](./hooks.ts)');
154
+ lines.push('');
155
+ lines.push('## Conventions');
156
+ lines.push('');
157
+ lines.push('- Query hooks: `use<PluralName>Query`, `use<SingularName>Query`');
158
+ lines.push('- Mutation hooks: `useCreate<Name>Mutation`, `useUpdate<Name>Mutation`, `useDelete<Name>Mutation`');
159
+ lines.push('- All hooks accept a `selection` parameter to pick fields');
160
+ lines.push('');
161
+ lines.push('## Boundaries');
162
+ lines.push('');
163
+ lines.push('All files in this directory are generated. Do not edit manually.');
148
164
  lines.push('');
149
- for (const table of tables) {
150
- const { singularName, pluralName } = (0, utils_1.getTableNames)(table);
151
- const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
152
- const scalarFields = (0, utils_1.getScalarFields)(table);
153
- lines.push(`### HOOK: ${(0, utils_1.getListQueryHookName)(table)}`);
154
- lines.push('');
155
- lines.push(`${table.description || `List all ${pluralName}`}.`);
156
- lines.push('');
157
- lines.push('```');
158
- lines.push(`TYPE: query`);
159
- lines.push(`USAGE: ${(0, utils_1.getListQueryHookName)(table)}({ selection: { fields: { ... } } })`);
160
- lines.push('');
161
- lines.push('INPUT:');
162
- lines.push(' selection: { fields: Record<string, boolean> } - Fields to select');
163
- lines.push('');
164
- lines.push('OUTPUT: UseQueryResult<Array<{');
165
- for (const f of scalarFields) {
166
- lines.push(` ${f.name}: ${(0, utils_1.fieldTypeToTs)(f.type)}`);
167
- }
168
- lines.push('}>>');
169
- lines.push('```');
170
- lines.push('');
171
- if ((0, utils_1.hasValidPrimaryKey)(table)) {
172
- lines.push(`### HOOK: ${(0, utils_1.getSingleQueryHookName)(table)}`);
173
- lines.push('');
174
- lines.push(`${table.description || `Get a single ${singularName} by ${pk.name}`}.`);
175
- lines.push('');
176
- lines.push('```');
177
- lines.push(`TYPE: query`);
178
- lines.push(`USAGE: ${(0, utils_1.getSingleQueryHookName)(table)}({ ${pk.name}: '<value>', selection: { fields: { ... } } })`);
179
- lines.push('');
180
- lines.push('INPUT:');
181
- lines.push(` ${pk.name}: ${pk.tsType} (required)`);
182
- lines.push(' selection: { fields: Record<string, boolean> } - Fields to select');
183
- lines.push('');
184
- lines.push('OUTPUT: UseQueryResult<{');
185
- for (const f of scalarFields) {
186
- lines.push(` ${f.name}: ${(0, utils_1.fieldTypeToTs)(f.type)}`);
187
- }
188
- lines.push('}>');
189
- lines.push('```');
190
- lines.push('');
191
- }
192
- lines.push(`### HOOK: ${(0, utils_1.getCreateMutationHookName)(table)}`);
193
- lines.push('');
194
- lines.push(`${table.description || `Create a new ${singularName}`}.`);
195
- lines.push('');
196
- lines.push('```');
197
- lines.push('TYPE: mutation');
198
- lines.push(`USAGE: const { mutate } = ${(0, utils_1.getCreateMutationHookName)(table)}({ selection: { fields: { ... } } })`);
199
- lines.push('');
200
- lines.push('OUTPUT: UseMutationResult');
201
- lines.push('```');
202
- lines.push('');
203
- if ((0, utils_1.hasValidPrimaryKey)(table)) {
204
- lines.push(`### HOOK: ${(0, utils_1.getUpdateMutationHookName)(table)}`);
205
- lines.push('');
206
- lines.push(`${table.description || `Update an existing ${singularName}`}.`);
207
- lines.push('');
208
- lines.push('```');
209
- lines.push('TYPE: mutation');
210
- lines.push(`USAGE: const { mutate } = ${(0, utils_1.getUpdateMutationHookName)(table)}({ selection: { fields: { ... } } })`);
211
- lines.push('');
212
- lines.push('OUTPUT: UseMutationResult');
213
- lines.push('```');
214
- lines.push('');
215
- lines.push(`### HOOK: ${(0, utils_1.getDeleteMutationHookName)(table)}`);
216
- lines.push('');
217
- lines.push(`${table.description || `Delete a ${singularName}`}.`);
218
- lines.push('');
219
- lines.push('```');
220
- lines.push('TYPE: mutation');
221
- lines.push(`USAGE: const { mutate } = ${(0, utils_1.getDeleteMutationHookName)(table)}({})`);
222
- lines.push('');
223
- lines.push('OUTPUT: UseMutationResult');
224
- lines.push('```');
225
- lines.push('');
226
- }
227
- }
228
- if (customOperations.length > 0) {
229
- lines.push('## CUSTOM OPERATION HOOKS');
230
- lines.push('');
231
- for (const op of customOperations) {
232
- const hookName = getCustomHookName(op);
233
- lines.push(`### HOOK: ${hookName}`);
234
- lines.push('');
235
- lines.push(op.description || op.name);
236
- lines.push('');
237
- lines.push('```');
238
- lines.push(`TYPE: ${op.kind}`);
239
- if (op.args.length > 0) {
240
- if (op.kind === 'mutation') {
241
- lines.push(`USAGE: const { mutate } = ${hookName}()`);
242
- lines.push(` mutate({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} })`);
243
- }
244
- else {
245
- lines.push(`USAGE: ${hookName}({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} })`);
246
- }
247
- lines.push('');
248
- lines.push('INPUT:');
249
- for (const arg of op.args) {
250
- lines.push(` ${arg.name}: ${(0, docs_utils_1.formatArgType)(arg)}`);
251
- }
252
- }
253
- else {
254
- lines.push(`USAGE: ${hookName}()`);
255
- lines.push('');
256
- lines.push('INPUT: none');
257
- }
258
- lines.push('');
259
- lines.push(`OUTPUT: ${op.kind === 'query' ? 'UseQueryResult' : 'UseMutationResult'}`);
260
- lines.push('```');
261
- lines.push('');
262
- }
263
- }
264
165
  return {
265
166
  fileName: 'AGENTS.md',
266
167
  content: lines.join('\n'),
@@ -70,6 +70,8 @@ function generateOrmReadme(tables, customOperations) {
70
70
  lines.push(`const deleted = await db.${singularName}.delete({ where: { ${pk.name}: '<value>' } }).execute();`);
71
71
  lines.push('```');
72
72
  lines.push('');
73
+ const ormSpecialGroups = (0, docs_utils_1.categorizeSpecialFields)(table);
74
+ lines.push(...(0, docs_utils_1.buildSpecialFieldsMarkdown)(ormSpecialGroups));
73
75
  }
74
76
  }
75
77
  if (customOperations.length > 0) {
@@ -113,17 +115,19 @@ function generateOrmReadme(tables, customOperations) {
113
115
  }
114
116
  function generateOrmAgentsDocs(tables, customOperations) {
115
117
  const lines = [];
116
- lines.push('# ORM Client - Agent Reference');
118
+ const tableCount = tables.length;
119
+ const customOpCount = customOperations.length;
120
+ lines.push('# ORM Client');
117
121
  lines.push('');
118
122
  lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
119
- lines.push('> This document is structured for LLM/agent consumption.');
120
123
  lines.push('');
121
- lines.push('## OVERVIEW');
124
+ lines.push('## Stack');
122
125
  lines.push('');
123
- lines.push('Prisma-like ORM client for interacting with a GraphQL API.');
124
- lines.push('All methods return a query builder. Call `.execute()` to run the query.');
126
+ lines.push('- Prisma-like ORM client for a GraphQL API (TypeScript)');
127
+ lines.push(`- ${tableCount} model${tableCount !== 1 ? 's' : ''}${customOpCount > 0 ? `, ${customOpCount} custom operation${customOpCount !== 1 ? 's' : ''}` : ''}`);
128
+ lines.push('- All methods return a query builder; call `.execute()` to run');
125
129
  lines.push('');
126
- lines.push('## SETUP');
130
+ lines.push('## Quick Start');
127
131
  lines.push('');
128
132
  lines.push('```typescript');
129
133
  lines.push("import { createClient } from './orm';");
@@ -134,88 +138,22 @@ function generateOrmAgentsDocs(tables, customOperations) {
134
138
  lines.push('});');
135
139
  lines.push('```');
136
140
  lines.push('');
137
- lines.push('## MODELS');
141
+ lines.push('## Resources');
138
142
  lines.push('');
139
- for (const table of tables) {
140
- const { singularName } = (0, utils_1.getTableNames)(table);
141
- const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
142
- const scalarFields = (0, utils_1.getScalarFields)(table);
143
- const editableFields = (0, docs_utils_1.getEditableFields)(table);
144
- lines.push(`### MODEL: ${singularName}`);
145
- lines.push('');
146
- lines.push(`Access: \`db.${singularName}\``);
147
- lines.push('');
148
- lines.push('```');
149
- lines.push('METHODS:');
150
- lines.push(` db.${singularName}.findMany({ select, where?, orderBy?, first?, offset? })`);
151
- lines.push(` db.${singularName}.findOne({ ${pk.name}, select })`);
152
- lines.push(` db.${singularName}.create({ data: { ${editableFields.map((f) => f.name).join(', ')} }, select })`);
153
- lines.push(` db.${singularName}.update({ where: { ${pk.name} }, data, select })`);
154
- lines.push(` db.${singularName}.delete({ where: { ${pk.name} } })`);
155
- lines.push('');
156
- lines.push('FIELDS:');
157
- for (const f of scalarFields) {
158
- const isPk = f.name === pk.name;
159
- lines.push(` ${f.name}: ${(0, utils_1.fieldTypeToTs)(f.type)}${isPk ? ' (primary key)' : ''}`);
160
- }
161
- lines.push('');
162
- lines.push('EDITABLE FIELDS:');
163
- for (const f of editableFields) {
164
- lines.push(` ${f.name}: ${(0, utils_1.fieldTypeToTs)(f.type)}`);
165
- }
166
- lines.push('');
167
- lines.push('OUTPUT: Promise<JSON>');
168
- lines.push(` findMany: [{ ${scalarFields.map((f) => f.name).join(', ')} }]`);
169
- lines.push(` findOne: { ${scalarFields.map((f) => f.name).join(', ')} }`);
170
- lines.push(` create: { ${scalarFields.map((f) => f.name).join(', ')} }`);
171
- lines.push(` update: { ${scalarFields.map((f) => f.name).join(', ')} }`);
172
- lines.push(` delete: { ${pk.name} }`);
173
- lines.push('```');
174
- lines.push('');
175
- }
176
- if (customOperations.length > 0) {
177
- lines.push('## CUSTOM OPERATIONS');
178
- lines.push('');
179
- for (const op of customOperations) {
180
- const accessor = op.kind === 'query' ? 'query' : 'mutation';
181
- lines.push(`### OPERATION: ${op.name}`);
182
- lines.push('');
183
- lines.push(op.description || op.name);
184
- lines.push('');
185
- lines.push('```');
186
- lines.push(`TYPE: ${op.kind}`);
187
- lines.push(`ACCESS: db.${accessor}.${op.name}`);
188
- if (op.args.length > 0) {
189
- lines.push(`USAGE: db.${accessor}.${op.name}({ ${op.args.map((a) => `${a.name}: <value>`).join(', ')} }).execute()`);
190
- lines.push('');
191
- lines.push('INPUT:');
192
- for (const arg of op.args) {
193
- lines.push(` ${arg.name}: ${(0, docs_utils_1.formatArgType)(arg)}`);
194
- }
195
- }
196
- else {
197
- lines.push(`USAGE: db.${accessor}.${op.name}().execute()`);
198
- lines.push('');
199
- lines.push('INPUT: none');
200
- }
201
- lines.push('');
202
- lines.push('OUTPUT: Promise<JSON>');
203
- lines.push('```');
204
- lines.push('');
205
- }
206
- }
207
- lines.push('## PATTERNS');
143
+ lines.push(`- **Full API reference:** [README.md](./README.md) model docs for all ${tableCount} tables`);
144
+ lines.push('- **Schema types:** [types.ts](./types.ts)');
145
+ lines.push('- **ORM client:** [orm.ts](./orm.ts)');
208
146
  lines.push('');
209
- lines.push('```typescript');
210
- lines.push('// All methods require .execute() to run');
211
- lines.push('const result = await db.modelName.findMany({ select: { id: true } }).execute();');
147
+ lines.push('## Conventions');
212
148
  lines.push('');
213
- lines.push('// Select specific fields');
214
- lines.push('const partial = await db.modelName.findMany({ select: { id: true, name: true } }).execute();');
149
+ lines.push('- Access models via `db.<ModelName>` (e.g. `db.User`)');
150
+ lines.push('- CRUD methods: `findMany`, `findOne`, `create`, `update`, `delete`');
151
+ lines.push('- Always call `.execute()` to run the query');
152
+ lines.push('- Custom operations via `db.query.<name>` or `db.mutation.<name>`');
215
153
  lines.push('');
216
- lines.push('// Filter with where clause');
217
- lines.push("const filtered = await db.modelName.findMany({ select: { id: true }, where: { name: 'test' } }).execute();");
218
- lines.push('```');
154
+ lines.push('## Boundaries');
155
+ lines.push('');
156
+ lines.push('All files in this directory are generated. Do not edit manually.');
219
157
  lines.push('');
220
158
  return {
221
159
  fileName: 'AGENTS.md',
@@ -359,11 +297,17 @@ function generateOrmSkills(tables, customOperations, targetName) {
359
297
  const modelName = (0, utils_1.lcFirst)(singularName);
360
298
  const refName = (0, komoji_1.toKebabCase)(singularName);
361
299
  referenceNames.push(refName);
300
+ const ormSkillSpecialGroups = (0, docs_utils_1.categorizeSpecialFields)(table);
301
+ const ormSkillBaseDesc = table.description || `ORM operations for ${table.name} records`;
302
+ const ormSkillSpecialDesc = ormSkillSpecialGroups.length > 0
303
+ ? ormSkillBaseDesc + '\n\n' +
304
+ ormSkillSpecialGroups.map((g) => `**${g.label}:** ${g.fields.map((f) => `\`${f.name}\``).join(', ')}\n${g.description}`).join('\n\n')
305
+ : ormSkillBaseDesc;
362
306
  files.push({
363
307
  fileName: `${skillName}/references/${refName}.md`,
364
308
  content: (0, docs_utils_1.buildSkillReference)({
365
309
  title: singularName,
366
- description: table.description || `ORM operations for ${table.name} records`,
310
+ description: ormSkillSpecialDesc,
367
311
  language: 'typescript',
368
312
  usage: [
369
313
  `db.${modelName}.findMany({ select: { id: true } }).execute()`,
@@ -3,7 +3,7 @@ import type { GeneratedDocFile, McpTool } from '../docs-utils';
3
3
  export { resolveDocsConfig } from '../docs-utils';
4
4
  export type { GeneratedDocFile, McpTool } from '../docs-utils';
5
5
  export declare function generateReadme(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, registry?: TypeRegistry): GeneratedDocFile;
6
- export declare function generateAgentsDocs(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, registry?: TypeRegistry): GeneratedDocFile;
6
+ export declare function generateAgentsDocs(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, _registry?: TypeRegistry): GeneratedDocFile;
7
7
  export declare function getCliMcpTools(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, registry?: TypeRegistry): McpTool[];
8
8
  export declare function generateSkills(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, targetName: string, registry?: TypeRegistry): GeneratedDocFile[];
9
9
  export interface MultiTargetDocsInput {