@contentrain/rules 0.2.0 → 0.3.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 (60) hide show
  1. package/README.md +24 -51
  2. package/context/context-bridge.md +2 -2
  3. package/dist/index.cjs +2 -26
  4. package/dist/index.d.cts +2 -11
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.mts +2 -11
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +2 -23
  9. package/dist/index.mjs.map +1 -1
  10. package/essential/contentrain-essentials.md +91 -0
  11. package/package.json +6 -9
  12. package/prompts/generate-mode.md +1 -1
  13. package/ide/claude-code/contentrain.md +0 -2984
  14. package/ide/claude-code/rules/contentrain-accessibility-rules.md +0 -155
  15. package/ide/claude-code/rules/contentrain-content-conventions.md +0 -314
  16. package/ide/claude-code/rules/contentrain-content-quality.md +0 -294
  17. package/ide/claude-code/rules/contentrain-i18n-quality.md +0 -293
  18. package/ide/claude-code/rules/contentrain-mcp-usage.md +0 -405
  19. package/ide/claude-code/rules/contentrain-media-rules.md +0 -165
  20. package/ide/claude-code/rules/contentrain-normalize-rules.md +0 -312
  21. package/ide/claude-code/rules/contentrain-schema-rules.md +0 -380
  22. package/ide/claude-code/rules/contentrain-security-rules.md +0 -141
  23. package/ide/claude-code/rules/contentrain-seo-rules.md +0 -224
  24. package/ide/claude-code/rules/contentrain-workflow-rules.md +0 -262
  25. package/ide/cursor/contentrain.cursorrules +0 -2959
  26. package/ide/cursor/rules/contentrain-accessibility-rules.mdc +0 -160
  27. package/ide/cursor/rules/contentrain-content-conventions.mdc +0 -319
  28. package/ide/cursor/rules/contentrain-content-quality.mdc +0 -299
  29. package/ide/cursor/rules/contentrain-i18n-quality.mdc +0 -298
  30. package/ide/cursor/rules/contentrain-mcp-usage.mdc +0 -410
  31. package/ide/cursor/rules/contentrain-media-rules.mdc +0 -170
  32. package/ide/cursor/rules/contentrain-normalize-rules.mdc +0 -317
  33. package/ide/cursor/rules/contentrain-schema-rules.mdc +0 -385
  34. package/ide/cursor/rules/contentrain-security-rules.mdc +0 -146
  35. package/ide/cursor/rules/contentrain-seo-rules.mdc +0 -229
  36. package/ide/cursor/rules/contentrain-workflow-rules.mdc +0 -267
  37. package/ide/generic/contentrain.md +0 -2984
  38. package/ide/windsurf/contentrain.windsurfrules +0 -2984
  39. package/ide/windsurf/rules/contentrain-accessibility-rules.md +0 -160
  40. package/ide/windsurf/rules/contentrain-content-conventions.md +0 -319
  41. package/ide/windsurf/rules/contentrain-content-quality.md +0 -299
  42. package/ide/windsurf/rules/contentrain-i18n-quality.md +0 -298
  43. package/ide/windsurf/rules/contentrain-mcp-usage.md +0 -410
  44. package/ide/windsurf/rules/contentrain-media-rules.md +0 -170
  45. package/ide/windsurf/rules/contentrain-normalize-rules.md +0 -317
  46. package/ide/windsurf/rules/contentrain-schema-rules.md +0 -385
  47. package/ide/windsurf/rules/contentrain-security-rules.md +0 -146
  48. package/ide/windsurf/rules/contentrain-seo-rules.md +0 -229
  49. package/ide/windsurf/rules/contentrain-workflow-rules.md +0 -267
  50. package/shared/accessibility-rules.md +0 -155
  51. package/shared/content-conventions.md +0 -314
  52. package/shared/content-quality.md +0 -294
  53. package/shared/i18n-quality.md +0 -293
  54. package/shared/mcp-usage.md +0 -405
  55. package/shared/media-rules.md +0 -165
  56. package/shared/normalize-rules.md +0 -312
  57. package/shared/schema-rules.md +0 -380
  58. package/shared/security-rules.md +0 -141
  59. package/shared/seo-rules.md +0 -224
  60. package/shared/workflow-rules.md +0 -262
