@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.
Files changed (80) hide show
  1. package/README.md +5 -3
  2. package/dist/{annotations-Clhbw2lh.mjs → annotations-BFJbfSKe.mjs} +13 -1
  3. package/dist/{annotations-Clhbw2lh.mjs.map → annotations-BFJbfSKe.mjs.map} +1 -1
  4. package/dist/{apply-manager-D5ym5Jba.mjs → apply-manager-CDss8ZIr.mjs} +5 -5
  5. package/dist/{apply-manager-D5ym5Jba.mjs.map → apply-manager-CDss8ZIr.mjs.map} +1 -1
  6. package/dist/{branch-lifecycle-CFVpTpNT.mjs → branch-lifecycle-DjX3MXNG.mjs} +10 -7
  7. package/dist/{branch-lifecycle-CFVpTpNT.mjs.map → branch-lifecycle-DjX3MXNG.mjs.map} +1 -1
  8. package/dist/{config-BIl5Ybj0.mjs → config-Lw7vpK9n.mjs} +4 -2
  9. package/dist/config-Lw7vpK9n.mjs.map +1 -0
  10. package/dist/{context-J0OU9Nfp.mjs → context-DS40WOdw.mjs} +2 -2
  11. package/dist/{context-J0OU9Nfp.mjs.map → context-DS40WOdw.mjs.map} +1 -1
  12. package/dist/core/apply-manager.mjs +5 -5
  13. package/dist/core/config.d.mts.map +1 -1
  14. package/dist/core/config.mjs +1 -1
  15. package/dist/core/context.mjs +2 -2
  16. package/dist/core/doctor.mjs +3 -3
  17. package/dist/core/validator/index.d.mts.map +1 -1
  18. package/dist/core/validator/index.mjs +2 -2
  19. package/dist/{doctor-CgBoT6h1.mjs → doctor-Dxu_SzZy.mjs} +3 -3
  20. package/dist/{doctor-CgBoT6h1.mjs.map → doctor-Dxu_SzZy.mjs.map} +1 -1
  21. package/dist/git/branch-lifecycle.d.mts.map +1 -1
  22. package/dist/git/branch-lifecycle.mjs +2 -2
  23. package/dist/git/transaction.d.mts.map +1 -1
  24. package/dist/git/transaction.mjs +3 -3
  25. package/dist/index.mjs +10 -10
  26. package/dist/{local-DvP4Y4T-.mjs → local-BEl5L7p5.mjs} +4 -4
  27. package/dist/{local-DvP4Y4T-.mjs.map → local-BEl5L7p5.mjs.map} +1 -1
  28. package/dist/providers/github/index.d.mts +60 -3
  29. package/dist/providers/github/index.d.mts.map +1 -1
  30. package/dist/providers/github/index.mjs +85 -5
  31. package/dist/providers/github/index.mjs.map +1 -1
  32. package/dist/providers/local/index.mjs +4 -4
  33. package/dist/server/http/index.d.mts +63 -21
  34. package/dist/server/http/index.d.mts.map +1 -1
  35. package/dist/server/http/index.mjs +165 -54
  36. package/dist/server/http/index.mjs.map +1 -1
  37. package/dist/{server-B1OJrUQ9.mjs → server-BiiVUjgs.mjs} +254 -48
  38. package/dist/server-BiiVUjgs.mjs.map +1 -0
  39. package/dist/server.mjs +10 -10
  40. package/dist/testing/conformance.d.mts +59 -0
  41. package/dist/testing/conformance.d.mts.map +1 -0
  42. package/dist/testing/conformance.mjs +59 -0
  43. package/dist/testing/conformance.mjs.map +1 -0
  44. package/dist/tools/annotations.d.mts.map +1 -1
  45. package/dist/tools/annotations.mjs +1 -1
  46. package/dist/{transaction-DgkNgWSI.mjs → transaction-0D_edyh0.mjs} +48 -5
  47. package/dist/transaction-0D_edyh0.mjs.map +1 -0
  48. package/dist/{validator-B0nNRFeU.mjs → validator-BX2ou7V-.mjs} +46 -17
  49. package/dist/validator-BX2ou7V-.mjs.map +1 -0
  50. package/package.json +11 -6
  51. package/testing/conformance/01-new-collection-entry/expected/.contentrain/config.json +15 -0
  52. package/testing/conformance/01-new-collection-entry/expected/.contentrain/content/blog/blog/en.json +6 -0
  53. package/testing/conformance/01-new-collection-entry/expected/.contentrain/context.json +22 -0
  54. package/testing/conformance/01-new-collection-entry/expected/.contentrain/meta/blog/en.json +7 -0
  55. package/testing/conformance/01-new-collection-entry/expected/.contentrain/models/blog.json +16 -0
  56. package/testing/conformance/01-new-collection-entry/scenario.json +19 -0
  57. package/testing/conformance/01-new-collection-entry/setup/.contentrain/config.json +15 -0
  58. package/testing/conformance/01-new-collection-entry/setup/.contentrain/models/blog.json +16 -0
  59. package/testing/conformance/02-new-singleton/expected/.contentrain/config.json +15 -0
  60. package/testing/conformance/02-new-singleton/expected/.contentrain/content/marketing/hero/en.json +4 -0
  61. package/testing/conformance/02-new-singleton/expected/.contentrain/context.json +22 -0
  62. package/testing/conformance/02-new-singleton/expected/.contentrain/meta/hero/en.json +5 -0
  63. package/testing/conformance/02-new-singleton/expected/.contentrain/models/hero.json +16 -0
  64. package/testing/conformance/02-new-singleton/scenario.json +18 -0
  65. package/testing/conformance/02-new-singleton/setup/.contentrain/config.json +15 -0
  66. package/testing/conformance/02-new-singleton/setup/.contentrain/models/hero.json +16 -0
  67. package/testing/conformance/03-new-dictionary/expected/.contentrain/config.json +15 -0
  68. package/testing/conformance/03-new-dictionary/expected/.contentrain/content/marketing/ui-strings/en.json +5 -0
  69. package/testing/conformance/03-new-dictionary/expected/.contentrain/context.json +22 -0
  70. package/testing/conformance/03-new-dictionary/expected/.contentrain/meta/ui-strings/en.json +5 -0
  71. package/testing/conformance/03-new-dictionary/expected/.contentrain/models/ui-strings.json +7 -0
  72. package/testing/conformance/03-new-dictionary/scenario.json +19 -0
  73. package/testing/conformance/03-new-dictionary/setup/.contentrain/config.json +15 -0
  74. package/testing/conformance/03-new-dictionary/setup/.contentrain/models/ui-strings.json +7 -0
  75. package/testing/conformance/README.md +77 -0
  76. package/testing/conformance/scenarios.json +36 -0
  77. package/dist/config-BIl5Ybj0.mjs.map +0 -1
  78. package/dist/server-B1OJrUQ9.mjs.map +0 -1
  79. package/dist/transaction-DgkNgWSI.mjs.map +0 -1
  80. package/dist/validator-B0nNRFeU.mjs.map +0 -1
