@contentrain/skills 0.8.0 → 0.8.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/package.json +3 -3
- package/skills/contentrain/SKILL.md +3 -2
- package/skills/contentrain/references/mcp-tools.md +32 -5
- package/skills/contentrain/references/workflow.md +13 -0
- package/skills/contentrain-bulk/SKILL.md +16 -3
- package/skills/contentrain-content/SKILL.md +1 -1
- package/skills/contentrain-model/SKILL.md +1 -1
- package/skills/contentrain-validate-fix/SKILL.md +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/skills",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "AI agent skills for Contentrain — workflow procedures, framework integration guides",
|
|
6
6
|
"type": "module",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"tsdown": "^0.21.0",
|
|
62
62
|
"typescript": "^5.7.0",
|
|
63
63
|
"vitest": "^3.0.0",
|
|
64
|
-
"@contentrain/mcp": "3.
|
|
65
|
-
"@contentrain/rules": "0.7.
|
|
64
|
+
"@contentrain/mcp": "3.1.8",
|
|
65
|
+
"@contentrain/rules": "0.7.1"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsdown src/index.ts --format esm,cjs --dts",
|
|
@@ -16,7 +16,7 @@ Contentrain consists of 6 packages that work together:
|
|
|
16
16
|
|
|
17
17
|
| Package | Role | How agent uses it |
|
|
18
18
|
|---|---|---|
|
|
19
|
-
| @contentrain/mcp |
|
|
19
|
+
| @contentrain/mcp | 22 MCP tools (scan, apply, validate, merge, reconcile, doctor...) | MCP tool calls |
|
|
20
20
|
| contentrain (CLI) | init, serve, generate, doctor, diff, status | Shell commands |
|
|
21
21
|
| @contentrain/types | Shared TypeScript contracts | Type safety |
|
|
22
22
|
| @contentrain/query | Generated SDK client (Prisma-pattern) | `import from '#contentrain'` |
|
|
@@ -121,8 +121,9 @@ slug: getting-started
|
|
|
121
121
|
| `contentrain_scan` | Scan project for structure or content candidates |
|
|
122
122
|
| `contentrain_apply` | Apply normalize operation (extract or reuse) |
|
|
123
123
|
| `contentrain_validate` | Validate content against model schemas |
|
|
124
|
-
| `contentrain_submit` | Push
|
|
124
|
+
| `contentrain_submit` | Push cr/* branches to remote |
|
|
125
125
|
| `contentrain_merge` | Merge a review-mode branch into contentrain locally |
|
|
126
|
+
| `contentrain_reconcile` | Content-aware three-way merge of a diverged contentrain ↔ base pair |
|
|
126
127
|
| `contentrain_bulk` | Run batch operations on existing content |
|
|
127
128
|
|
|
128
129
|
## Calling Sequences
|
|
@@ -132,7 +132,9 @@ Each entry in the `entries` array:
|
|
|
132
132
|
"id": "optional-entry-id",
|
|
133
133
|
"slug": "optional-slug",
|
|
134
134
|
"locale": "en",
|
|
135
|
-
"data": { "field_name": "field_value" }
|
|
135
|
+
"data": { "field_name": "field_value" },
|
|
136
|
+
"publish_at": "2026-04-01T00:00:00Z",
|
|
137
|
+
"expire_at": "2026-06-30T23:59:59Z"
|
|
136
138
|
}
|
|
137
139
|
```
|
|
138
140
|
|
|
@@ -145,6 +147,8 @@ Each entry in the `entries` array:
|
|
|
145
147
|
| **Singleton** | `locale`, `data` | No `id` or `slug` needed |
|
|
146
148
|
| **Dictionary** | `locale`, `data` | `data` is flat key-value: `{ "auth.login": "Log In" }` |
|
|
147
149
|
|
|
150
|
+
**Scheduling** (`publish_at`, `expire_at`, optional, ISO 8601) sits next to `data`, never inside it. It is stored in meta only and **never changes `status`** — a past `publish_at` does not publish a draft; the dates gate delivery of an entry that is already `published`. Pass `null` to clear a date; omit the key to leave it unchanged. To publish, use `contentrain_bulk update_status`.
|
|
151
|
+
|
|
148
152
|
**NEVER include system fields** (`status`, `source`, `updated_by`, `updated_at`, `createdAt`, `updatedAt`) in `data`.
|
|
149
153
|
|
|
150
154
|
### contentrain_content_delete
|
|
@@ -313,7 +317,7 @@ Validate project content against model schemas.
|
|
|
313
317
|
| Parameter | Type | Required | Description |
|
|
314
318
|
|-----------|------|----------|-------------|
|
|
315
319
|
| `model` | string | No | Validate specific model only (omit for all) |
|
|
316
|
-
| `fix` | boolean | No | Auto-fix structural issues (canonical sort, orphan meta, missing locale files, stray non-i18n meta layout)
|
|
320
|
+
| `fix` | boolean | No | Auto-fix structural issues (canonical sort, orphan meta, missing locale files, stray non-i18n meta layout, `publish_at`/`expire_at` copies an older `content_save` left in document frontmatter) and backfill a missing model `title_field`. Default: `false` |
|
|
317
321
|
|
|
318
322
|
### contentrain_submit
|
|
319
323
|
|
|
@@ -340,7 +344,28 @@ Target the branch by exact name, or resolve it by model:
|
|
|
340
344
|
|
|
341
345
|
\* Provide either `branch` or `model`. Ambiguous model matches return the candidate branches so you can pick one (or pass `latest: true`).
|
|
342
346
|
|
|
343
|
-
Returns `{ branch, action, commit, sync }` — `sync.skipped[]` lists files the selective sync skipped because the developer has uncommitted changes. The CLI surfaces
|
|
347
|
+
Returns `{ branch, action, commit, sync, base_advance, remote_push }` — `sync.skipped[]` lists files the selective sync skipped because the developer has uncommitted changes; `base_advance: "blocked_diverged"` means the merge landed on `contentrain` but the base branch has diverged and was not advanced (run `contentrain_reconcile`). The CLI surfaces these as warnings.
|
|
348
|
+
|
|
349
|
+
### contentrain_reconcile
|
|
350
|
+
|
|
351
|
+
Content-aware three-way merge of a diverged `contentrain` ↔ base-branch pair. The divergence exists when the base branch holds commits `contentrain` does not (typically a dual-domain migration PR); until it is reconciled, every auto-merge write reports `base_advance: "blocked_diverged"`. Everything one side changed merges mechanically (entry-, key-, and term+locale-level); only the same item changed differently on both sides becomes a conflict. Local-filesystem only (`localWorktree`).
|
|
352
|
+
|
|
353
|
+
| Parameter | Type | Required | Description |
|
|
354
|
+
|-----------|------|----------|-------------|
|
|
355
|
+
| `dry_run` | boolean | No | Default `true` — preview the plan and conflicts, touch nothing. Set `false` to execute |
|
|
356
|
+
| `resolutions` | array | No | Decisions for conflicts from a previous dry-run: `{ id, choose: "ours"\|"theirs" }` or `{ id, value }` |
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{ "dry_run": true }
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Returns `{ status: "in_sync" | "preview" | "conflicts" | "reconciled", summary, changes, conflicts, ... }`. On execute it lands as a two-parent merge commit on `contentrain` and fast-forwards the base branch.
|
|
363
|
+
|
|
364
|
+
Notes:
|
|
365
|
+
- Always dry-run first. Conflicts are content decisions — ask the editor/user which side wins; never guess.
|
|
366
|
+
- Conflict ids hash position AND values: a resolution made against a stale preview is dropped and the conflict re-reported with a fresh id.
|
|
367
|
+
- Document bodies are never text-merged; two-sided body edits always conflict.
|
|
368
|
+
- Non-content files that conflict (source code committed to both branches) abort with `RECONCILE_SOURCE_CONFLICT` — those need a manual git merge.
|
|
344
369
|
|
|
345
370
|
### contentrain_branch_list
|
|
346
371
|
|
|
@@ -387,7 +412,9 @@ Run git-backed batch operations on existing content entries.
|
|
|
387
412
|
| `model` | string | Yes | Target model ID |
|
|
388
413
|
| `source_locale` | string | Conditional | For `copy_locale` |
|
|
389
414
|
| `target_locale` | string | Conditional | For `copy_locale` |
|
|
390
|
-
| `entry_ids` | string[] | Conditional | For `update_status` and `delete_entries` |
|
|
415
|
+
| `entry_ids` | string[] | Conditional | For `update_status` on collections, and for `delete_entries` |
|
|
416
|
+
| `slugs` | string[] | Conditional | For `update_status` on documents — documents are keyed by slug, not entry ID |
|
|
417
|
+
| `locale` | string | No | Scope `update_status` to one locale (i18n models); default is every supported locale |
|
|
391
418
|
| `status` | string | Conditional | For `update_status` |
|
|
392
419
|
| `confirm` | boolean | Conditional | For `delete_entries` (must be `true`) |
|
|
393
420
|
|
|
@@ -396,7 +423,7 @@ Run git-backed batch operations on existing content entries.
|
|
|
396
423
|
| Operation | Applicable Kinds | Required Params |
|
|
397
424
|
|-----------|-----------------|-----------------|
|
|
398
425
|
| `copy_locale` | collection, singleton, dictionary (i18n only) | `source_locale`, `target_locale` |
|
|
399
|
-
| `update_status` | collection
|
|
426
|
+
| `update_status` | collection (`entry_ids`), document (`slugs`), singleton and dictionary (neither) | `status`, plus the list that matches the kind |
|
|
400
427
|
| `delete_entries` | collection only | `entry_ids`, `confirm: true` |
|
|
401
428
|
|
|
402
429
|
#### Rules
|
|
@@ -157,6 +157,19 @@ Contentrain's storage format is designed to minimize Git merge conflicts:
|
|
|
157
157
|
- When a conflict occurs, the agent should NOT attempt to resolve it. Inform the user and direct them to Studio.
|
|
158
158
|
- Collection object-map format with sorted keys means ~0.3% chance of conflict for two new entries in a 350-entry collection.
|
|
159
159
|
|
|
160
|
+
### 4.4 Branch Divergence and Reconcile
|
|
161
|
+
|
|
162
|
+
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**.
|
|
163
|
+
|
|
164
|
+
- Divergence is a product state, not an error. Writes still land on `contentrain` and report `base_advance: "blocked_diverged"`; nothing is lost.
|
|
165
|
+
- The exit is `contentrain_reconcile` (CLI: `npx contentrain reconcile`) — a content-aware three-way merge. Always dry-run first.
|
|
166
|
+
- Everything one side changed merges mechanically (entry-, key-, and term+locale-level). Only the same item changed differently on both sides becomes a `ConflictItem`.
|
|
167
|
+
- Conflicts are content decisions: take them to the editor/user, collect `{ id, choose }` resolutions, run again. NEVER guess a side.
|
|
168
|
+
- Conflict ids hash position AND values — a decision made against a stale preview is dropped and re-asked. This is intended, not a bug.
|
|
169
|
+
- The result is a two-parent merge commit on `contentrain`; the fast-forward advance works again afterwards.
|
|
170
|
+
|
|
171
|
+
**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.
|
|
172
|
+
|
|
160
173
|
---
|
|
161
174
|
|
|
162
175
|
## 5. Workflow States
|
|
@@ -39,13 +39,14 @@ Confirm:
|
|
|
39
39
|
### 2. Pick the Correct Bulk Operation
|
|
40
40
|
|
|
41
41
|
- `copy_locale`: clone one locale to another for i18n-enabled `collection`, `singleton`, or `dictionary` models
|
|
42
|
-
- `update_status`: update metadata state — many entries at once for `collection
|
|
42
|
+
- `update_status`: update metadata state — many entries at once for `collection` (by `entry_ids`) or `document` (by `slugs`), or the single record of a `singleton`/`dictionary`
|
|
43
43
|
- `delete_entries`: remove many collection entries at once
|
|
44
44
|
|
|
45
45
|
### 3. Apply Safety Rules
|
|
46
46
|
|
|
47
47
|
- never use `copy_locale` on non-i18n models
|
|
48
|
-
- `update_status`
|
|
48
|
+
- `update_status` addresses records the way their meta is laid out: `entry_ids` for `collection` models, `slugs` for `document` models (meta is one file per slug — the same identity `contentrain_content_save` uses), and **neither** for `singleton`/`dictionary`, which have one meta record per locale. The wrong list is rejected with a message naming the right one.
|
|
49
|
+
- `publish_at` on `contentrain_content_save` does **not** publish — it gates delivery of an already-published entry. `update_status` is the only way to change status.
|
|
49
50
|
- `delete_entries` is collection-only
|
|
50
51
|
- `update_status` rewrites every supported locale unless you pass `locale`. Pass it when restoring one locale's status, or you will change the other's too.
|
|
51
52
|
- confirm entry IDs before delete operations
|
|
@@ -85,7 +86,19 @@ Scoped to one locale — leaves every other locale's status untouched:
|
|
|
85
86
|
}
|
|
86
87
|
```
|
|
87
88
|
|
|
88
|
-
A
|
|
89
|
+
A document model — meta is one file per slug, so pass `slugs`, not `entry_ids`:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"operation": "update_status",
|
|
94
|
+
"model": "guide-sections",
|
|
95
|
+
"slugs": ["instagram-9", "instagram-10", "instagram-11"],
|
|
96
|
+
"status": "published",
|
|
97
|
+
"locale": "tr"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A singleton or dictionary — one meta record, so no `entry_ids` or `slugs`:
|
|
89
102
|
|
|
90
103
|
```json
|
|
91
104
|
{
|
|
@@ -167,7 +167,7 @@ For large content operations (10+ entries), also recommend running `contentrain
|
|
|
167
167
|
Call `contentrain_submit` to push branches to remote:
|
|
168
168
|
|
|
169
169
|
- Write operations already create branches and commit changes automatically.
|
|
170
|
-
- `contentrain_submit` pushes unmerged `
|
|
170
|
+
- `contentrain_submit` pushes unmerged `cr/*` branches to the remote.
|
|
171
171
|
- In `auto-merge` mode, branches are already merged to the base branch during the write operation.
|
|
172
172
|
- In `review` mode, branches are pushed for team review.
|
|
173
173
|
|
|
@@ -65,7 +65,7 @@ Default rules:
|
|
|
65
65
|
Follow these rules:
|
|
66
66
|
|
|
67
67
|
- model IDs must be kebab-case
|
|
68
|
-
- field names must be snake_case
|
|
68
|
+
- field names must be snake_case — for *new* fields. A field an existing model already has under a legacy name (`creativeWork`) is kept by `contentrain_model_save` and reported in `schema_warnings`; never rename it just to satisfy the rule, since the rename must reach its content keys in every locale and every consumer too
|
|
69
69
|
- relation fields must define a target `model`
|
|
70
70
|
- prefer small, explicit schemas over large generic `object` blobs
|
|
71
71
|
- only mark fields as `required` when the content truly cannot function without them
|
|
@@ -45,6 +45,13 @@ Auto-fix candidates:
|
|
|
45
45
|
- stray non-i18n meta layout (an `i18n: false` model with per-locale meta files
|
|
46
46
|
left by older versions) — pruned or migrated deterministically, never merging
|
|
47
47
|
or downgrading a status
|
|
48
|
+
- `publish_at`/`expire_at` copies an older `content_save` left in document
|
|
49
|
+
frontmatter — stripped only when the meta record holds the same key, so the
|
|
50
|
+
value is never lost and an author's own frontmatter field is never touched
|
|
51
|
+
- a missing model `title_field` — backfilled by the same ranking Studio's title
|
|
52
|
+
resolver uses (required name-like field first; slug/url/code never inferred),
|
|
53
|
+
reported as a notice naming the rule. Read it: if the pick is wrong, correct it
|
|
54
|
+
with `contentrain_model_save`
|
|
48
55
|
- structural housekeeping reported by the validator
|
|
49
56
|
|
|
50
57
|
Manual fix candidates:
|