@contentrain/skills 0.1.2 → 0.2.1

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.
Files changed (42) hide show
  1. package/README.md +65 -60
  2. package/dist/index.cjs +65 -1
  3. package/dist/index.d.cts +50 -2
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +50 -2
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +65 -2
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +3 -1
  10. package/skills/contentrain/SKILL.md +149 -0
  11. package/skills/contentrain/references/architecture.md +212 -0
  12. package/skills/contentrain/references/content-formats.md +279 -0
  13. package/skills/contentrain/references/i18n.md +298 -0
  14. package/skills/contentrain/references/mcp-pipelines.md +195 -0
  15. package/skills/contentrain/references/mcp-tools.md +308 -0
  16. package/skills/contentrain/references/model-kinds.md +330 -0
  17. package/skills/contentrain/references/schema-types.md +177 -0
  18. package/skills/contentrain/references/security.md +146 -0
  19. package/skills/contentrain/references/workflow.md +285 -0
  20. package/skills/contentrain-bulk/SKILL.md +99 -0
  21. package/skills/contentrain-content/SKILL.md +162 -0
  22. package/skills/contentrain-diff/SKILL.md +62 -0
  23. package/skills/contentrain-doctor/SKILL.md +62 -0
  24. package/skills/contentrain-generate/SKILL.md +183 -0
  25. package/skills/contentrain-generate/references/generated-client.md +198 -0
  26. package/skills/contentrain-init/SKILL.md +99 -0
  27. package/skills/contentrain-model/SKILL.md +141 -0
  28. package/skills/contentrain-normalize/SKILL.md +185 -0
  29. package/skills/contentrain-normalize/references/extraction.md +164 -0
  30. package/skills/contentrain-normalize/references/reuse.md +146 -0
  31. package/skills/contentrain-normalize/references/what-is-content.md +115 -0
  32. package/skills/contentrain-quality/SKILL.md +180 -0
  33. package/skills/contentrain-quality/references/accessibility.md +160 -0
  34. package/skills/contentrain-quality/references/content-quality.md +299 -0
  35. package/skills/contentrain-quality/references/media.md +170 -0
  36. package/skills/contentrain-quality/references/seo.md +229 -0
  37. package/skills/contentrain-review/SKILL.md +170 -0
  38. package/skills/contentrain-sdk/SKILL.md +145 -0
  39. package/skills/contentrain-sdk/references/bundler-config.md +135 -0
  40. package/skills/contentrain-serve/SKILL.md +96 -0
  41. package/skills/contentrain-translate/SKILL.md +180 -0
  42. package/skills/contentrain-validate-fix/SKILL.md +92 -0