@@ -0,0 +1,16 @@
1
+ {
2
+ "domain": "blog",
3
+ "fields": {
4
+ "body": {
5
+ "type": "text"
6
+ },
7
+ "title": {
8
+ "required": true,
9
+ "type": "string"
10
+ }
11
+ },
12
+ "i18n": true,
13
+ "id": "blog",
14
+ "kind": "collection",
15
+ "name": "Blog"
16
+ }
@@ -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,15 @@
1
+ {
2
+ "domains": [
3
+ "blog"
4
+ ],
5
+ "locales": {
6
+ "default": "en",
7
+ "supported": [
8
+ "en",
9
+ "tr"
10
+ ]
11
+ },
12
+ "stack": "vue-nuxt",
13
+ "version": 1,
14
+ "workflow": "auto-merge"
15
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "domain": "blog",
3
+ "fields": {
4
+ "body": {
5
+ "type": "text"
6
+ },
7
+ "title": {
8
+ "required": true,
9
+ "type": "string"
10
+ }
11
+ },
12
+ "i18n": true,
13
+ "id": "blog",
14
+ "kind": "collection",
15
+ "name": "Blog"
16
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "domains": [
3
+ "marketing"
4
+ ],
5
+ "locales": {
6
+ "default": "en",
7
+ "supported": [
8
+ "en",
9
+ "tr"
10
+ ]
11
+ },
12
+ "stack": "vue-nuxt",
13
+ "version": 1,
14
+ "workflow": "auto-merge"
15
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "subtitle": "Build the web with AI agents",
3
+ "title": "Welcome to Contentrain"
4
+ }
@@ -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,5 @@
1
+ {
2
+ "source": "agent",
3
+ "status": "draft",
4
+ "updated_by": "contentrain-mcp"
5
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "domain": "marketing",
3
+ "fields": {
4
+ "subtitle": {
5
+ "type": "text"
6
+ },
7
+ "title": {
8
+ "required": true,
9
+ "type": "string"
10
+ }
11
+ },
12
+ "i18n": true,
13
+ "id": "hero",
14
+ "kind": "singleton",
15
+ "name": "Hero"
16
+ }
@@ -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,15 @@
1
+ {
2
+ "domains": [
3
+ "marketing"
4
+ ],
5
+ "locales": {
6
+ "default": "en",
7
+ "supported": [
8
+ "en",
9
+ "tr"
10
+ ]
11
+ },
12
+ "stack": "vue-nuxt",
13
+ "version": 1,
14
+ "workflow": "auto-merge"
15
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "domain": "marketing",
3
+ "fields": {
4
+ "subtitle": {
5
+ "type": "text"
6
+ },
7
+ "title": {
8
+ "required": true,
9
+ "type": "string"
10
+ }
11
+ },
12
+ "i18n": true,
13
+ "id": "hero",
14
+ "kind": "singleton",
15
+ "name": "Hero"
16
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "domains": [
3
+ "marketing"
4
+ ],
5
+ "locales": {
6
+ "default": "en",
7
+ "supported": [
8
+ "en",
9
+ "tr"
10
+ ]
11
+ },
12
+ "stack": "vue-nuxt",
13
+ "version": 1,
14
+ "workflow": "auto-merge"
15
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "hero.cta": "Get started",
3
+ "hero.subtitle": "Build the web with AI agents",
4
+ "hero.title": "Welcome to Contentrain"
5
+ }
@@ -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,5 @@
1
+ {
2
+ "source": "agent",
3
+ "status": "draft",
4
+ "updated_by": "contentrain-mcp"
5
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "domain": "marketing",
3
+ "i18n": true,
4
+ "id": "ui-strings",
5
+ "kind": "dictionary",
6
+ "name": "UI Strings"
7
+ }
@@ -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,15 @@
1
+ {
2
+ "domains": [
3
+ "marketing"
4
+ ],
5
+ "locales": {
6
+ "default": "en",
7
+ "supported": [
8
+ "en",
9
+ "tr"
10
+ ]
11
+ },
12
+ "stack": "vue-nuxt",
13
+ "version": 1,
14
+ "workflow": "auto-merge"
15
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "domain": "marketing",
3
+ "i18n": true,
4
+ "id": "ui-strings",
5
+ "kind": "dictionary",
6
+ "name": "UI Strings"
7
+ }
@@ -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"}