@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.
- package/README.md +65 -60
- package/dist/index.cjs +65 -1
- package/dist/index.d.cts +50 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +50 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +65 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/skills/contentrain/SKILL.md +149 -0
- package/skills/contentrain/references/architecture.md +212 -0
- package/skills/contentrain/references/content-formats.md +279 -0
- package/skills/contentrain/references/i18n.md +298 -0
- package/skills/contentrain/references/mcp-pipelines.md +195 -0
- package/skills/contentrain/references/mcp-tools.md +308 -0
- package/skills/contentrain/references/model-kinds.md +330 -0
- package/skills/contentrain/references/schema-types.md +177 -0
- package/skills/contentrain/references/security.md +146 -0
- package/skills/contentrain/references/workflow.md +285 -0
- package/skills/contentrain-bulk/SKILL.md +99 -0
- package/skills/contentrain-content/SKILL.md +162 -0
- package/skills/contentrain-diff/SKILL.md +62 -0
- package/skills/contentrain-doctor/SKILL.md +62 -0
- package/skills/contentrain-generate/SKILL.md +183 -0
- package/skills/contentrain-generate/references/generated-client.md +198 -0
- package/skills/contentrain-init/SKILL.md +99 -0
- package/skills/contentrain-model/SKILL.md +141 -0
- package/skills/contentrain-normalize/SKILL.md +185 -0
- package/skills/contentrain-normalize/references/extraction.md +164 -0
- package/skills/contentrain-normalize/references/reuse.md +146 -0
- package/skills/contentrain-normalize/references/what-is-content.md +115 -0
- package/skills/contentrain-quality/SKILL.md +180 -0
- package/skills/contentrain-quality/references/accessibility.md +160 -0
- package/skills/contentrain-quality/references/content-quality.md +299 -0
- package/skills/contentrain-quality/references/media.md +170 -0
- package/skills/contentrain-quality/references/seo.md +229 -0
- package/skills/contentrain-review/SKILL.md +170 -0
- package/skills/contentrain-sdk/SKILL.md +145 -0
- package/skills/contentrain-sdk/references/bundler-config.md +135 -0
- package/skills/contentrain-serve/SKILL.md +96 -0
- package/skills/contentrain-translate/SKILL.md +180 -0
- 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
|