@@ -0,0 +1,308 @@
1
+ ---
2
+ name: mcp-tools
3
+ description: "Complete MCP tool parameter tables, entry formats, scan modes, apply parameters, bulk operations, and workflow tools."
4
+ ---
5
+
6
+ # MCP Tools Reference
7
+
8
+ ## Context Tools (read-only)
9
+
10
+ ### contentrain_status
11
+
12
+ Get full project state in one call. No parameters.
13
+
14
+ Returns:
15
+ - `initialized`: whether `.contentrain/` exists with config.json
16
+ - `config`: project configuration (stack, workflow, locales, domains)
17
+ - `models[]`: summary of all models (id, kind, domain, i18n, field count)
18
+ - `context`: last operation and project stats from `context.json`
19
+ - `vocabulary_size`: number of terms in vocabulary.json
20
+ - `branches`: branch health (total, merged, unmerged counts)
21
+ - `branch_warning`: warning message if too many active branches
22
+ - `next_steps`: suggested next actions
23
+
24
+ If not initialized, returns `detected_stack` and a suggestion to run `contentrain_init`.
25
+
26
+ ### contentrain_describe
27
+
28
+ Get full schema + optional sample for one model.
29
+
30
+ | Parameter | Type | Required | Description |
31
+ |-----------|------|----------|-------------|
32
+ | `model` | string | Yes | Model ID |
33
+ | `include_sample` | boolean | No | Include a sample entry |
34
+ | `locale` | string | No | Locale for sample data |
35
+
36
+ Returns full schema, all field definitions, entry stats, and stack-aware import snippets.
37
+
38
+ ### contentrain_describe_format
39
+
40
+ Get the storage and file-format contract for Contentrain content. No parameters.
41
+
42
+ Returns the complete format specification for all four model kinds, canonical serialization rules, and file path patterns.
43
+
44
+ ## Setup Tools
45
+
46
+ ### contentrain_init
47
+
48
+ Initialize `.contentrain/` directory structure.
49
+
50
+ | Parameter | Type | Required | Description |
51
+ |-----------|------|----------|-------------|
52
+ | `stack` | string | No | Framework: `"nuxt"`, `"next"`, `"astro"`, `"sveltekit"`, `"react"`, `"node"` |
53
+ | `locales` | string[] | No | Supported locales |
54
+ | `domains` | string[] | No | Content domains |
55
+
56
+ ### contentrain_scaffold
57
+
58
+ Generate models from a built-in template.
59
+
60
+ | Parameter | Type | Required | Description |
61
+ |-----------|------|----------|-------------|
62
+ | `template` | string | Yes | Template name (see below) |
63
+ | `locales` | string[] | No | Override default locales |
64
+ | `with_sample_content` | boolean | No | Generate example content (default: true) |
65
+
66
+ Available templates:
67
+
68
+ | Template | Models Created | Use Case |
69
+ |----------|---------------|----------|
70
+ | `blog` | blog-post (document), categories (collection), authors (collection) | Blog/magazine |
71
+ | `landing` | hero (singleton), features (singleton), pricing (collection), faq (collection), testimonials (collection) | Landing page |
72
+ | `docs` | doc-page (document), doc-categories (collection) | Documentation |
73
+ | `ecommerce` | products (collection), categories (collection), brands (collection) | E-commerce |
74
+ | `saas` | hero (singleton), features (singleton), pricing (collection), changelog (document), faq (collection) | SaaS site |
75
+ | `i18n` | navigation (singleton), form-labels (singleton), error-messages (dictionary), app-strings (dictionary) | Localization |
76
+ | `mobile` | app-strings (dictionary), error-messages (dictionary), onboarding (collection) | Mobile app |
77
+
78
+ ## Model Tools
79
+
80
+ ### contentrain_model_save
81
+
82
+ Create or update a model definition (upsert).
83
+
84
+ | Parameter | Type | Required | Description |
85
+ |-----------|------|----------|-------------|
86
+ | `id` | string | Yes | Kebab-case model ID |
87
+ | `name` | string | Yes | Human-readable display name |
88
+ | `kind` | string | Yes | `singleton`, `collection`, `document`, `dictionary` |
89
+ | `domain` | string | Yes | Organizational group |
90
+ | `i18n` | boolean | Yes | Whether model supports localization |
91
+ | `fields` | object | No | Field definitions (not for dictionary) |
92
+ | `description` | string | No | Model description |
93
+ | `content_path` | string | No | Framework-relative path for content files |
94
+ | `locale_strategy` | string | No | `"file"` (default), `"suffix"`, `"directory"`, `"none"` |
95
+
96
+ Notes:
97
+ - Upserts by model ID: existing model is updated, new model is created
98
+ - `locale_strategy: "none"` requires `i18n: false`
99
+ - `content_path` overrides default `.contentrain/content/` location
100
+
101
+ ### contentrain_model_delete
102
+
103
+ Delete a model and its content.
104
+
105
+ | Parameter | Type | Required | Description |
106
+ |-----------|------|----------|-------------|
107
+ | `model` | string | Yes | Model ID to delete |
108
+ | `confirm` | boolean | Yes | Must be `true` |
109
+
110
+ Performs referential integrity check before deletion. If other models reference the target model via `relation` or `relations` fields, deletion is **BLOCKED**. Remove the referencing fields first.
111
+
112
+ ## Content Tools
113
+
114
+ ### contentrain_content_save
115
+
116
+ Create or update content entries (upsert).
117
+
118
+ | Parameter | Type | Required | Description |
119
+ |-----------|------|----------|-------------|
120
+ | `model` | string | Yes | Target model ID |
121
+ | `entries` | array | Yes | Array of entry objects |
122
+
123
+ #### Entry Format
124
+
125
+ Each entry in the `entries` array:
126
+
127
+ ```json
128
+ {
129
+ "id": "optional-entry-id",
130
+ "slug": "optional-slug",
131
+ "locale": "en",
132
+ "data": { "field_name": "field_value" }
133
+ }
134
+ ```
135
+
136
+ **Per kind:**
137
+
138
+ | Kind | Required Fields | Notes |
139
+ |------|----------------|-------|
140
+ | **Collection** | `locale`, `data` | Provide `id` to update, omit for new (auto-generated) |
141
+ | **Document** | `slug`, `locale`, `data` | `slug` is required. Include `"body"` in `data` for markdown content |
142
+ | **Singleton** | `locale`, `data` | No `id` or `slug` needed |
143
+ | **Dictionary** | `locale`, `data` | `data` is flat key-value: `{ "auth.login": "Log In" }` |
144
+
145
+ **NEVER include system fields** (`status`, `source`, `updated_by`, `updated_at`, `createdAt`, `updatedAt`) in `data`.
146
+
147
+ ### contentrain_content_delete
148
+
149
+ Delete a content entry.
150
+
151
+ | Parameter | Type | Required | Description |
152
+ |-----------|------|----------|-------------|
153
+ | `model` | string | Yes | Model ID |
154
+ | `id` | string | Conditional | Entry ID (for collections) |
155
+ | `slug` | string | Conditional | Document slug (for documents) |
156
+ | `locale` | string | Conditional | Locale (for singleton/dictionary) |
157
+ | `confirm` | boolean | Yes | Must be `true` |
158
+
159
+ ### contentrain_content_list
160
+
161
+ List content entries for a model (read-only).
162
+
163
+ | Parameter | Type | Required | Description |
164
+ |-----------|------|----------|-------------|
165
+ | `model` | string | Yes | Model ID |
166
+ | `locale` | string | No | Locale filter |
167
+ | `filter` | object | No | Field-based filter |
168
+ | `resolve` | boolean | No | Resolve relations |
169
+ | `limit` | number | No | Max entries to return |
170
+ | `offset` | number | No | Pagination offset |
171
+
172
+ ## Normalize Tools
173
+
174
+ ### contentrain_scan
175
+
176
+ Scan project for structure or content candidates.
177
+
178
+ | Parameter | Type | Required | Description |
179
+ |-----------|------|----------|-------------|
180
+ | `mode` | string | No | `"candidates"` (default), `"graph"`, `"summary"` |
181
+ | `paths` | string[] | No | Directories to scan (auto-detected if omitted) |
182
+ | `include` | string[] | No | File extensions (default: .tsx, .jsx, .vue, .ts, .js, .mjs, .astro, .svelte) |
183
+ | `exclude` | string[] | No | Additional directories to exclude |
184
+ | `limit` | number | No | Batch size for candidates mode (default: 50) |
185
+ | `offset` | number | No | Pagination offset for candidates mode |
186
+ | `min_length` | number | No | Min string length filter |
187
+ | `max_length` | number | No | Max string length filter |
188
+
189
+ #### Scan Modes
190
+
191
+ | Mode | Output | Purpose |
192
+ |------|--------|---------|
193
+ | `graph` | Import/component dependency graph with string counts | Understand project structure, prioritize files |
194
+ | `candidates` | Hardcoded string candidates with file locations, context | Find content to extract (paginated) |
195
+ | `summary` | High-level stats by directory, top repeated strings | Quick project overview |
196
+
197
+ Candidates mode supports pagination via `offset` and `limit`. Response includes `stats.has_more`.
198
+
199
+ ### contentrain_apply
200
+
201
+ Apply normalize operation (extract or reuse).
202
+
203
+ | Parameter | Type | Required | Description |
204
+ |-----------|------|----------|-------------|
205
+ | `mode` | string | Yes | `"extract"` or `"reuse"` |
206
+ | `dry_run` | boolean | No | Preview without executing (default: `true`) |
207
+ | `extractions` | array | Conditional | For extract mode |
208
+ | `scope` | object | Conditional | For reuse mode (requires `model` or `domain`) |
209
+ | `patches` | array | Conditional | For reuse mode (max 100 per call) |
210
+
211
+ #### Extract Mode
212
+
213
+ ```json
214
+ {
215
+ "mode": "extract",
216
+ "dry_run": true,
217
+ "extractions": [
218
+ {
219
+ "model": "ui-texts",
220
+ "kind": "dictionary",
221
+ "domain": "system",
222
+ "i18n": true,
223
+ "fields": {},
224
+ "entries": [
225
+ {
226
+ "locale": "en",
227
+ "data": { "nav.home": "Home" },
228
+ "source": { "file": "src/Nav.vue", "line": 5, "value": "Home" }
229
+ }
230
+ ]
231
+ }
232
+ ]
233
+ }
234
+ ```
235
+
236
+ #### Reuse Mode
237
+
238
+ ```json
239
+ {
240
+ "mode": "reuse",
241
+ "dry_run": true,
242
+ "scope": { "model": "ui-texts" },
243
+ "patches": [
244
+ {
245
+ "file": "src/Nav.vue",
246
+ "line": 5,
247
+ "old_value": "Home",
248
+ "new_expression": "{{ $t('nav.home') }}",
249
+ "import_statement": ""
250
+ }
251
+ ]
252
+ }
253
+ ```
254
+
255
+ - `dry_run` defaults to `true`. ALWAYS preview first, then `dry_run: false` to execute
256
+ - `scope` requires at least one of `model` or `domain`
257
+ - `patches` max: 100 per call
258
+
259
+ ## Workflow Tools
260
+
261
+ ### contentrain_validate
262
+
263
+ Validate project content against model schemas.
264
+
265
+ | Parameter | Type | Required | Description |
266
+ |-----------|------|----------|-------------|
267
+ | `model` | string | No | Validate specific model only (omit for all) |
268
+ | `fix` | boolean | No | Auto-fix structural issues (canonical sort, orphan meta, missing locale files). Default: `false` |
269
+
270
+ ### contentrain_submit
271
+
272
+ Push contentrain/* branches to remote.
273
+
274
+ | Parameter | Type | Required | Description |
275
+ |-----------|------|----------|-------------|
276
+ | `branches` | string[] | No | Specific branches to push (omit for all contentrain/* branches) |
277
+ | `message` | string | No | Optional message for the push operation |
278
+
279
+ ## Bulk Tools
280
+
281
+ ### contentrain_bulk
282
+
283
+ Run git-backed batch operations on existing content entries.
284
+
285
+ | Parameter | Type | Required | Description |
286
+ |-----------|------|----------|-------------|
287
+ | `operation` | string | Yes | `"copy_locale"`, `"update_status"`, `"delete_entries"` |
288
+ | `model` | string | Yes | Target model ID |
289
+ | `source_locale` | string | Conditional | For `copy_locale` |
290
+ | `target_locale` | string | Conditional | For `copy_locale` |
291
+ | `entry_ids` | string[] | Conditional | For `update_status` and `delete_entries` |
292
+ | `status` | string | Conditional | For `update_status` |
293
+ | `confirm` | boolean | Conditional | For `delete_entries` (must be `true`) |
294
+
295
+ #### Operations
296
+
297
+ | Operation | Applicable Kinds | Required Params |
298
+ |-----------|-----------------|-----------------|
299
+ | `copy_locale` | collection, singleton, dictionary (i18n only) | `source_locale`, `target_locale` |
300
+ | `update_status` | collection only | `entry_ids`, `status` |
301
+ | `delete_entries` | collection only | `entry_ids`, `confirm: true` |
302
+
303
+ #### Rules
304
+
305
+ - ALWAYS verify the target model with `contentrain_describe` or `contentrain_status` before running bulk
306
+ - `copy_locale` MUST NOT be used on non-i18n models
307
+ - Bulk operations create branches and commits like other write tools
308
+ - Validate afterward when content shape may have changed
@@ -0,0 +1,330 @@
1
+ ---
2
+ name: model-kinds
3
+ description: "Detailed model kinds reference including storage formats, relations, nesting rules, and examples for all four kinds."
4
+ ---
5
+
6
+ # Model Kinds Reference
7
+
8
+ Contentrain has four model kinds, each with distinct storage format, ID management, and use cases.
9
+
10
+ ## Singleton
11
+
12
+ **One instance per locale.** Use for page sections, site config, navigation.
13
+
14
+ | Aspect | Detail |
15
+ |--------|--------|
16
+ | Storage | JSON object (one file per locale) |
17
+ | File path | `.contentrain/content/{domain}/{model-id}/{locale}.json` |
18
+ | `i18n: false` path | `.contentrain/content/{domain}/{model-id}/data.json` |
19
+ | ID management | None (single instance) |
20
+ | Relation target | Cannot be referenced by relations |
21
+
22
+ ### Example Model
23
+
24
+ ```json
25
+ {
26
+ "id": "hero-section",
27
+ "name": "Hero Section",
28
+ "kind": "singleton",
29
+ "domain": "marketing",
30
+ "i18n": true,
31
+ "fields": {
32
+ "title": { "type": "string", "required": true, "max": 80 },
33
+ "subtitle": { "type": "text", "max": 200 },
34
+ "cta": { "type": "string", "required": true },
35
+ "cta_url": { "type": "url", "required": true },
36
+ "background": { "type": "image" }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ### Example Content (`en.json`)
42
+
43
+ ```json
44
+ {
45
+ "background": "assets/hero-bg.webp",
46
+ "cta": "Get Started",
47
+ "cta_url": "https://app.example.com/signup",
48
+ "subtitle": "The modern content platform for developers",
49
+ "title": "Build faster with Contentrain"
50
+ }
51
+ ```
52
+
53
+ **Use for:** Hero sections, navigation menus, site settings, form labels, footer content -- anything with exactly one instance per locale.
54
+
55
+ ## Collection
56
+
57
+ **Multiple entries.** Use for team members, products, FAQs, categories.
58
+
59
+ | Aspect | Detail |
60
+ |--------|--------|
61
+ | Storage | JSON object-map (entry ID as key, sorted lexicographically) |
62
+ | File path | `.contentrain/content/{domain}/{model-id}/{locale}.json` |
63
+ | Tool output | Array with `id` injected |
64
+ | ID management | Auto-generated 12-char hex |
65
+ | Relation target | Referenced by entry `id` |
66
+
67
+ ### Example Model
68
+
69
+ ```json
70
+ {
71
+ "id": "team-members",
72
+ "name": "Team Members",
73
+ "kind": "collection",
74
+ "domain": "marketing",
75
+ "i18n": true,
76
+ "fields": {
77
+ "name": { "type": "string", "required": true },
78
+ "role": { "type": "string", "required": true },
79
+ "avatar": { "type": "image" },
80
+ "bio": { "type": "text" },
81
+ "linkedin": { "type": "url" }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### Storage Format (on disk)
87
+
88
+ ```json
89
+ {
90
+ "a1b2c3d4e5f6": {
91
+ "avatar": "assets/ahmet.jpg",
92
+ "bio": "Founder and CEO",
93
+ "linkedin": "https://linkedin.com/in/ahmet",
94
+ "name": "Ahmet",
95
+ "role": "CEO"
96
+ },
97
+ "f6e5d4c3b2a1": {
98
+ "avatar": "assets/jane.jpg",
99
+ "bio": "Technical leader",
100
+ "linkedin": "https://linkedin.com/in/jane",
101
+ "name": "Jane",
102
+ "role": "CTO"
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### MCP Tool Output (differs from storage)
108
+
109
+ MCP tools return collections as arrays with `id` injected:
110
+
111
+ ```json
112
+ [
113
+ { "id": "a1b2c3d4e5f6", "name": "Ahmet", "role": "CEO", "avatar": "assets/ahmet.jpg" },
114
+ { "id": "f6e5d4c3b2a1", "name": "Jane", "role": "CTO", "avatar": "assets/jane.jpg" }
115
+ ]
116
+ ```
117
+
118
+ ### ID Management
119
+
120
+ - IDs are 12-char hex strings, auto-generated by MCP tools
121
+ - Provide `id` in `contentrain_content_save` entry to update an existing entry
122
+ - Omit `id` to create a new entry (ID auto-generated)
123
+ - The same ID is used across all locale files for the same entry
124
+
125
+ **Why object-map?**
126
+ - Sorted keys produce predictable diffs and minimize Git merge conflicts
127
+ - No ID duplication -- key IS the ID
128
+ - Two branches adding different entries rarely conflict (~0.3% chance for 350 entries)
129
+
130
+ ## Document
131
+
132
+ **Markdown with frontmatter.** Use for blog posts, documentation, changelogs.
133
+
134
+ | Aspect | Detail |
135
+ |--------|--------|
136
+ | Storage | `.md` file with YAML frontmatter |
137
+ | File path | `.contentrain/content/{domain}/{model-id}/{slug}/{locale}.md` |
138
+ | `i18n: false` path | `.contentrain/content/{domain}/{model-id}/{slug}.md` |
139
+ | ID management | `slug` field (URL-safe, unique) |
140
+ | Relation target | Referenced by `slug` |
141
+
142
+ ### Example Model
143
+
144
+ ```json
145
+ {
146
+ "id": "blog-post",
147
+ "name": "Blog Post",
148
+ "kind": "document",
149
+ "domain": "blog",
150
+ "i18n": true,
151
+ "fields": {
152
+ "title": { "type": "string", "required": true, "max": 120 },
153
+ "slug": { "type": "slug", "required": true, "unique": true },
154
+ "excerpt": { "type": "text", "max": 200 },
155
+ "author": { "type": "relation", "model": "team-members", "required": true },
156
+ "tags": { "type": "array", "items": "string" },
157
+ "cover": { "type": "image" }
158
+ }
159
+ }
160
+ ```
161
+
162
+ ### Example Content (`getting-started/en.md`)
163
+
164
+ ```markdown
165
+ ---
166
+ title: Getting Started
167
+ slug: getting-started
168
+ excerpt: Learn how to set up Contentrain in your project
169
+ author: a1b2c3d4e5f6
170
+ tags: [tutorial, intro]
171
+ cover: assets/getting-started-cover.webp
172
+ ---
173
+ # Getting Started with Contentrain
174
+
175
+ Your markdown body content here...
176
+ ```
177
+
178
+ ### Slug Management
179
+
180
+ - Slug is the directory name and identity for the document
181
+ - Provide `slug` in `contentrain_content_save` entry (required for documents)
182
+ - Slug must be URL-safe: lowercase, kebab-case, matching `/^[a-z0-9]+(?:-[a-z0-9]+)*$/`
183
+ - Same slug used across all locale files
184
+
185
+ **Use for:** Blog posts, documentation pages, changelogs, knowledge base articles -- long-form content with typed metadata.
186
+
187
+ ## Dictionary
188
+
189
+ **Flat key-value pairs.** Use for error messages, UI strings, translations.
190
+
191
+ | Aspect | Detail |
192
+ |--------|--------|
193
+ | Storage | Flat JSON object (key-value, all values are strings) |
194
+ | File path | `.contentrain/content/{domain}/{model-id}/{locale}.json` |
195
+ | Fields definition | None -- dictionary models have NO `fields` property |
196
+ | ID management | Key is identity |
197
+ | Relation target | Cannot be referenced by relations |
198
+
199
+ ### Example Model
200
+
201
+ ```json
202
+ {
203
+ "id": "error-messages",
204
+ "name": "Error Messages",
205
+ "kind": "dictionary",
206
+ "domain": "system",
207
+ "i18n": true
208
+ }
209
+ ```
210
+
211
+ ### Example Content (`en.json`)
212
+
213
+ ```json
214
+ {
215
+ "auth.expired": "Session expired. Please log in again.",
216
+ "auth.failed": "Authentication failed. Check your credentials.",
217
+ "auth.locked": "Account locked. Contact support.",
218
+ "validation.email": "Please enter a valid email address.",
219
+ "validation.required": "{field} is required."
220
+ }
221
+ ```
222
+
223
+ ### Key Naming Rules
224
+
225
+ - Semantic, hierarchical, dot-separated keys
226
+ - Always in English regardless of locale
227
+ - Lowercase with dots as separators, no spaces, no camelCase
228
+ - Group by feature/domain, not by page/position
229
+ - GOOD: `auth.login.button`, `errors.404.title`, `nav.main.dashboard`
230
+ - BAD: `button1`, `text_3`, `label_top`, `str_47`
231
+
232
+ **Use for:** Error messages, UI strings, i18n translation files, mobile app strings, form validation messages.
233
+
234
+ ## Relations
235
+
236
+ ### Basic Relations
237
+
238
+ ```json
239
+ "author": { "type": "relation", "model": "team-members", "required": true }
240
+ "categories": { "type": "relations", "model": "categories" }
241
+ ```
242
+
243
+ - `relation` (1:1): Value is a single string -- an entry ID or slug
244
+ - `relations` (1:many): Value is a string array of entry IDs or slugs
245
+
246
+ ### Target Model Restrictions
247
+
248
+ | Target Kind | Reference Key | Can Be Target? |
249
+ |-------------|--------------|----------------|
250
+ | Collection | Entry `id` | Yes |
251
+ | Document | Document `slug` | Yes |
252
+ | Singleton | -- | No |
253
+ | Dictionary | -- | No |
254
+
255
+ ### Polymorphic Relations
256
+
257
+ When a field can reference multiple model types:
258
+
259
+ ```json
260
+ "target": { "type": "relation", "model": ["blog-post", "page"] }
261
+ ```
262
+
263
+ Storage for polymorphic references uses a compound value:
264
+
265
+ ```json
266
+ { "model": "blog-post", "ref": "getting-started" }
267
+ ```
268
+
269
+ ### Self-Referencing
270
+
271
+ Models can reference themselves (e.g., hierarchical categories):
272
+
273
+ ```json
274
+ "parent": { "type": "relation", "model": "categories" }
275
+ ```
276
+
277
+ ### Resolution Rules
278
+
279
+ - Relations are resolved **1 level deep** -- no recursive resolution
280
+ - **Unresolved IDs** (target entry does not exist) are kept as raw strings (graceful degradation)
281
+ - **Cascade deletion does not exist.** Deleting a referenced entry produces a broken relation warning
282
+ - **Array order is preserved** for `relations` type
283
+ - **IDs/slugs are locale-agnostic.** The same reference works across all locales
284
+
285
+ ### Validation
286
+
287
+ - Referenced ID/slug MUST exist in the target model
288
+ - `model` property MUST reference an existing model ID
289
+ - Referential integrity is checked by `contentrain_validate`
290
+ - Deleting a model that is referenced by other models is BLOCKED
291
+
292
+ ## Nesting
293
+
294
+ ### Object Type
295
+
296
+ ```json
297
+ "address": {
298
+ "type": "object",
299
+ "fields": {
300
+ "city": { "type": "string", "required": true },
301
+ "street": { "type": "string", "required": true },
302
+ "zip": { "type": "string" }
303
+ }
304
+ }
305
+ ```
306
+
307
+ ### Array of Objects
308
+
309
+ ```json
310
+ "variants": {
311
+ "type": "array",
312
+ "items": {
313
+ "type": "object",
314
+ "fields": {
315
+ "color": { "type": "color", "required": true },
316
+ "price": { "type": "decimal", "required": true },
317
+ "size": { "type": "select", "options": ["S", "M", "L"] }
318
+ }
319
+ },
320
+ "max": 50
321
+ }
322
+ ```
323
+
324
+ ### Depth Limit
325
+
326
+ **Maximum nesting depth: 2 levels.** An object inside an object is allowed. An object inside an object inside an object is NOT.
327
+
328
+ - Prefer flat types over deeply nested structures
329
+ - Use relations to model complex data relationships instead of nesting
330
+ - If you need deeper nesting, create a separate model and use a `relation` field