@contentrain/rules 0.7.0 → 0.7.2
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/dist/index.cjs +11 -0
- package/dist/index.d.cts +9 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +9 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +11 -0
- package/dist/index.mjs.map +1 -1
- package/essential/contentrain-essentials.md +4 -1
- package/package.json +2 -2
- package/shared/mcp-usage.md +6 -4
- package/shared/schema-rules.md +2 -0
- package/shared/workflow-rules.md +13 -0
package/dist/index.cjs
CHANGED
|
@@ -80,6 +80,16 @@ const MODEL_PROPERTIES = [
|
|
|
80
80
|
{
|
|
81
81
|
name: "fields",
|
|
82
82
|
required: true
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "form",
|
|
86
|
+
required: false,
|
|
87
|
+
runtimeOwned: true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "comments",
|
|
91
|
+
required: false,
|
|
92
|
+
runtimeOwned: true
|
|
83
93
|
}
|
|
84
94
|
];
|
|
85
95
|
const MCP_TOOLS = [
|
|
@@ -100,6 +110,7 @@ const MCP_TOOLS = [
|
|
|
100
110
|
"contentrain_validate",
|
|
101
111
|
"contentrain_submit",
|
|
102
112
|
"contentrain_merge",
|
|
113
|
+
"contentrain_reconcile",
|
|
103
114
|
"contentrain_branch_list",
|
|
104
115
|
"contentrain_branch_delete",
|
|
105
116
|
"contentrain_bulk",
|
package/dist/index.d.cts
CHANGED
|
@@ -38,9 +38,17 @@ declare const MODEL_PROPERTIES: readonly [{
|
|
|
38
38
|
}, {
|
|
39
39
|
readonly name: "fields";
|
|
40
40
|
readonly required: true;
|
|
41
|
+
}, {
|
|
42
|
+
readonly name: "form";
|
|
43
|
+
readonly required: false;
|
|
44
|
+
readonly runtimeOwned: true;
|
|
45
|
+
}, {
|
|
46
|
+
readonly name: "comments";
|
|
47
|
+
readonly required: false;
|
|
48
|
+
readonly runtimeOwned: true;
|
|
41
49
|
}];
|
|
42
50
|
type ModelProperty = (typeof MODEL_PROPERTIES)[number]['name'];
|
|
43
|
-
declare const MCP_TOOLS: readonly ["contentrain_status", "contentrain_describe", "contentrain_describe_format", "contentrain_init", "contentrain_scaffold", "contentrain_model_save", "contentrain_model_delete", "contentrain_content_save", "contentrain_content_delete", "contentrain_content_list", "contentrain_vocabulary_save", "contentrain_vocabulary_delete", "contentrain_scan", "contentrain_apply", "contentrain_validate", "contentrain_submit", "contentrain_merge", "contentrain_branch_list", "contentrain_branch_delete", "contentrain_bulk", "contentrain_doctor", "contentrain_media_list", "contentrain_media_get", "contentrain_media_ingest", "contentrain_media_update", "contentrain_media_delete"];
|
|
51
|
+
declare const MCP_TOOLS: readonly ["contentrain_status", "contentrain_describe", "contentrain_describe_format", "contentrain_init", "contentrain_scaffold", "contentrain_model_save", "contentrain_model_delete", "contentrain_content_save", "contentrain_content_delete", "contentrain_content_list", "contentrain_vocabulary_save", "contentrain_vocabulary_delete", "contentrain_scan", "contentrain_apply", "contentrain_validate", "contentrain_submit", "contentrain_merge", "contentrain_reconcile", "contentrain_branch_list", "contentrain_branch_delete", "contentrain_bulk", "contentrain_doctor", "contentrain_media_list", "contentrain_media_get", "contentrain_media_ingest", "contentrain_media_update", "contentrain_media_delete"];
|
|
44
52
|
type McpTool = (typeof MCP_TOOLS)[number];
|
|
45
53
|
declare const ESSENTIAL_RULES_FILE: "essential/contentrain-essentials.md";
|
|
46
54
|
declare const STACKS: readonly ["nuxt", "next", "astro", "sveltekit", "react", "node"];
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;AAQA;;;;cAAa,WAAA;AAAA,KAUD,SAAA,WAAoB,WAAA;AAAA,cAInB,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,cASnB,gBAAA;EAAA
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;AAQA;;;;cAAa,WAAA;AAAA,KAUD,SAAA,WAAoB,WAAA;AAAA,cAInB,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,cASnB,gBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmBD,aAAA,WAAwB,gBAAA;AAAA,cAIvB,SAAA;AAAA,KAkBD,OAAA,WAAkB,SAAA;AAAA,cAIjB,oBAAA;AAAA,cAIA,MAAA;AAAA,KACD,KAAA,WAAgB,MAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -38,9 +38,17 @@ declare const MODEL_PROPERTIES: readonly [{
|
|
|
38
38
|
}, {
|
|
39
39
|
readonly name: "fields";
|
|
40
40
|
readonly required: true;
|
|
41
|
+
}, {
|
|
42
|
+
readonly name: "form";
|
|
43
|
+
readonly required: false;
|
|
44
|
+
readonly runtimeOwned: true;
|
|
45
|
+
}, {
|
|
46
|
+
readonly name: "comments";
|
|
47
|
+
readonly required: false;
|
|
48
|
+
readonly runtimeOwned: true;
|
|
41
49
|
}];
|
|
42
50
|
type ModelProperty = (typeof MODEL_PROPERTIES)[number]['name'];
|
|
43
|
-
declare const MCP_TOOLS: readonly ["contentrain_status", "contentrain_describe", "contentrain_describe_format", "contentrain_init", "contentrain_scaffold", "contentrain_model_save", "contentrain_model_delete", "contentrain_content_save", "contentrain_content_delete", "contentrain_content_list", "contentrain_vocabulary_save", "contentrain_vocabulary_delete", "contentrain_scan", "contentrain_apply", "contentrain_validate", "contentrain_submit", "contentrain_merge", "contentrain_branch_list", "contentrain_branch_delete", "contentrain_bulk", "contentrain_doctor", "contentrain_media_list", "contentrain_media_get", "contentrain_media_ingest", "contentrain_media_update", "contentrain_media_delete"];
|
|
51
|
+
declare const MCP_TOOLS: readonly ["contentrain_status", "contentrain_describe", "contentrain_describe_format", "contentrain_init", "contentrain_scaffold", "contentrain_model_save", "contentrain_model_delete", "contentrain_content_save", "contentrain_content_delete", "contentrain_content_list", "contentrain_vocabulary_save", "contentrain_vocabulary_delete", "contentrain_scan", "contentrain_apply", "contentrain_validate", "contentrain_submit", "contentrain_merge", "contentrain_reconcile", "contentrain_branch_list", "contentrain_branch_delete", "contentrain_bulk", "contentrain_doctor", "contentrain_media_list", "contentrain_media_get", "contentrain_media_ingest", "contentrain_media_update", "contentrain_media_delete"];
|
|
44
52
|
type McpTool = (typeof MCP_TOOLS)[number];
|
|
45
53
|
declare const ESSENTIAL_RULES_FILE: "essential/contentrain-essentials.md";
|
|
46
54
|
declare const STACKS: readonly ["nuxt", "next", "astro", "sveltekit", "react", "node"];
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;AAQA;;;;cAAa,WAAA;AAAA,KAUD,SAAA,WAAoB,WAAA;AAAA,cAInB,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,cASnB,gBAAA;EAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;AAQA;;;;cAAa,WAAA;AAAA,KAUD,SAAA,WAAoB,WAAA;AAAA,cAInB,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,cASnB,gBAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmBD,aAAA,WAAwB,gBAAA;AAAA,cAIvB,SAAA;AAAA,KAkBD,OAAA,WAAkB,SAAA;AAAA,cAIjB,oBAAA;AAAA,cAIA,MAAA;AAAA,KACD,KAAA,WAAgB,MAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -79,6 +79,16 @@ const MODEL_PROPERTIES = [
|
|
|
79
79
|
{
|
|
80
80
|
name: "fields",
|
|
81
81
|
required: true
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "form",
|
|
85
|
+
required: false,
|
|
86
|
+
runtimeOwned: true
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "comments",
|
|
90
|
+
required: false,
|
|
91
|
+
runtimeOwned: true
|
|
82
92
|
}
|
|
83
93
|
];
|
|
84
94
|
const MCP_TOOLS = [
|
|
@@ -99,6 +109,7 @@ const MCP_TOOLS = [
|
|
|
99
109
|
"contentrain_validate",
|
|
100
110
|
"contentrain_submit",
|
|
101
111
|
"contentrain_merge",
|
|
112
|
+
"contentrain_reconcile",
|
|
102
113
|
"contentrain_branch_list",
|
|
103
114
|
"contentrain_branch_delete",
|
|
104
115
|
"contentrain_bulk",
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @contentrain/rules — AI agent rules for Contentrain\n *\n * Quality standards, architecture conventions, and IDE integration.\n */\n\n// ─── Field Types (27 flat types) ───\n\nexport const FIELD_TYPES = [\n 'string', 'text', 'email', 'url', 'slug', 'color', 'phone', 'code', 'icon',\n 'markdown', 'richtext',\n 'number', 'integer', 'decimal', 'percent', 'rating',\n 'boolean', 'date', 'datetime',\n 'image', 'video', 'file',\n 'relation', 'relations',\n 'select', 'array', 'object',\n] as const\n\nexport type FieldType = (typeof FIELD_TYPES)[number]\n\n// ─── Model Kinds ───\n\nexport const MODEL_KINDS = ['singleton', 'collection', 'document', 'dictionary'] as const\nexport type ModelKind = (typeof MODEL_KINDS)[number]\n\n// ─── Model Definition Properties ───\n//\n// The parity anchor for the model schema. Docs describing `ModelDefinition` drift\n// silently: nothing failed when a property landed in @contentrain/types and never\n// reached schema-rules.md. The tests in tests/mcp-parity.test.ts assert this list\n// against MODEL_FIELD_ORDER in @contentrain/mcp and against the prose tables.\n\nexport const MODEL_PROPERTIES = [\n { name: 'id', required: true },\n { name: 'name', required: true },\n { name: 'kind', required: true },\n { name: 'domain', required: true },\n { name: 'i18n', required: true },\n { name: 'title_field', required: true },\n { name: 'description', required: false },\n { name: 'content_path', required: false },\n { name: 'locale_strategy', required: false },\n // `required` here means \"documented as required\", not the TS optionality.\n // `fields` is `fields?` on the interface but every non-dictionary model needs\n // it — a collection with no fields cannot even name a title_field. The\n // per-kind nuance lives in the docs cell (\"Yes (except dictionary)\").\n { name: 'fields', required: true },\n] as const\n\nexport type ModelProperty = (typeof MODEL_PROPERTIES)[number]['name']\n\n// ─── MCP Tools (
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @contentrain/rules — AI agent rules for Contentrain\n *\n * Quality standards, architecture conventions, and IDE integration.\n */\n\n// ─── Field Types (27 flat types) ───\n\nexport const FIELD_TYPES = [\n 'string', 'text', 'email', 'url', 'slug', 'color', 'phone', 'code', 'icon',\n 'markdown', 'richtext',\n 'number', 'integer', 'decimal', 'percent', 'rating',\n 'boolean', 'date', 'datetime',\n 'image', 'video', 'file',\n 'relation', 'relations',\n 'select', 'array', 'object',\n] as const\n\nexport type FieldType = (typeof FIELD_TYPES)[number]\n\n// ─── Model Kinds ───\n\nexport const MODEL_KINDS = ['singleton', 'collection', 'document', 'dictionary'] as const\nexport type ModelKind = (typeof MODEL_KINDS)[number]\n\n// ─── Model Definition Properties ───\n//\n// The parity anchor for the model schema. Docs describing `ModelDefinition` drift\n// silently: nothing failed when a property landed in @contentrain/types and never\n// reached schema-rules.md. The tests in tests/mcp-parity.test.ts assert this list\n// against MODEL_FIELD_ORDER in @contentrain/mcp and against the prose tables.\n\nexport const MODEL_PROPERTIES = [\n { name: 'id', required: true },\n { name: 'name', required: true },\n { name: 'kind', required: true },\n { name: 'domain', required: true },\n { name: 'i18n', required: true },\n { name: 'title_field', required: true },\n { name: 'description', required: false },\n { name: 'content_path', required: false },\n { name: 'locale_strategy', required: false },\n // `required` here means \"documented as required\", not the TS optionality.\n // `fields` is `fields?` on the interface but every non-dictionary model needs\n // it — a collection with no fields cannot even name a title_field. The\n // per-kind nuance lives in the docs cell (\"Yes (except dictionary)\").\n { name: 'fields', required: true },\n { name: 'form', required: false, runtimeOwned: true },\n { name: 'comments', required: false, runtimeOwned: true },\n] as const\n\nexport type ModelProperty = (typeof MODEL_PROPERTIES)[number]['name']\n\n// ─── MCP Tools (27 tools: 22 core + 5 media) ───\n\nexport const MCP_TOOLS = [\n 'contentrain_status', 'contentrain_describe', 'contentrain_describe_format',\n 'contentrain_init', 'contentrain_scaffold',\n 'contentrain_model_save', 'contentrain_model_delete',\n 'contentrain_content_save', 'contentrain_content_delete', 'contentrain_content_list',\n 'contentrain_vocabulary_save', 'contentrain_vocabulary_delete',\n 'contentrain_scan', 'contentrain_apply',\n 'contentrain_validate', 'contentrain_submit',\n 'contentrain_merge', 'contentrain_reconcile',\n 'contentrain_branch_list', 'contentrain_branch_delete',\n 'contentrain_bulk',\n 'contentrain_doctor',\n // Media tools — registered only when the provider exposes a media stack\n // (Studio MCP Cloud); absent from local stdio servers.\n 'contentrain_media_list', 'contentrain_media_get', 'contentrain_media_ingest',\n 'contentrain_media_update', 'contentrain_media_delete',\n] as const\n\nexport type McpTool = (typeof MCP_TOOLS)[number]\n\n// ─── Essential Rules ───\n\nexport const ESSENTIAL_RULES_FILE = 'essential/contentrain-essentials.md' as const\n\n// ─── Stacks ───\n\nexport const STACKS = ['nuxt', 'next', 'astro', 'sveltekit', 'react', 'node'] as const\nexport type Stack = (typeof STACKS)[number]\n"],"mappings":";;;;;;AAQA,MAAa,cAAc;CACzB;CAAU;CAAQ;CAAS;CAAO;CAAQ;CAAS;CAAS;CAAQ;CACpE;CAAY;CACZ;CAAU;CAAW;CAAW;CAAW;CAC3C;CAAW;CAAQ;CACnB;CAAS;CAAS;CAClB;CAAY;CACZ;CAAU;CAAS;CACpB;AAMD,MAAa,cAAc;CAAC;CAAa;CAAc;CAAY;CAAa;AAUhF,MAAa,mBAAmB;CAC9B;EAAE,MAAM;EAAM,UAAU;EAAM;CAC9B;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAU,UAAU;EAAM;CAClC;EAAE,MAAM;EAAQ,UAAU;EAAM;CAChC;EAAE,MAAM;EAAe,UAAU;EAAM;CACvC;EAAE,MAAM;EAAe,UAAU;EAAO;CACxC;EAAE,MAAM;EAAgB,UAAU;EAAO;CACzC;EAAE,MAAM;EAAmB,UAAU;EAAO;CAK5C;EAAE,MAAM;EAAU,UAAU;EAAM;CAClC;EAAE,MAAM;EAAQ,UAAU;EAAO,cAAc;EAAM;CACrD;EAAE,MAAM;EAAY,UAAU;EAAO,cAAc;EAAM;CAC1D;AAMD,MAAa,YAAY;CACvB;CAAsB;CAAwB;CAC9C;CAAoB;CACpB;CAA0B;CAC1B;CAA4B;CAA8B;CAC1D;CAA+B;CAC/B;CAAoB;CACpB;CAAwB;CACxB;CAAqB;CACrB;CAA2B;CAC3B;CACA;CAGA;CAA0B;CAAyB;CACnD;CAA4B;CAC7B;AAMD,MAAa,uBAAuB;AAIpC,MAAa,SAAS;CAAC;CAAQ;CAAQ;CAAS;CAAa;CAAS;CAAO"}
|
|
@@ -33,7 +33,7 @@ MCP is **deterministic infrastructure**. The agent (you) is the **intelligence l
|
|
|
33
33
|
- **NEVER** edit `.contentrain/context.json` — MCP writes, agents read
|
|
34
34
|
- **NEVER** include system fields in content data: `id`, `status`, `source`, `updated_by`, `updated_at`, `createdAt`, `updatedAt`
|
|
35
35
|
- **ALWAYS** use MCP tools — do not write `.contentrain/` JSON files directly
|
|
36
|
-
- **Publishing is yours, not MCP's.** `contentrain_content_save` preserves an existing entry's `status`; only a brand-new entry starts at `draft`. To change publish state, call `contentrain_bulk update_status` deliberately — and note that on the CDN
|
|
36
|
+
- **Publishing is yours, not MCP's.** `contentrain_content_save` preserves an existing entry's `status`; only a brand-new entry starts at `draft`. To change publish state, call `contentrain_bulk update_status` deliberately (`entry_ids` for collections, `slugs` for documents, neither for singletons/dictionaries) — and note that on the CDN an entry is delivered only when its status is `published`. `publish_at`/`expire_at` on `content_save` never change status: they gate delivery of a published entry, live in meta only, and `null` clears them.
|
|
37
37
|
- **Schema validity is MCP's.** `contentrain_content_save` validates before it writes and **refuses** the save on any error — nothing is committed, so there is no branch to clean up. Fix the values and call it again. Warnings (email/url/colour/phone heuristics, `accept` extension checks) do not block; they come back in the response.
|
|
38
38
|
- **Every model declares its `title_field`.** Required on create, and it must stay correct when you rename or drop a field. It names the field shown as an entry's title and must be a `string`/`text`/`slug`/`email`/`url`/`code`/`markdown`/`richtext` field on that model; dictionaries have no fields, so they use the reserved value `"key"`. Pick what a human reads to tell entries apart — the headline, the name, the question — never the slug, icon or a relation ID. Consumers used to guess this and titled rows `i-lucide-bot`.
|
|
39
39
|
- **A declared constraint is a real constraint.** `model_save` rejects a property declared where it cannot apply (`options` on a non-select, `accept` on a non-media field, `unique` on a singleton) rather than accepting it and doing nothing. The one exception is `maxSize`, which MCP cannot enforce — your media provider does, at ingest, and `model_save` says so.
|
|
@@ -59,6 +59,7 @@ MCP is **deterministic infrastructure**. The agent (you) is the **intelligence l
|
|
|
59
59
|
| `contentrain_validate` | Validate content against schemas |
|
|
60
60
|
| `contentrain_submit` | Push branches to remote |
|
|
61
61
|
| `contentrain_merge` | Merge a review-mode branch into contentrain locally (by exact branch or model) |
|
|
62
|
+
| `contentrain_reconcile` | Content-aware three-way merge of a diverged contentrain ↔ base pair (dry_run first) |
|
|
62
63
|
| `contentrain_branch_list` | List pending `cr/*` branches + merge status |
|
|
63
64
|
| `contentrain_branch_delete` | Delete a stale/failed `cr/*` branch (the contentrain branch is protected) |
|
|
64
65
|
| `contentrain_bulk` | Batch operations (copy_locale/update_status/delete_entries) |
|
|
@@ -92,6 +93,8 @@ Media tools appear only when the provider exposes a media stack; discover paths
|
|
|
92
93
|
- The `contentrain` branch is protected from deletion
|
|
93
94
|
- context.json is never committed on feature branches — it is regenerated on the `contentrain` branch after merge
|
|
94
95
|
- Never create branches manually, never commit directly to main or the `contentrain` branch
|
|
96
|
+
- The one legitimate exception: a dual-domain migration (package bump + `.contentrain/` schema change) lands on the base branch via PR — afterwards `contentrain` and the base have diverged, and writes report `base_advance: "blocked_diverged"` (the content is safe on `contentrain`; only the fast-forward is pending)
|
|
97
|
+
- Divergence is reconciled with `contentrain_reconcile`: dry_run first, review the plan and conflicts, collect resolutions from the editor for anything two-sided, then execute — it lands as a two-parent merge commit and the fast-forward advance works again
|
|
95
98
|
- 50+ active `cr/*` branches = warning, 80+ = blocked
|
|
96
99
|
|
|
97
100
|
## CLI Serve — Review & Approval
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/rules",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "AI agent rules for Contentrain — quality standards, architecture conventions, IDE integration",
|
|
6
6
|
"type": "module",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"tsdown": "^0.21.0",
|
|
56
56
|
"typescript": "^5.7.0",
|
|
57
57
|
"vitest": "^3.0.0",
|
|
58
|
-
"@contentrain/mcp": "3.
|
|
58
|
+
"@contentrain/mcp": "3.3.2"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown src/index.ts --format esm,cjs --dts",
|
package/shared/mcp-usage.md
CHANGED
|
@@ -166,25 +166,26 @@ Each entry in the `entries` array has this shape:
|
|
|
166
166
|
|
|
167
167
|
| Tool | Purpose | Parameters |
|
|
168
168
|
|------|---------|------------|
|
|
169
|
-
| `contentrain_bulk` | Run git-backed batch operations on existing content entries | `operation`, `model`, `source_locale?`, `target_locale?`, `entry_ids?`, `status?`, `confirm?` |
|
|
169
|
+
| `contentrain_bulk` | Run git-backed batch operations on existing content entries | `operation`, `model`, `source_locale?`, `target_locale?`, `entry_ids?`, `slugs?`, `locale?`, `status?`, `confirm?` |
|
|
170
170
|
|
|
171
171
|
#### contentrain_bulk operations
|
|
172
172
|
|
|
173
173
|
- `copy_locale`: copy one locale to another for an i18n-enabled `collection`, `singleton`, or `dictionary` model. Requires `source_locale` and `target_locale`.
|
|
174
|
-
- `update_status`: update entry metadata status for a `collection`
|
|
174
|
+
- `update_status`: update entry metadata status. Requires `status`, plus the list that matches the model's meta layout: `entry_ids` for a `collection`, `slugs` for a `document`, and neither for a `singleton` or `dictionary` (one meta record per locale). Pass `locale` to scope the change; otherwise every supported locale is rewritten.
|
|
175
175
|
- `delete_entries`: delete multiple entries from a `collection` model. Requires `entry_ids` and `confirm: true`.
|
|
176
176
|
|
|
177
177
|
#### contentrain_bulk rules
|
|
178
178
|
|
|
179
179
|
- ALWAYS verify the target model with `contentrain_describe` or `contentrain_status` before running a bulk operation.
|
|
180
180
|
- `copy_locale` MUST NOT be used on non-i18n models.
|
|
181
|
-
- `update_status`
|
|
181
|
+
- `update_status` is the only way to change publish state. `publish_at` on `contentrain_content_save` does not publish — it gates delivery of an entry that is already `published`.
|
|
182
|
+
- `delete_entries` is a collection-only operation.
|
|
182
183
|
- Bulk operations create branches and commits like other write tools; validate afterward when content shape may have changed.
|
|
183
184
|
|
|
184
185
|
#### contentrain_validate parameters
|
|
185
186
|
|
|
186
187
|
- `model`: validate a specific model only (omit for all models).
|
|
187
|
-
- `fix`: auto-fix structural issues like canonical sort, orphan meta, missing locale files,
|
|
188
|
+
- `fix`: auto-fix structural issues like canonical sort, orphan meta, missing locale files, stray non-i18n meta layout, and `publish_at`/`expire_at` copies an older `content_save` left in document frontmatter (stripped only when meta holds the same key), and backfill a missing model `title_field` (default: false). Stray-meta cleanup is deterministic: it prunes extras when the default-locale meta is authoritative, or migrates a lone stray to the default path — it never merges or downgrades a status.
|
|
188
189
|
|
|
189
190
|
#### contentrain_submit parameters
|
|
190
191
|
|
|
@@ -390,6 +391,7 @@ Creates or updates a model definition. Key parameters:
|
|
|
390
391
|
- `title_field`: **required** — the field shown as an entry's title. Must name a field declared on this model whose type is `string`, `text`, `slug`, `email`, `url`, `code`, `markdown` or `richtext`. Dictionary models use the reserved value `"key"`. Set it when creating a model, and keep it correct when renaming or removing fields.
|
|
391
392
|
- `content_path`: optional framework-relative path (e.g., `"content/blog"`, `"locales"`). When set, content is written here instead of `.contentrain/content/`.
|
|
392
393
|
- `locale_strategy`: how locale is encoded in filenames: `"file"` (default), `"suffix"`, `"directory"`, `"none"`.
|
|
394
|
+
- `contentrain_model_save` preserves existing `form` configuration and preserves existing `comments` configuration. These runtime-owned blocks are not structural tool inputs; use the runtime provider's settings surface to change them.
|
|
393
395
|
- `locale_strategy: "none"` requires `i18n: false`. All other strategies work with both `i18n: true` and `i18n: false`.
|
|
394
396
|
|
|
395
397
|
### 5.5 contentrain_model_delete
|
package/shared/schema-rules.md
CHANGED
|
@@ -161,6 +161,8 @@ Model definitions live at `.contentrain/models/{model-id}.json`. One file per mo
|
|
|
161
161
|
| `fields` | `object` | Yes (except dictionary) | Field definitions. Dictionary kind has NO fields. |
|
|
162
162
|
| `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/`. |
|
|
163
163
|
| `locale_strategy` | `string` | No | How locale is encoded in file names: `"file"` (default), `"suffix"`, `"directory"`, `"none"`. |
|
|
164
|
+
| `form` | `object` | No | Runtime-owned public form settings. Structural model edits preserve the existing block; configure through the runtime provider. |
|
|
165
|
+
| `comments` | `object` | No | Runtime-owned public comments settings, preserved across structural model edits. |
|
|
164
166
|
|
|
165
167
|
#### Choosing `title_field`
|
|
166
168
|
|
package/shared/workflow-rules.md
CHANGED
|
@@ -153,6 +153,19 @@ Contentrain's storage format is designed to minimize Git merge conflicts:
|
|
|
153
153
|
- When a conflict occurs, the agent should NOT attempt to resolve it. Inform the user and direct them to Studio.
|
|
154
154
|
- Collection object-map format with sorted keys means ~0.3% chance of conflict for two new entries in a 350-entry collection.
|
|
155
155
|
|
|
156
|
+
### 4.4 Branch Divergence and Reconcile
|
|
157
|
+
|
|
158
|
+
The `contentrain` branch normally contains the base branch, so advancing the base is always a fast-forward. The invariant breaks legitimately in exactly one case: a **dual-domain migration** — a package bump and a `.contentrain/` schema change that must ship as one atomic PR to the base branch. After it lands, the branches have **diverged**.
|
|
159
|
+
|
|
160
|
+
- Divergence is a product state, not an error. Writes still land on `contentrain` and report `base_advance: "blocked_diverged"`; nothing is lost.
|
|
161
|
+
- The exit is `contentrain_reconcile` (CLI: `npx contentrain reconcile`) — a content-aware three-way merge. Always dry-run first.
|
|
162
|
+
- Everything one side changed merges mechanically (entry-, key-, and term+locale-level). Only the same item changed differently on both sides becomes a `ConflictItem`.
|
|
163
|
+
- Conflicts are content decisions: take them to the editor/user, collect `{ id, choose }` resolutions, run again. NEVER guess a side.
|
|
164
|
+
- Conflict ids hash position AND values — a decision made against a stale preview is dropped and re-asked. This is intended, not a bug.
|
|
165
|
+
- The result is a two-parent merge commit on `contentrain`; the fast-forward advance works again afterwards.
|
|
166
|
+
|
|
167
|
+
**Dual-domain migration recipe**: (1) build the migration on an isolated branch and land it on the base branch via PR — never hand-edit `.contentrain/` on the base outside this exception; (2) immediately after the PR merges, run the reconcile (Studio does this from its health check; locally run `npx contentrain reconcile`); (3) resume normal writes.
|
|
168
|
+
|
|
156
169
|
---
|
|
157
170
|
|
|
158
171
|
## 5. Workflow States
|