@contentrain/mcp 1.3.0 → 1.5.0
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 +5 -3
- package/dist/{annotations-Clhbw2lh.mjs → annotations-BFJbfSKe.mjs} +13 -1
- package/dist/{annotations-Clhbw2lh.mjs.map → annotations-BFJbfSKe.mjs.map} +1 -1
- package/dist/{apply-manager-D5ym5Jba.mjs → apply-manager-CDss8ZIr.mjs} +5 -5
- package/dist/{apply-manager-D5ym5Jba.mjs.map → apply-manager-CDss8ZIr.mjs.map} +1 -1
- package/dist/{branch-lifecycle-CFVpTpNT.mjs → branch-lifecycle-DjX3MXNG.mjs} +10 -7
- package/dist/{branch-lifecycle-CFVpTpNT.mjs.map → branch-lifecycle-DjX3MXNG.mjs.map} +1 -1
- package/dist/{config-BIl5Ybj0.mjs → config-Lw7vpK9n.mjs} +4 -2
- package/dist/config-Lw7vpK9n.mjs.map +1 -0
- package/dist/{context-J0OU9Nfp.mjs → context-DS40WOdw.mjs} +2 -2
- package/dist/{context-J0OU9Nfp.mjs.map → context-DS40WOdw.mjs.map} +1 -1
- package/dist/core/apply-manager.mjs +5 -5
- package/dist/core/config.d.mts.map +1 -1
- package/dist/core/config.mjs +1 -1
- package/dist/core/context.mjs +2 -2
- package/dist/core/doctor.mjs +3 -3
- package/dist/core/validator/index.d.mts.map +1 -1
- package/dist/core/validator/index.mjs +2 -2
- package/dist/{doctor-CgBoT6h1.mjs → doctor-Dxu_SzZy.mjs} +3 -3
- package/dist/{doctor-CgBoT6h1.mjs.map → doctor-Dxu_SzZy.mjs.map} +1 -1
- package/dist/git/branch-lifecycle.d.mts.map +1 -1
- package/dist/git/branch-lifecycle.mjs +2 -2
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +3 -3
- package/dist/index.mjs +10 -10
- package/dist/{local-DvP4Y4T-.mjs → local-BEl5L7p5.mjs} +4 -4
- package/dist/{local-DvP4Y4T-.mjs.map → local-BEl5L7p5.mjs.map} +1 -1
- package/dist/providers/github/index.d.mts +60 -3
- package/dist/providers/github/index.d.mts.map +1 -1
- package/dist/providers/github/index.mjs +85 -5
- package/dist/providers/github/index.mjs.map +1 -1
- package/dist/providers/local/index.mjs +4 -4
- package/dist/server/http/index.d.mts +63 -21
- package/dist/server/http/index.d.mts.map +1 -1
- package/dist/server/http/index.mjs +165 -54
- package/dist/server/http/index.mjs.map +1 -1
- package/dist/{server-B1OJrUQ9.mjs → server-BiiVUjgs.mjs} +254 -48
- package/dist/server-BiiVUjgs.mjs.map +1 -0
- package/dist/server.mjs +10 -10
- package/dist/testing/conformance.d.mts +59 -0
- package/dist/testing/conformance.d.mts.map +1 -0
- package/dist/testing/conformance.mjs +59 -0
- package/dist/testing/conformance.mjs.map +1 -0
- package/dist/tools/annotations.d.mts.map +1 -1
- package/dist/tools/annotations.mjs +1 -1
- package/dist/{transaction-DgkNgWSI.mjs → transaction-0D_edyh0.mjs} +48 -5
- package/dist/transaction-0D_edyh0.mjs.map +1 -0
- package/dist/{validator-B0nNRFeU.mjs → validator-BX2ou7V-.mjs} +46 -17
- package/dist/validator-BX2ou7V-.mjs.map +1 -0
- package/package.json +11 -6
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/config.json +15 -0
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/content/blog/blog/en.json +6 -0
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/context.json +22 -0
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/meta/blog/en.json +7 -0
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/models/blog.json +16 -0
- package/testing/conformance/01-new-collection-entry/scenario.json +19 -0
- package/testing/conformance/01-new-collection-entry/setup/.contentrain/config.json +15 -0
- package/testing/conformance/01-new-collection-entry/setup/.contentrain/models/blog.json +16 -0
- package/testing/conformance/02-new-singleton/expected/.contentrain/config.json +15 -0
- package/testing/conformance/02-new-singleton/expected/.contentrain/content/marketing/hero/en.json +4 -0
- package/testing/conformance/02-new-singleton/expected/.contentrain/context.json +22 -0
- package/testing/conformance/02-new-singleton/expected/.contentrain/meta/hero/en.json +5 -0
- package/testing/conformance/02-new-singleton/expected/.contentrain/models/hero.json +16 -0
- package/testing/conformance/02-new-singleton/scenario.json +18 -0
- package/testing/conformance/02-new-singleton/setup/.contentrain/config.json +15 -0
- package/testing/conformance/02-new-singleton/setup/.contentrain/models/hero.json +16 -0
- package/testing/conformance/03-new-dictionary/expected/.contentrain/config.json +15 -0
- package/testing/conformance/03-new-dictionary/expected/.contentrain/content/marketing/ui-strings/en.json +5 -0
- package/testing/conformance/03-new-dictionary/expected/.contentrain/context.json +22 -0
- package/testing/conformance/03-new-dictionary/expected/.contentrain/meta/ui-strings/en.json +5 -0
- package/testing/conformance/03-new-dictionary/expected/.contentrain/models/ui-strings.json +7 -0
- package/testing/conformance/03-new-dictionary/scenario.json +19 -0
- package/testing/conformance/03-new-dictionary/setup/.contentrain/config.json +15 -0
- package/testing/conformance/03-new-dictionary/setup/.contentrain/models/ui-strings.json +7 -0
- package/testing/conformance/README.md +77 -0
- package/testing/conformance/scenarios.json +36 -0
- package/dist/config-BIl5Ybj0.mjs.map +0 -1
- package/dist/server-B1OJrUQ9.mjs.map +0 -1
- package/dist/transaction-DgkNgWSI.mjs.map +0 -1
- package/dist/validator-B0nNRFeU.mjs.map +0 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"context_tool": "contentrain_content_save",
|
|
3
|
+
"description": "Save a new collection entry for a model",
|
|
4
|
+
"id": "01-new-collection-entry",
|
|
5
|
+
"input": {
|
|
6
|
+
"entries": [
|
|
7
|
+
{
|
|
8
|
+
"data": {
|
|
9
|
+
"body": "First post content",
|
|
10
|
+
"title": "Hello World"
|
|
11
|
+
},
|
|
12
|
+
"id": "abc123def456",
|
|
13
|
+
"locale": "en"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"model": "blog"
|
|
17
|
+
},
|
|
18
|
+
"operation": "save_content"
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lastOperation": {
|
|
3
|
+
"entries": [
|
|
4
|
+
"en"
|
|
5
|
+
],
|
|
6
|
+
"locale": "en",
|
|
7
|
+
"model": "hero",
|
|
8
|
+
"source": "mcp-local",
|
|
9
|
+
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
10
|
+
"tool": "contentrain_content_save"
|
|
11
|
+
},
|
|
12
|
+
"stats": {
|
|
13
|
+
"entries": 1,
|
|
14
|
+
"lastSync": "2026-01-01T00:00:00.000Z",
|
|
15
|
+
"locales": [
|
|
16
|
+
"en",
|
|
17
|
+
"tr"
|
|
18
|
+
],
|
|
19
|
+
"models": 1
|
|
20
|
+
},
|
|
21
|
+
"version": "1"
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"context_tool": "contentrain_content_save",
|
|
3
|
+
"description": "Save a new singleton content block",
|
|
4
|
+
"id": "02-new-singleton",
|
|
5
|
+
"input": {
|
|
6
|
+
"entries": [
|
|
7
|
+
{
|
|
8
|
+
"data": {
|
|
9
|
+
"subtitle": "Build the web with AI agents",
|
|
10
|
+
"title": "Welcome to Contentrain"
|
|
11
|
+
},
|
|
12
|
+
"locale": "en"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"model": "hero"
|
|
16
|
+
},
|
|
17
|
+
"operation": "save_content"
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lastOperation": {
|
|
3
|
+
"entries": [
|
|
4
|
+
"en"
|
|
5
|
+
],
|
|
6
|
+
"locale": "en",
|
|
7
|
+
"model": "ui-strings",
|
|
8
|
+
"source": "mcp-local",
|
|
9
|
+
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
10
|
+
"tool": "contentrain_content_save"
|
|
11
|
+
},
|
|
12
|
+
"stats": {
|
|
13
|
+
"entries": 1,
|
|
14
|
+
"lastSync": "2026-01-01T00:00:00.000Z",
|
|
15
|
+
"locales": [
|
|
16
|
+
"en",
|
|
17
|
+
"tr"
|
|
18
|
+
],
|
|
19
|
+
"models": 1
|
|
20
|
+
},
|
|
21
|
+
"version": "1"
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"context_tool": "contentrain_content_save",
|
|
3
|
+
"description": "Create a dictionary with multiple string keys",
|
|
4
|
+
"id": "03-new-dictionary",
|
|
5
|
+
"input": {
|
|
6
|
+
"entries": [
|
|
7
|
+
{
|
|
8
|
+
"data": {
|
|
9
|
+
"hero.cta": "Get started",
|
|
10
|
+
"hero.subtitle": "Build the web with AI agents",
|
|
11
|
+
"hero.title": "Welcome to Contentrain"
|
|
12
|
+
},
|
|
13
|
+
"locale": "en"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"model": "ui-strings"
|
|
17
|
+
},
|
|
18
|
+
"operation": "save_content"
|
|
19
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Conformance Fixtures
|
|
2
|
+
|
|
3
|
+
Byte-identical conformance test suite for content operations. Each fixture describes an operation and its expected on-disk file tree after execution. Used by `packages/mcp/tests/conformance.test.ts`.
|
|
4
|
+
|
|
5
|
+
**Purpose** — guarantee byte-level parity between providers (LocalProvider today; GitHubProvider + GitLabProvider in later phases) for the same operation. Any silent drift in serialization, meta generation, context.json, or path resolution shows up as a failed fixture.
|
|
6
|
+
|
|
7
|
+
## Directory layout
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
conformance/
|
|
11
|
+
├── README.md — this file
|
|
12
|
+
├── scenarios.json — human-readable index
|
|
13
|
+
├── 01-new-collection-entry/
|
|
14
|
+
│ ├── scenario.json — input spec
|
|
15
|
+
│ ├── setup/ — files copied into temp projectRoot before op runs (optional)
|
|
16
|
+
│ └── expected/ — byte-identical file tree after op (GENERATED, committed)
|
|
17
|
+
├── 02-new-singleton/
|
|
18
|
+
│ └── ...
|
|
19
|
+
└── 03-new-dictionary/
|
|
20
|
+
└── ...
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Scenario schema
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
{
|
|
27
|
+
"id": "01-new-collection-entry",
|
|
28
|
+
"description": "Save a new collection entry",
|
|
29
|
+
"operation": "save_content", // save_content | save_model | delete_content
|
|
30
|
+
"context_tool": "contentrain_content_save", // tool name recorded in context.json (optional)
|
|
31
|
+
"input": { // operation-specific params
|
|
32
|
+
"model": "blog",
|
|
33
|
+
"entries": [
|
|
34
|
+
{ "id": "...", "locale": "en", "data": { ... } }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Determinism rules
|
|
41
|
+
|
|
42
|
+
The harness fixes non-deterministic sources so output is reproducible:
|
|
43
|
+
|
|
44
|
+
- `Date.now()` / `new Date()` returns `2026-01-01T00:00:00.000Z` (vitest fake timers)
|
|
45
|
+
- `CONTENTRAIN_SOURCE=mcp-local` env var is set
|
|
46
|
+
- `Math.random()` is NOT stubbed globally — scenarios that need determinism must pass explicit entry IDs instead of relying on `generateEntryId()`
|
|
47
|
+
- Git is not initialized — core write functions don't require it
|
|
48
|
+
|
|
49
|
+
## Regenerating `expected/`
|
|
50
|
+
|
|
51
|
+
When an intentional behavior change lands (e.g., schema bump, new default field), regenerate the expected fixtures and review the diff:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
GENERATE_FIXTURES=1 pnpm --filter @contentrain/mcp test -- conformance
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then `git diff` the `expected/` directories. Each byte difference must be a conscious decision.
|
|
58
|
+
|
|
59
|
+
## Adding a new fixture
|
|
60
|
+
|
|
61
|
+
1. `mkdir NN-short-name/setup/.contentrain`
|
|
62
|
+
2. Write `scenario.json` at the fixture root
|
|
63
|
+
3. Optionally add setup files under `setup/`
|
|
64
|
+
4. `GENERATE_FIXTURES=1 pnpm --filter @contentrain/mcp test -- conformance` to populate `expected/`
|
|
65
|
+
5. Review generated `expected/` files manually — they become the contract
|
|
66
|
+
6. Commit both `setup/` and `expected/`
|
|
67
|
+
|
|
68
|
+
## Provider matrix (future)
|
|
69
|
+
|
|
70
|
+
Today the harness runs scenarios against the current MCP write path (direct fs + `writeContent` + `writeContext`). Post-refactor phases will add:
|
|
71
|
+
|
|
72
|
+
- Phase 2 — run through plan/apply API, assert `FileChange[]` → same `expected/`
|
|
73
|
+
- Phase 3 — LocalProvider.applyPlan → same `expected/`
|
|
74
|
+
- Phase 5 — GitHubProvider.applyPlan (mocked) → same `expected/`
|
|
75
|
+
- Phase 8 — GitLab/Bitbucket providers → same `expected/`
|
|
76
|
+
|
|
77
|
+
The fixtures themselves never change between providers. Only the runner wiring does.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"seeds": [
|
|
3
|
+
{
|
|
4
|
+
"id": "01-new-collection-entry",
|
|
5
|
+
"operation": "save_content",
|
|
6
|
+
"coverage": "collection kind — new entry, merge semantics, object-map sort"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "02-new-singleton",
|
|
10
|
+
"operation": "save_content",
|
|
11
|
+
"coverage": "singleton kind — new entry, default meta"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "03-new-dictionary",
|
|
15
|
+
"operation": "save_content",
|
|
16
|
+
"coverage": "dictionary kind — multi-key create, flat object format"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"planned": [
|
|
20
|
+
"04-collection-update",
|
|
21
|
+
"05-singleton-update",
|
|
22
|
+
"06-dictionary-update",
|
|
23
|
+
"07-new-document",
|
|
24
|
+
"08-document-i18n-directory",
|
|
25
|
+
"09-delete-collection-entry",
|
|
26
|
+
"10-locale-copy",
|
|
27
|
+
"11-model-save-new",
|
|
28
|
+
"12-model-save-update",
|
|
29
|
+
"13-entry-status-update",
|
|
30
|
+
"14-delete-entire-locale",
|
|
31
|
+
"15-relation-validation",
|
|
32
|
+
"16-unique-field-violation",
|
|
33
|
+
"17-vocabulary-advisory",
|
|
34
|
+
"18-init-project-minimal"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-BIl5Ybj0.mjs","names":[],"sources":["../src/core/config.ts"],"sourcesContent":["import type { ContentrainConfig, Vocabulary } from '@contentrain/types'\nimport { join } from 'node:path'\nimport { contentrainDir, readJson } from '../util/fs.js'\nimport type { RepoReader } from './contracts/index.js'\n\nconst CONFIG_PATH = '.contentrain/config.json'\nconst VOCABULARY_PATH = '.contentrain/vocabulary.json'\n\nasync function readJsonViaReader<T>(reader: RepoReader, path: string): Promise<T | null> {\n try {\n return JSON.parse(await reader.readFile(path)) as T\n } catch {\n return null\n }\n}\n\nfunction normaliseConfig(raw: Partial<ContentrainConfig> | null): ContentrainConfig | null {\n if (!raw) return null\n return {\n version: raw.version ?? 1,\n stack: raw.stack ?? 'other',\n workflow: raw.workflow ?? 'auto-merge',\n locales: {\n default: raw.locales?.default ?? 'en',\n supported: raw.locales?.supported ?? [raw.locales?.default ?? 'en'],\n },\n domains: raw.domains ?? [],\n repository: raw.repository,\n assets_path: raw.assets_path,\n branchRetention: raw.branchRetention,\n }\n}\n\n/**\n * Read `.contentrain/config.json` and normalise it with safe defaults.\n *\n * Accepts either a legacy `projectRoot` string (LocalReader is constructed\n * internally) or any `RepoReader` — tool handlers pass their provider so\n * the same logic resolves against local fs or the GitHub Git Data API.\n */\nexport function readConfig(projectRoot: string): Promise<ContentrainConfig | null>\nexport function readConfig(reader: RepoReader): Promise<ContentrainConfig | null>\nexport async function readConfig(input: string | RepoReader): Promise<ContentrainConfig | null> {\n if (typeof input === 'string') {\n const raw = await readJson<Partial<ContentrainConfig>>(join(contentrainDir(input), 'config.json'))\n return normaliseConfig(raw)\n }\n const raw = await readJsonViaReader<Partial<ContentrainConfig>>(input, CONFIG_PATH)\n return normaliseConfig(raw)\n}\n\n/**\n * Read `.contentrain/vocabulary.json`. Same dual signature as {@link readConfig}.\n */\nexport function readVocabulary(projectRoot: string): Promise<Vocabulary | null>\nexport function readVocabulary(reader: RepoReader): Promise<Vocabulary | null>\nexport async function readVocabulary(input: string | RepoReader): Promise<Vocabulary | null> {\n if (typeof input === 'string') {\n return readJson<Vocabulary>(join(contentrainDir(input), 'vocabulary.json'))\n }\n return readJsonViaReader<Vocabulary>(input, VOCABULARY_PATH)\n}\n"],"mappings":";;;AAKA,MAAM,cAAc;AACpB,MAAM,kBAAkB;AAExB,eAAe,kBAAqB,QAAoB,MAAiC;AACvF,KAAI;AACF,SAAO,KAAK,MAAM,MAAM,OAAO,SAAS,KAAK,CAAC;SACxC;AACN,SAAO;;;AAIX,SAAS,gBAAgB,KAAkE;AACzF,KAAI,CAAC,IAAK,QAAO;AACjB,QAAO;EACL,SAAS,IAAI,WAAW;EACxB,OAAO,IAAI,SAAS;EACpB,UAAU,IAAI,YAAY;EAC1B,SAAS;GACP,SAAS,IAAI,SAAS,WAAW;GACjC,WAAW,IAAI,SAAS,aAAa,CAAC,IAAI,SAAS,WAAW,KAAK;GACpE;EACD,SAAS,IAAI,WAAW,EAAE;EAC1B,YAAY,IAAI;EAChB,aAAa,IAAI;EACjB,iBAAiB,IAAI;EACtB;;AAYH,eAAsB,WAAW,OAA+D;AAC9F,KAAI,OAAO,UAAU,SAEnB,QAAO,gBADK,MAAM,SAAqC,KAAK,eAAe,MAAM,EAAE,cAAc,CAAC,CACvE;AAG7B,QAAO,gBADK,MAAM,kBAA8C,OAAO,YAAY,CACxD;;AAQ7B,eAAsB,eAAe,OAAwD;AAC3F,KAAI,OAAO,UAAU,SACnB,QAAO,SAAqB,KAAK,eAAe,MAAM,EAAE,kBAAkB,CAAC;AAE7E,QAAO,kBAA8B,OAAO,gBAAgB"}
|