@@ -1,317 +0,0 @@
1
- ---
2
- description: Normalize Rules — Contentrain content governance
3
- alwaysApply: true
4
- ---
5
-
6
- # Contentrain Normalize Rules
7
-
8
- > These rules govern the normalize flow: extracting hardcoded content from source code into `.contentrain/` (Phase 1) and patching source files to reference extracted content (Phase 2).
9
-
10
- ---
11
-
12
- ## 1. Purpose
13
-
14
- Normalize converts a codebase with hardcoded strings into a Contentrain-managed content architecture:
15
-
16
- - **Phase 1 (Extraction):** Pull content from source code into `.contentrain/` structure. Source files are NOT modified.
17
- - **Phase 2 (Reuse):** Patch source files to replace hardcoded strings with content references. Requires completed extraction.
18
-
19
- Each phase produces a separate branch for independent review. This separation ensures extraction can be reviewed and merged before any source file modifications occur.
20
-
21
- ---
22
-
23
- ## 2. Two-Phase Architecture
24
-
25
- | Aspect | Phase 1: Extraction | Phase 2: Reuse |
26
- |--------|-------------------|----------------|
27
- | Purpose | Pull content from source to `.contentrain/` | Patch source files with content references |
28
- | Scope | Full project scan | Per model or per domain |
29
- | Source files modified | No | Yes |
30
- | Branch pattern | `contentrain/normalize/extract/{domain}/{timestamp}` | `contentrain/normalize/reuse/{model}/{locale}/{timestamp}` |
31
- | Prerequisite | Initialized `.contentrain/` | Completed extraction (content exists in `.contentrain/`) |
32
- | Workflow mode | Always `review` | Always `review` |
33
- | Standalone value | Yes -- content is manageable in Studio immediately | Depends on Phase 1 |
34
-
35
- ### Why Two Phases?
36
-
37
- - Phase 1 alone is valuable: content is extracted, Studio can manage it, new locales can be added.
38
- - Phase 2 can be done incrementally, model by model, reducing risk.
39
- - Separate reviews mean clearer diffs and easier rollback.
40
-
41
- ---
42
-
43
- ## 3. Agent vs MCP Responsibilities
44
-
45
- ### Agent (Intelligence Layer -- You)
46
-
47
- - **Decide what is content vs code.** This is a semantic judgment that requires understanding context.
48
- - **Assign domain grouping.** Determine which domain each piece of content belongs to.
49
- - **Determine model structure.** Group related content into models, choose the right kind.
50
- - **Create replacement expressions.** These are stack-specific and require framework knowledge:
51
- - Vue/Nuxt: `{{ $t('key') }}` or `{{ t('key') }}`
52
- - React/Next: `{t('key')}` or `{intl.formatMessage({id: 'key'})}`
53
- - Svelte/SvelteKit: `{$t('key')}`
54
- - Astro: `{t('key')}`
55
- - Generic: direct import from `#contentrain`
56
- - **Filter false positives** from scan candidates.
57
- - **Evaluate and group** candidates into logical models.
58
-
59
- ### MCP (Deterministic Infrastructure)
60
-
61
- - **Scan the file system** (build graph, find candidates).
62
- - **Read and write files** in `.contentrain/`.
63
- - **Patch source files** with exact string replacement (agent provides the replacement expression).
64
- - **Create branches, commit, validate** all changes.
65
- - **Enforce guardrails** (file limits, type restrictions, dry-run requirement).
66
-
67
- **Rule:** MCP is framework-agnostic. It does NOT know how `{t('key')}` differs from `{{ $t('key') }}`. The agent provides all stack-specific logic.
68
-
69
- ---
70
-
71
- ## 4. Guardrails
72
-
73
- These limits protect against runaway operations and ensure reviewable changes.
74
-
75
- | Guardrail | Limit | Rationale |
76
- |-----------|-------|-----------|
77
- | Allowed source file types | `.vue`, `.tsx`, `.jsx`, `.ts`, `.js`, `.mjs`, `.astro`, `.svelte` | Only scan files that can contain UI content |
78
- | Max files per scan | 500 | Prevent scanning entire monorepos |
79
- | Max files per apply | 100 | Keep diffs reviewable |
80
- | Dry-run before apply | **MANDATORY** | Preview all changes before executing |
81
- | Workflow mode | Always `review` | Normalize changes are never auto-merged |
82
- | Reuse scope | Per model or per domain | No whole-project reuse in one operation |
83
- | Reuse prerequisite | Extraction must be complete | Content must exist in `.contentrain/` before patching source |
84
-
85
- ### Enforcement
86
-
87
- - Attempting to apply without a prior dry-run will be rejected.
88
- - Attempting to reuse without completed extraction will be rejected.
89
- - Exceeding file limits will truncate results with a warning.
90
-
91
- ---
92
-
93
- ## 5. What IS Content
94
-
95
- Use these heuristics to identify content strings in source code. Content is user-visible text that should be managed separately from code.
96
-
97
- ### Extract These
98
-
99
- - **Headings and titles** in templates/JSX (`<h1>`, `<h2>`, etc.)
100
- - **Paragraph text** and body copy
101
- - **Button labels** (`<button>Submit</button>`)
102
- - **Link text** (`<a>Learn more</a>`)
103
- - **Form labels and placeholders** (`<label>`, `placeholder="..."`)
104
- - **Error messages** shown to users
105
- - **Success/notification messages**
106
- - **Alt text** on images (`alt="..."`)
107
- - **ARIA labels** (`aria-label="..."`)
108
- - **Meta descriptions and page titles** (`<title>`, `<meta name="description">`)
109
- - **Navigation items** (menu labels, breadcrumbs)
110
- - **Tooltip text**
111
- - **Empty state messages** ("No results found")
112
- - **CTA (call-to-action) text**
113
-
114
- ### Do NOT Extract These
115
-
116
- - CSS class names, HTML IDs
117
- - Variable names, function names, parameter names
118
- - Technical identifiers (API endpoints, route paths, event names)
119
- - Import paths and file paths
120
- - Numbers used as constants (port numbers, HTTP status codes, pixel dimensions, timeouts)
121
- - Strings shorter than 3 characters (unless semantically meaningful: "OK", "No", "Yes")
122
- - Regular expressions
123
- - Configuration values (environment variables, feature flags)
124
- - Log messages (internal, not user-facing)
125
- - Code comments
126
- - Test assertion strings
127
- - JSON keys
128
- - Enum values used as code identifiers (not displayed to users)
129
-
130
- ### Dictionary Interpolation Limitation
131
-
132
- Strings containing dynamic expressions (`${variable}`, template literals with runtime values) cannot be stored in dictionaries. These must remain as hardcoded expressions in source code.
133
-
134
- **CANNOT extract:**
135
-
136
- - `"Add a new entry to ${modelId}"` — contains runtime variable
137
- - `` `Hello, ${user.name}!` `` — template literal with variable
138
-
139
- **CAN extract:**
140
-
141
- - `"Add a new entry"` — static string (parameterize separately if needed)
142
- - `"Hello, World!"` — no runtime variables
143
-
144
- Agent should split parameterized strings: extract the static template pattern and leave the interpolation in code.
145
-
146
- ```
147
- // Instead of: `"Add a new entry to ${modelId}"`
148
- // Extract: dictionary key "add-entry-to" = "Add a new entry to"
149
- // Code: `${t['add-entry-to']} ${modelId}`
150
- ```
151
-
152
- ### Edge Cases
153
-
154
- | String | Extract? | Reason |
155
- |--------|----------|--------|
156
- | `"OK"` | Yes | User-visible confirmation |
157
- | `"px"` | No | CSS unit, not content |
158
- | `"Loading..."` | Yes | User-visible state message |
159
- | `"GET"` | No | HTTP method, technical |
160
- | `"en"` | No | Locale code, technical |
161
- | `"Submit Form"` | Yes | User-visible button label |
162
- | `"/api/users"` | No | API endpoint |
163
- | `"flex"` | No | CSS value |
164
- | `"user.name"` | No | Object property path |
165
- | `"An error occurred"` | Yes | User-facing error message |
166
-
167
- ---
168
-
169
- ## 6. Phase 1: Extraction Flow
170
-
171
- ### Step-by-Step
172
-
173
- 1. **Build project graph:**
174
- ```
175
- contentrain_scan(mode: "graph")
176
- ```
177
- Returns import/component dependency graph. Use this to understand project structure and prioritize files.
178
-
179
- 2. **Find candidates:**
180
- ```
181
- contentrain_scan(mode: "candidates")
182
- ```
183
- Returns hardcoded string candidates with file locations, line numbers, and surrounding context.
184
-
185
- 3. **Agent evaluation (your job):**
186
- - Filter out false positives using the heuristics in Section 5.
187
- - Group candidates by domain (e.g., `marketing`, `blog`, `system`).
188
- - Determine model structure: which kind (singleton, collection, dictionary), which fields.
189
- - Assign field names and keys.
190
-
191
- 4. **Preview extraction:**
192
- ```
193
- contentrain_apply(mode: "extract", dry_run: true)
194
- ```
195
- Returns a preview of what will be created in `.contentrain/` without making changes.
196
-
197
- 5. **Review dry-run output:**
198
- - Verify models are structured correctly.
199
- - Verify content assignments are accurate.
200
- - Check for missing or misclassified candidates.
201
-
202
- 6. **Execute extraction:**
203
- ```
204
- contentrain_apply(mode: "extract")
205
- ```
206
- Creates model definitions and content files in `.contentrain/`.
207
-
208
- 7. **Validate and submit:**
209
- ```
210
- contentrain_validate
211
- contentrain_submit
212
- ```
213
- Submit always uses `review` mode for normalize operations.
214
-
215
- ### Extraction Rules
216
-
217
- - Extraction creates content in `.contentrain/` but does NOT modify source files.
218
- - Each extraction produces a single branch with all extracted content.
219
- - Group related content into the fewest models that make semantic sense.
220
- - Prefer dictionary kind for UI labels and error messages.
221
- - Prefer singleton kind for page-specific content (hero, features).
222
- - Prefer collection kind for repeating items (team members, FAQs, testimonials).
223
-
224
- ---
225
-
226
- ## 7. Phase 2: Reuse Flow
227
-
228
- ### Step-by-Step
229
-
230
- 1. **Select scope:** Choose one model or domain to process.
231
-
232
- 2. **Determine replacement expressions (your job):**
233
- Based on the project's tech stack, determine how source files should reference content:
234
-
235
- | Stack | Pattern | Example |
236
- |-------|---------|---------|
237
- | Vue/Nuxt (i18n) | `{{ $t('key') }}` | `{{ $t('hero.title') }}` |
238
- | React/Next (react-intl) | `{intl.formatMessage({id: 'key'})}` | `{intl.formatMessage({id: 'hero.title'})}` |
239
- | React/Next (next-intl) | `{t('key')}` | `{t('hero.title')}` |
240
- | Svelte/SvelteKit | `{$t('key')}` | `{$t('hero.title')}` |
241
- | Astro | `{t('key')}` | `{t('hero.title')}` |
242
- | SDK import | `query('model').all()` | Direct data access via `#contentrain` |
243
-
244
- 3. **Preview reuse:**
245
- ```
246
- contentrain_apply(mode: "reuse", scope: "model-id", dry_run: true)
247
- ```
248
- Returns a preview of source file patches.
249
-
250
- 4. **Review dry-run output:**
251
- - Verify each replacement is correct.
252
- - Verify import statements will be added where needed.
253
- - Check that no non-content strings are being replaced.
254
-
255
- 5. **Execute reuse:**
256
- ```
257
- contentrain_apply(mode: "reuse", scope: "model-id")
258
- ```
259
- Patches source files with content references.
260
-
261
- 6. **Validate and submit:**
262
- ```
263
- contentrain_validate
264
- contentrain_submit
265
- ```
266
-
267
- 7. **Repeat** for each model/domain until all extracted content is referenced.
268
-
269
- ### Reuse Rules
270
-
271
- - Process one model or domain at a time. Do NOT reuse the entire project in one operation.
272
- - Ensure the i18n/content library is properly configured in the project before reuse.
273
- - Add necessary imports to patched files (e.g., `import { useTranslation } from 'next-intl'`).
274
- - Do NOT change the structure or behavior of components -- only replace string literals with content references.
275
- - If a source file requires setup code (composable import, hook call), include it in the patch.
276
-
277
- ---
278
-
279
- ## 8. Domain and Model Grouping Guidelines
280
-
281
- When evaluating scan candidates, group content logically:
282
-
283
- ### Domain Assignment
284
-
285
- | Content Location | Suggested Domain |
286
- |------------------|-----------------|
287
- | Landing page, marketing sections | `marketing` |
288
- | Blog, articles, posts | `blog` |
289
- | Navigation, footer, header | `ui` |
290
- | Error messages, validation | `system` |
291
- | Product pages, e-commerce | `product` |
292
- | Documentation, help | `docs` |
293
- | User-facing app strings | `app` |
294
-
295
- ### Model Kind Selection
296
-
297
- | Content Pattern | Kind | Rationale |
298
- |----------------|------|-----------|
299
- | One set of fields per page section | `singleton` | Hero, features, pricing header |
300
- | Multiple items of same type | `collection` | Team members, FAQs, testimonials |
301
- | Long-form with metadata | `document` | Blog posts, documentation pages |
302
- | Key-value UI strings | `dictionary` | Error messages, button labels, form labels |
303
-
304
- ---
305
-
306
- ## 9. Common Mistakes to Avoid
307
-
308
- | Mistake | Correct Approach |
309
- |---------|-----------------|
310
- | Extracting CSS values as content | Only extract user-visible text |
311
- | Creating one model per component | Group related content into shared models |
312
- | Skipping dry-run | ALWAYS preview before apply |
313
- | Auto-merging normalize changes | Normalize ALWAYS uses review mode |
314
- | Reusing before extraction is merged | Wait for extraction review and merge first |
315
- | Processing all models in one reuse | Scope reuse to one model/domain at a time |
316
- | Ignoring project graph | Use graph output to understand component relationships |
317
- | Hardcoding replacement patterns | Detect the project's i18n stack and use its conventions |
@@ -1,385 +0,0 @@
1
- ---
2
- description: Schema Rules — Contentrain content governance
3
- alwaysApply: true
4
- ---
5
-
6
- # Contentrain Schema Rules
7
-
8
- > These rules define the Contentrain type system, field definitions, model definitions, and relation system. Follow them exactly when creating or modifying models and content schemas.
9
-
10
- ---
11
-
12
- ## 1. Type System Overview
13
-
14
- Contentrain uses a **flat type system** with 27 types. Each type is a single keyword -- there is no `format` sub-layer. `type: "email"` is the complete specification.
15
-
16
- This design is optimized for AI agents: easy to produce, cheap to read, fast to validate.
17
-
18
- ---
19
-
20
- ## 2. Complete Type Reference (27 Types)
21
-
22
- ### 2.1 String Family (11 types)
23
-
24
- | Type | Description | Validation | JSON Schema Export |
25
- |------|-------------|------------|-------------------|
26
- | `string` | Single-line text | -- | `{ "type": "string" }` |
27
- | `text` | Multi-line text | -- | `{ "type": "string" }` |
28
- | `email` | Email address | RFC 5321 | `{ "type": "string", "format": "email" }` |
29
- | `url` | URL | RFC 3986 | `{ "type": "string", "format": "uri" }` |
30
- | `slug` | URL-safe identifier | `/^[a-z0-9]+(?:-[a-z0-9]+)*$/` | `{ "type": "string", "pattern": "..." }` |
31
- | `color` | Hex color code | `/^#[0-9a-fA-F]{6}$/` | `{ "type": "string" }` |
32
- | `phone` | Phone number | E.164 or freeform | `{ "type": "string" }` |
33
- | `code` | Code snippet | -- | `{ "type": "string" }` |
34
- | `icon` | Icon identifier | -- | `{ "type": "string" }` |
35
- | `markdown` | Markdown content | -- | `{ "type": "string" }` |
36
- | `richtext` | HTML rich text | -- | `{ "type": "string" }` |
37
-
38
- ### 2.2 Number Family (5 types)
39
-
40
- | Type | Description | Constraints | JSON Schema Export |
41
- |------|-------------|-------------|-------------------|
42
- | `number` | General number | -- | `{ "type": "number" }` |
43
- | `integer` | Whole number | -- | `{ "type": "integer" }` |
44
- | `decimal` | Decimal number | -- | `{ "type": "number" }` |
45
- | `percent` | Percentage | 0-100 | `{ "type": "number", "minimum": 0, "maximum": 100 }` |
46
- | `rating` | Rating score | 1-5 | `{ "type": "integer", "minimum": 1, "maximum": 5 }` |
47
-
48
- ### 2.3 Primitives (3 types)
49
-
50
- | Type | Storage Format | JSON Schema Export |
51
- |------|---------------|-------------------|
52
- | `boolean` | `true` / `false` | `{ "type": "boolean" }` |
53
- | `date` | `"YYYY-MM-DD"` string | `{ "type": "string", "format": "date" }` |
54
- | `datetime` | ISO 8601 string | `{ "type": "string", "format": "date-time" }` |
55
-
56
- ### 2.4 Media (3 types)
57
-
58
- | Type | Storage | Description |
59
- |------|---------|-------------|
60
- | `image` | Relative path (string) | Image file reference |
61
- | `video` | Relative path (string) | Video file reference |
62
- | `file` | Relative path (string) | Generic file reference |
63
-
64
- In v1, media fields store URL/path strings only. Upload and processing are out of scope.
65
-
66
- ### 2.5 Relations (2 types)
67
-
68
- | Type | Cardinality | Storage |
69
- |------|-------------|---------|
70
- | `relation` | One-to-one | `"entry-id"` (string) |
71
- | `relations` | One-to-many | `["id-1", "id-2"]` (string array) |
72
-
73
- ### 2.6 Structural (3 types)
74
-
75
- | Type | Description | Requires |
76
- |------|-------------|----------|
77
- | `select` | Fixed options, pick one | `options` property |
78
- | `array` | Ordered list of items | `items` property |
79
- | `object` | Nested key-value structure | `fields` property |
80
-
81
- ---
82
-
83
- ## 3. Field Definition
84
-
85
- A field definition describes one field in a model. Include only the properties that apply -- omit all defaults.
86
-
87
- ```json
88
- {
89
- "type": "string",
90
- "required": true,
91
- "min": 3,
92
- "max": 100,
93
- "unique": true,
94
- "description": "Page title"
95
- }
96
- ```
97
-
98
- ### 3.1 Field Properties
99
-
100
- | Property | Applicable Types | Description |
101
- |----------|-----------------|-------------|
102
- | `type` | ALL | **Required.** One of the 27 types. |
103
- | `required` | ALL | Mark field as mandatory. Default: `false`. Omit if `false`. |
104
- | `unique` | `string`, `email`, `slug`, `integer` | Enforce uniqueness within model. Default: `false`. Omit if `false`. |
105
- | `default` | ALL | Default value. Omit if `null`. |
106
- | `min` | string/text: char count; numbers: value; array: element count | Minimum constraint. |
107
- | `max` | Same as `min` | Maximum constraint. |
108
- | `pattern` | `string`, `text`, `code` | Regex validation pattern. |
109
- | `options` | `select` ONLY | Fixed choices: `["draft", "published", "archived"]` |
110
- | `model` | `relation`, `relations` ONLY | Target model ID. String or string array for polymorphic. |
111
- | `items` | `array` ONLY | Element type: `"string"` or `{ "type": "object", "fields": {...} }` |
112
- | `fields` | `object` ONLY | Nested field definitions. |
113
- | `accept` | `image`, `video`, `file` | Allowed MIME types: `"image/png,image/jpeg"` |
114
- | `maxSize` | `image`, `video`, `file` | Maximum file size in bytes. |
115
- | `description` | ALL | Human-readable hint (shown in Studio UI tooltip, used as agent context). |
116
-
117
- ### 3.2 Omission Rules
118
-
119
- These rules produce minimal, clean schema definitions:
120
-
121
- - If `required` is `false` (the default), do NOT include it.
122
- - If `unique` is `false` (the default), do NOT include it.
123
- - If `default` is `null`, do NOT include it.
124
- - Only include properties that add information. Fewer properties = fewer tokens = faster agent processing.
125
-
126
- ---
127
-
128
- ## 4. Model Definition
129
-
130
- Model definitions live at `.contentrain/models/{model-id}.json`. One file per model.
131
-
132
- ```json
133
- {
134
- "id": "blog-post",
135
- "name": "Blog Post",
136
- "kind": "document",
137
- "domain": "blog",
138
- "i18n": true,
139
- "description": "Blog articles with markdown body",
140
- "fields": {
141
- "title": { "type": "string", "required": true, "max": 120 },
142
- "slug": { "type": "slug", "required": true, "unique": true },
143
- "author": { "type": "relation", "model": "team-members", "required": true },
144
- "tags": { "type": "array", "items": "string" }
145
- }
146
- }
147
- ```
148
-
149
- ### 4.1 Model Properties
150
-
151
- | Property | Type | Required | Description |
152
- |----------|------|----------|-------------|
153
- | `id` | `string` | Yes | Unique identifier, **kebab-case**. |
154
- | `name` | `string` | Yes | Human-readable display name. |
155
- | `kind` | `string` | Yes | One of: `singleton`, `collection`, `document`, `dictionary`. |
156
- | `domain` | `string` | Yes | Organizational group (maps to content subdirectory). |
157
- | `i18n` | `boolean` | Yes | Whether the model supports multiple locales. |
158
- | `description` | `string` | No | Model description for documentation and agent context. |
159
- | `fields` | `object` | Yes (except dictionary) | Field definitions. Dictionary kind has NO fields. |
160
- | `content_path` | `string` | No | Framework-relative path for content files (e.g., `"content/blog"`, `"locales"`). When set, content is written here instead of `.contentrain/content/`. |
161
- | `locale_strategy` | `string` | No | How locale is encoded in file names: `"file"` (default), `"suffix"`, `"directory"`, `"none"`. |
162
-
163
- ### 4.2 System Fields
164
-
165
- Do NOT define these in the schema. The platform manages them automatically:
166
-
167
- | Field | Kind | Source |
168
- |-------|------|--------|
169
- | `id` | Collection | 12-char hex, used as object-map key |
170
- | `slug` | Document | Directory name in content path |
171
- | `createdAt` / `updatedAt` | All | Derived from Git commit history (not stored) |
172
- | `status`, `source`, `updated_by`, `approved_by` | All | Stored in `.contentrain/meta/` |
173
-
174
- ---
175
-
176
- ### 4.3 Locale Strategy Rules
177
-
178
- The `locale_strategy` property controls how locale is encoded in file paths:
179
-
180
- | Strategy | i18n:true JSON path | i18n:true Document path | i18n:false path |
181
- |----------|---------------------|------------------------|-----------------|
182
- | `file` (default) | `{dir}/{locale}.json` | `{dir}/{slug}/{locale}.md` | `{dir}/data.json` |
183
- | `suffix` | `{dir}/{model}.{locale}.json` | `{dir}/{slug}.{locale}.md` | `{dir}/data.json` |
184
- | `directory` | `{dir}/{locale}/{model}.json` | `{dir}/{locale}/{slug}.md` | `{dir}/data.json` |
185
- | `none` | **INVALID** (requires i18n:false) | **INVALID** | `{dir}/{model}.json` or `{dir}/{slug}.md` |
186
-
187
- - `locale_strategy: "none"` requires `i18n: false`. The "none" strategy stores a single file without locale encoding.
188
- - When `content_path` is set, `{dir}` is the content_path. Otherwise `{dir}` is `.contentrain/content/{domain}/{model-id}`.
189
-
190
- ---
191
-
192
- ## 5. The Four Model Kinds
193
-
194
- ### 5.1 Singleton
195
-
196
- **One instance per locale.** Use for page sections, site config, navigation.
197
-
198
- | Aspect | Detail |
199
- |--------|--------|
200
- | Storage | JSON object (one file per locale) |
201
- | File path | `content/{domain}/{model-id}/{locale}.json` |
202
- | `i18n: false` path | `content/{domain}/{model-id}/data.json` |
203
- | ID management | None (single instance) |
204
- | Relation target | Cannot be referenced by relations |
205
-
206
- ```json
207
- {
208
- "cta": "Get Started",
209
- "title": "Build faster"
210
- }
211
- ```
212
-
213
- ### 5.2 Collection
214
-
215
- **Multiple entries.** Use for team members, products, FAQs, categories.
216
-
217
- | Aspect | Detail |
218
- |--------|--------|
219
- | Storage | JSON object-map (entry ID as key, sorted lexicographically) |
220
- | File path | `content/{domain}/{model-id}/{locale}.json` |
221
- | Tool output | Array with `id` injected |
222
- | ID management | Auto-generated 12-char hex |
223
- | Relation target | Referenced by entry `id` |
224
-
225
- ```json
226
- {
227
- "a1b2c3d4e5f6": { "name": "Ahmet", "role": "CEO" },
228
- "f6e5d4c3b2a1": { "name": "Jane", "role": "CTO" }
229
- }
230
- ```
231
-
232
- ### 5.3 Document
233
-
234
- **Markdown with frontmatter.** Use for blog posts, documentation, changelogs.
235
-
236
- | Aspect | Detail |
237
- |--------|--------|
238
- | Storage | `.md` file with YAML frontmatter |
239
- | File path | `content/{domain}/{model-id}/{slug}/{locale}.md` |
240
- | `i18n: false` path | `content/{domain}/{model-id}/{slug}.md` |
241
- | ID management | `slug` field (URL-safe, unique) |
242
- | Relation target | Referenced by `slug` |
243
-
244
- ```markdown
245
- ---
246
- title: Getting Started
247
- slug: getting-started
248
- author: a1b2c3d4e5f6
249
- ---
250
- # Getting Started with Contentrain
251
- ```
252
-
253
- ### 5.4 Dictionary
254
-
255
- **Flat key-value pairs.** Use for error messages, UI strings, translations.
256
-
257
- | Aspect | Detail |
258
- |--------|--------|
259
- | Storage | Flat JSON object (key-value, all values are strings) |
260
- | File path | `content/{domain}/{model-id}/{locale}.json` |
261
- | Fields definition | None -- dictionary models have NO `fields` property |
262
- | ID management | Key is identity |
263
- | Relation target | Cannot be referenced by relations |
264
-
265
- ```json
266
- {
267
- "auth.expired": "Session expired",
268
- "auth.failed": "Authentication failed"
269
- }
270
- ```
271
-
272
- ---
273
-
274
- ## 6. Relation Rules
275
-
276
- ### 6.1 Basic Relations
277
-
278
- ```json
279
- "author": { "type": "relation", "model": "team-members", "required": true }
280
- "categories": { "type": "relations", "model": "categories" }
281
- ```
282
-
283
- - `relation` (1:1): Value is a single string -- an entry ID or slug.
284
- - `relations` (1:many): Value is a string array of entry IDs or slugs.
285
-
286
- ### 6.2 Target Model Restrictions
287
-
288
- | Target Kind | Reference Key | Can Be Target? |
289
- |-------------|--------------|----------------|
290
- | Collection | Entry `id` | Yes |
291
- | Document | Document `slug` | Yes |
292
- | Singleton | -- | No |
293
- | Dictionary | -- | No |
294
-
295
- ### 6.3 Polymorphic Relations
296
-
297
- When a field can reference multiple model types:
298
-
299
- ```json
300
- "target": { "type": "relation", "model": ["blog-post", "page"] }
301
- ```
302
-
303
- Storage for polymorphic references uses a compound value:
304
-
305
- ```json
306
- { "model": "blog-post", "ref": "getting-started" }
307
- ```
308
-
309
- ### 6.4 Self-Referencing
310
-
311
- Models can reference themselves (e.g., hierarchical categories):
312
-
313
- ```json
314
- "parent": { "type": "relation", "model": "categories" }
315
- ```
316
-
317
- ### 6.5 Resolution Rules
318
-
319
- - Relations are resolved **1 level deep** -- no recursive resolution.
320
- - **Unresolved IDs** (target entry does not exist) are kept as raw strings (graceful degradation).
321
- - **Cascade deletion does not exist.** Deleting a referenced entry produces a broken relation warning.
322
- - **Array order is preserved** for `relations` type.
323
- - **IDs/slugs are locale-agnostic.** The same reference works across all locales.
324
-
325
- ### 6.6 Validation
326
-
327
- - Referenced ID/slug MUST exist in the target model.
328
- - `model` property MUST reference an existing model ID.
329
- - Referential integrity is checked by `contentrain_validate`.
330
- - Deleting a model that is referenced by other models is BLOCKED.
331
-
332
- ---
333
-
334
- ## 7. Nesting Limits
335
-
336
- ### 7.1 Object Type
337
-
338
- ```json
339
- "address": {
340
- "type": "object",
341
- "fields": {
342
- "city": { "type": "string", "required": true },
343
- "street": { "type": "string", "required": true },
344
- "zip": { "type": "string" }
345
- }
346
- }
347
- ```
348
-
349
- ### 7.2 Array of Objects
350
-
351
- ```json
352
- "variants": {
353
- "type": "array",
354
- "items": {
355
- "type": "object",
356
- "fields": {
357
- "color": { "type": "color", "required": true },
358
- "price": { "type": "decimal", "required": true },
359
- "size": { "type": "select", "options": ["S", "M", "L"] }
360
- }
361
- },
362
- "max": 50
363
- }
364
- ```
365
-
366
- ### 7.3 Depth Limit
367
-
368
- **Maximum nesting depth: 2 levels.** An object inside an object is allowed. An object inside an object inside an object is NOT.
369
-
370
- - Prefer flat types over deeply nested structures.
371
- - Use relations to model complex data relationships instead of nesting.
372
- - If you need deeper nesting, create a separate model and use a `relation` field.
373
-
374
- ---
375
-
376
- ## 8. Naming Conventions
377
-
378
- | Element | Convention | Example |
379
- |---------|-----------|---------|
380
- | Model ID | kebab-case | `blog-post`, `team-members` |
381
- | Field key | snake_case | `hero_image`, `cta_url` |
382
- | Domain | lowercase, single word or kebab-case | `blog`, `marketing`, `system` |
383
- | Dictionary key | dot-notation | `auth.failed`, `validation.required` |
384
- | Slug | lowercase, kebab-case | `getting-started` |
385
- | Locale | ISO 639-1 | `en`, `tr`, `de` |