@contentrain/mcp 2.3.1 → 3.0.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 +1 -1
- package/dist/{annotations-D3tlsF38.mjs → annotations-C-tQVmot.mjs} +15 -1
- package/dist/annotations-C-tQVmot.mjs.map +1 -0
- package/dist/{apply-manager-SLCRLHN_.mjs → apply-manager-BybvnUVC.mjs} +28 -18
- package/dist/apply-manager-BybvnUVC.mjs.map +1 -0
- package/dist/{astro-parser-LNjEspnf.mjs → astro-parser-Cb9uuBpW.mjs} +3 -3
- package/dist/{astro-parser-LNjEspnf.mjs.map → astro-parser-Cb9uuBpW.mjs.map} +1 -1
- package/dist/{availability-sWWRz2W0.mjs → availability-Dgcmvv99.mjs} +1 -1
- package/dist/{availability-sWWRz2W0.mjs.map → availability-Dgcmvv99.mjs.map} +1 -1
- package/dist/{branch-lifecycle-BAfgSQBv.mjs → branch-lifecycle-B6PXZ2N7.mjs} +11 -61
- package/dist/branch-lifecycle-B6PXZ2N7.mjs.map +1 -0
- package/dist/{branch-lifecycle-D4W0WIs4.d.mts → branch-lifecycle-DhzHfzs3.d.mts} +1 -1
- package/dist/{branch-lifecycle-D4W0WIs4.d.mts.map → branch-lifecycle-DhzHfzs3.d.mts.map} +1 -1
- package/dist/{compiler-CNzm2Y2I.mjs → compiler-B6hNQeCC.mjs} +2 -2
- package/dist/{compiler-CNzm2Y2I.mjs.map → compiler-B6hNQeCC.mjs.map} +1 -1
- package/dist/{compiler-sfc.cjs-y0B0L3MH.mjs → compiler-sfc.cjs-8yY7b5bx.mjs} +2 -2
- package/dist/{compiler-sfc.cjs-y0B0L3MH.mjs.map → compiler-sfc.cjs-8yY7b5bx.mjs.map} +1 -1
- package/dist/{config-oxxgznz7.mjs → config-VlRKRwAS.mjs} +1 -1
- package/dist/{config-oxxgznz7.mjs.map → config-VlRKRwAS.mjs.map} +1 -1
- package/dist/{content-manager-DWo3G64y.d.mts → content-manager-Dn4tqDw1.d.mts} +10 -7
- package/dist/content-manager-Dn4tqDw1.d.mts.map +1 -0
- package/dist/{context-DjglDPvj.mjs → context-C3x6I3cE.mjs} +4 -4
- package/dist/{context-DjglDPvj.mjs.map → context-C3x6I3cE.mjs.map} +1 -1
- package/dist/core/apply-manager.d.mts +8 -0
- package/dist/core/apply-manager.d.mts.map +1 -1
- package/dist/core/apply-manager.mjs +10 -9
- package/dist/core/config.d.mts +1 -1
- package/dist/core/config.mjs +1 -1
- package/dist/core/content-manager.d.mts +2 -2
- package/dist/core/content-manager.mjs +2 -2
- package/dist/core/context.d.mts +1 -1
- package/dist/core/context.mjs +5 -5
- package/dist/core/contracts/index.d.mts +2 -2
- package/dist/core/doctor.mjs +7 -6
- package/dist/core/graph-builder.mjs +2 -2
- package/dist/core/meta-manager.d.mts +8 -9
- package/dist/core/meta-manager.d.mts.map +1 -1
- package/dist/core/meta-manager.mjs +2 -2
- package/dist/core/model-manager.d.mts +39 -3
- package/dist/core/model-manager.d.mts.map +1 -1
- package/dist/core/model-manager.mjs +3 -3
- package/dist/core/ops/index.d.mts +40 -3
- package/dist/core/ops/index.d.mts.map +1 -1
- package/dist/core/ops/index.mjs +4 -4
- package/dist/core/overlay-reader.d.mts +1 -1
- package/dist/core/overlay-reader.mjs +1 -1
- package/dist/core/scan-config.mjs +1 -1
- package/dist/core/scanner.mjs +3 -3
- package/dist/core/validator/index.d.mts +1 -1
- package/dist/core/validator/index.d.mts.map +1 -1
- package/dist/core/validator/index.mjs +4 -4
- package/dist/{detect-CdDU4Ew_.mjs → detect-B0oXbQaP.mjs} +1 -1
- package/dist/{detect-CdDU4Ew_.mjs.map → detect-B0oXbQaP.mjs.map} +1 -1
- package/dist/{doctor-BwJ_nmqS.mjs → doctor-DNPuGgcg.mjs} +28 -15
- package/dist/doctor-DNPuGgcg.mjs.map +1 -0
- package/dist/{errors-e0YdjooK.mjs → errors-CvBS12e7.mjs} +1 -1
- package/dist/{errors-e0YdjooK.mjs.map → errors-CvBS12e7.mjs.map} +1 -1
- package/dist/fs-DLbVB-Ek.mjs.map +1 -1
- package/dist/git/branch-lifecycle.d.mts +1 -1
- package/dist/git/branch-lifecycle.mjs +3 -2
- package/dist/git/identity.d.mts +42 -0
- package/dist/git/identity.d.mts.map +1 -0
- package/dist/git/identity.mjs +2 -0
- package/dist/git/transaction.d.mts +1 -1
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +9 -8
- package/dist/{graph-builder-DK4Mh8Tn.mjs → graph-builder-BEocwW13.mjs} +2 -2
- package/dist/{graph-builder-DK4Mh8Tn.mjs.map → graph-builder-BEocwW13.mjs.map} +1 -1
- package/dist/{id-DV_T9Ic8.mjs → id-BjhaIxTL.mjs} +1 -1
- package/dist/{id-DV_T9Ic8.mjs.map → id-BjhaIxTL.mjs.map} +1 -1
- package/dist/identity-D8QoGxhT.mjs +125 -0
- package/dist/identity-D8QoGxhT.mjs.map +1 -0
- package/dist/{index-w8QHThNS.d.mts → index-BWbTJlXm.d.mts} +2 -2
- package/dist/index.mjs +21 -20
- package/dist/index.mjs.map +1 -1
- package/dist/{local-8dJ5NIWL.mjs → local-Db5X9mjZ.mjs} +25 -13
- package/dist/local-Db5X9mjZ.mjs.map +1 -0
- package/dist/{meta-manager-CJUiTgP2.mjs → meta-manager-B_Se183X.mjs} +29 -5
- package/dist/meta-manager-B_Se183X.mjs.map +1 -0
- package/dist/{model-manager-DP2CZiMT.mjs → model-manager-DgyKMjLN.mjs} +169 -25
- package/dist/model-manager-DgyKMjLN.mjs.map +1 -0
- package/dist/{node-BxujauDw.mjs → node-DUGiIkv1.mjs} +1 -1
- package/dist/{node-BxujauDw.mjs.map → node-DUGiIkv1.mjs.map} +1 -1
- package/dist/{ops-CGKIKwDc.mjs → ops-BzbThweu.mjs} +165 -26
- package/dist/ops-BzbThweu.mjs.map +1 -0
- package/dist/{overlay-reader-DNaVsgiS.mjs → overlay-reader-CkU_pGXZ.mjs} +1 -1
- package/dist/{overlay-reader-DNaVsgiS.mjs.map → overlay-reader-CkU_pGXZ.mjs.map} +1 -1
- package/dist/{paths-enT2coeX.mjs → paths-CdPPWpbG.mjs} +1 -1
- package/dist/{paths-enT2coeX.mjs.map → paths-CdPPWpbG.mjs.map} +1 -1
- package/dist/providers/github/index.d.mts +1 -1
- package/dist/providers/github/index.mjs +2 -2
- package/dist/providers/gitlab/index.d.mts +1 -1
- package/dist/providers/gitlab/index.mjs +2 -2
- package/dist/providers/local/index.d.mts +7 -1
- package/dist/providers/local/index.d.mts.map +1 -1
- package/dist/providers/local/index.mjs +11 -10
- package/dist/{scan-config-BlNLRCMx.mjs → scan-config-Dx49nCqk.mjs} +1 -1
- package/dist/{scan-config-BlNLRCMx.mjs.map → scan-config-Dx49nCqk.mjs.map} +1 -1
- package/dist/{scanner-CGWhmpDz.mjs → scanner-BSvXjfkp.mjs} +6 -6
- package/dist/{scanner-CGWhmpDz.mjs.map → scanner-BSvXjfkp.mjs.map} +1 -1
- package/dist/serialization-tmgfFBOh.mjs +2 -0
- package/dist/server/http/index.d.mts +1 -1
- package/dist/server/http/index.mjs +21 -20
- package/dist/server/http/index.mjs.map +1 -1
- package/dist/{server-DLauBiu2.d.mts → server-CLPNnhYL.d.mts} +2 -2
- package/dist/{server-DLauBiu2.d.mts.map → server-CLPNnhYL.d.mts.map} +1 -1
- package/dist/{server-CUZnuHQx.mjs → server-D-zJWbdI.mjs} +313 -115
- package/dist/server-D-zJWbdI.mjs.map +1 -0
- package/dist/server.d.mts +1 -1
- package/dist/server.mjs +21 -20
- package/dist/{source-map-generator-Bvbu-kM5.mjs → source-map-generator-DhjiQdV3.mjs} +1 -1
- package/dist/{source-map-generator-Bvbu-kM5.mjs.map → source-map-generator-DhjiQdV3.mjs.map} +1 -1
- package/dist/{svelte-parser-DaFK9iY0.mjs → svelte-parser-CwySu9yn.mjs} +3 -3
- package/dist/{svelte-parser-DaFK9iY0.mjs.map → svelte-parser-CwySu9yn.mjs.map} +1 -1
- package/dist/templates/index.mjs +1 -1
- package/dist/{templates-D6EllixQ.mjs → templates-CAkTIRkE.mjs} +430 -109
- package/dist/templates-CAkTIRkE.mjs.map +1 -0
- package/dist/testing/memory-provider.d.mts +52 -0
- package/dist/testing/memory-provider.d.mts.map +1 -0
- package/dist/testing/memory-provider.mjs +205 -0
- package/dist/testing/memory-provider.mjs.map +1 -0
- package/dist/tools/annotations.d.mts.map +1 -1
- package/dist/tools/annotations.mjs +1 -1
- package/dist/tools/availability.mjs +1 -1
- package/dist/{transaction-1SPznNt3.mjs → transaction-BW9ektWu.mjs} +13 -12
- package/dist/transaction-BW9ektWu.mjs.map +1 -0
- package/dist/{tsx-parser-md1N0Niu.mjs → tsx-parser-BvsK6lyG.mjs} +1 -1
- package/dist/{tsx-parser-md1N0Niu.mjs.map → tsx-parser-BvsK6lyG.mjs.map} +1 -1
- package/dist/tsx-parser-Byc7HlJ8.mjs +2 -0
- package/dist/util/detect.mjs +1 -1
- package/dist/util/fs.d.mts +1 -1
- package/dist/util/fs.d.mts.map +1 -1
- package/dist/util/id.mjs +1 -1
- package/dist/{validator-ChiYk6ap.mjs → validator-0sJ9vh1j.mjs} +65 -8
- package/dist/validator-0sJ9vh1j.mjs.map +1 -0
- package/dist/{vue-parser-CHxDohd8.mjs → vue-parser-auedZiJG.mjs} +3 -3
- package/dist/{vue-parser-CHxDohd8.mjs.map → vue-parser-auedZiJG.mjs.map} +1 -1
- package/package.json +13 -5
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/meta/blog/en.json +1 -0
- package/testing/conformance/01-new-collection-entry/expected/.contentrain/models/blog.json +6 -5
- package/testing/conformance/01-new-collection-entry/setup/.contentrain/models/blog.json +6 -5
- package/testing/conformance/02-new-singleton/expected/.contentrain/meta/hero/en.json +1 -0
- package/testing/conformance/02-new-singleton/expected/.contentrain/models/hero.json +6 -5
- package/testing/conformance/02-new-singleton/setup/.contentrain/models/hero.json +6 -5
- package/testing/conformance/03-new-dictionary/expected/.contentrain/meta/ui-strings/en.json +1 -0
- package/testing/conformance/03-new-dictionary/expected/.contentrain/models/ui-strings.json +4 -3
- package/testing/conformance/03-new-dictionary/setup/.contentrain/models/ui-strings.json +4 -3
- package/dist/annotations-D3tlsF38.mjs.map +0 -1
- package/dist/apply-manager-SLCRLHN_.mjs.map +0 -1
- package/dist/branch-lifecycle-BAfgSQBv.mjs.map +0 -1
- package/dist/content-manager-DWo3G64y.d.mts.map +0 -1
- package/dist/doctor-BwJ_nmqS.mjs.map +0 -1
- package/dist/local-8dJ5NIWL.mjs.map +0 -1
- package/dist/meta-manager-CJUiTgP2.mjs.map +0 -1
- package/dist/model-manager-DP2CZiMT.mjs.map +0 -1
- package/dist/ops-CGKIKwDc.mjs.map +0 -1
- package/dist/serialization-B1CEzR4H.mjs +0 -2
- package/dist/server-CUZnuHQx.mjs.map +0 -1
- package/dist/templates-D6EllixQ.mjs.map +0 -1
- package/dist/transaction-1SPznNt3.mjs.map +0 -1
- package/dist/tsx-parser-C3XsIrwU.mjs +0 -2
- package/dist/validator-ChiYk6ap.mjs.map +0 -1
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import { i as
|
|
1
|
+
import { a as planContentSave, i as planModelDelete, n as planVocabularySave, o as planContentDelete, r as planModelSave, t as planVocabularyDelete } from "./ops-BzbThweu.mjs";
|
|
2
2
|
import { n as contentFilePath, t as contentDirPath } from "./paths-CmVw5Cw2.mjs";
|
|
3
3
|
import { a as readJson, n as ensureDir, r as pathExists, s as writeJson, t as contentrainDir } from "./fs-DLbVB-Ek.mjs";
|
|
4
|
-
import { a as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { n as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { n as
|
|
11
|
-
import { n as
|
|
12
|
-
import { t as
|
|
13
|
-
import { t as
|
|
14
|
-
import {
|
|
15
|
-
import { n as
|
|
16
|
-
import {
|
|
17
|
-
import { t as
|
|
18
|
-
import { t as
|
|
19
|
-
import {
|
|
20
|
-
import { n as
|
|
21
|
-
import {
|
|
4
|
+
import { a as writeMeta, i as readMeta, o as writeMetaEntries, r as mergeEntryMeta, t as applyStatusChange } from "./meta-manager-B_Se183X.mjs";
|
|
5
|
+
import { r as createGit } from "./identity-D8QoGxhT.mjs";
|
|
6
|
+
import { n as readVocabulary, t as readConfig } from "./config-VlRKRwAS.mjs";
|
|
7
|
+
import { a as deleteRemoteBranch, c as pruneMergedRemoteBranches, i as cleanupMergedBranches, n as checkBranchHealth, s as listRemoteCrBranches } from "./branch-lifecycle-B6PXZ2N7.mjs";
|
|
8
|
+
import { T as writeContent, _ as resolveContentDir, b as resolveMdFilePath, c as listModels, d as validateModelDefinition, f as writeModel, i as countEntries, l as readModel, m as listContent, o as fieldDefZodSchema, p as deleteContent, r as checkReferences, v as resolveJsonFilePath, y as resolveLocaleStrategy } from "./model-manager-DgyKMjLN.mjs";
|
|
9
|
+
import { n as readContext } from "./context-C3x6I3cE.mjs";
|
|
10
|
+
import { i as mergeBranch, n as createTransaction, r as ensureContentBranch, t as buildBranchName } from "./transaction-BW9ektWu.mjs";
|
|
11
|
+
import { n as isMerged, t as LocalProvider } from "./local-Db5X9mjZ.mjs";
|
|
12
|
+
import { n as isToolAvailable, t as TOOL_REQUIREMENTS } from "./availability-Dgcmvv99.mjs";
|
|
13
|
+
import { t as detectStack } from "./detect-B0oXbQaP.mjs";
|
|
14
|
+
import { t as TOOL_ANNOTATIONS } from "./annotations-C-tQVmot.mjs";
|
|
15
|
+
import { n as listTemplates, t as getTemplate } from "./templates-CAkTIRkE.mjs";
|
|
16
|
+
import { n as mapProviderError } from "./errors-CvBS12e7.mjs";
|
|
17
|
+
import { t as validateProject } from "./validator-0sJ9vh1j.mjs";
|
|
18
|
+
import { t as OverlayReader } from "./overlay-reader-CkU_pGXZ.mjs";
|
|
19
|
+
import { t as buildGraph } from "./graph-builder-BEocwW13.mjs";
|
|
20
|
+
import { n as scanSummary, t as scanCandidates } from "./scanner-BSvXjfkp.mjs";
|
|
21
|
+
import { n as applyExtract, r as applyReuse } from "./apply-manager-BybvnUVC.mjs";
|
|
22
|
+
import { t as runDoctor } from "./doctor-DNPuGgcg.mjs";
|
|
22
23
|
import { join } from "node:path";
|
|
23
24
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24
25
|
import { CONTENTRAIN_BRANCH } from "@contentrain/types";
|
|
25
26
|
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
26
|
-
import
|
|
27
|
+
import "simple-git";
|
|
27
28
|
import { z } from "zod";
|
|
28
29
|
//#region src/tools/context.ts
|
|
29
30
|
function registerContextTools(server, provider, projectRoot) {
|
|
@@ -127,6 +128,7 @@ function registerContextTools(server, provider, projectRoot) {
|
|
|
127
128
|
kind: modelDef.kind,
|
|
128
129
|
domain: modelDef.domain,
|
|
129
130
|
i18n: modelDef.i18n,
|
|
131
|
+
title_field: modelDef.title_field,
|
|
130
132
|
description: modelDef.description,
|
|
131
133
|
fields: modelDef.fields,
|
|
132
134
|
stats: {
|
|
@@ -167,6 +169,24 @@ function registerContextTools(server, provider, projectRoot) {
|
|
|
167
169
|
".contentrain/meta/{model-id}/": "Metadata files per model (status, source, timestamps)"
|
|
168
170
|
}
|
|
169
171
|
},
|
|
172
|
+
model_definition: {
|
|
173
|
+
description: "Shape of .contentrain/models/{model-id}.json. Keys are written in this order.",
|
|
174
|
+
required: {
|
|
175
|
+
id: "Kebab-case model ID, matching the file name.",
|
|
176
|
+
name: "Human-readable display name.",
|
|
177
|
+
kind: "One of: singleton, collection, document, dictionary.",
|
|
178
|
+
domain: "Organizational group; maps to the content subdirectory.",
|
|
179
|
+
i18n: "Whether the model stores content per locale.",
|
|
180
|
+
title_field: "Name of the field shown as an entry title in listings, pickers and relation references. Must name a field declared on this model whose type is one of: string, text, slug, email, url, code, markdown, richtext. Dictionary models have no fields — they use the reserved value \"key\", meaning the entry key is the title."
|
|
181
|
+
},
|
|
182
|
+
optional: {
|
|
183
|
+
description: "Model description for documentation and agent context.",
|
|
184
|
+
fields: "Field definitions. Dictionary models must NOT declare fields.",
|
|
185
|
+
content_path: "Framework-relative path for content files, replacing .contentrain/content/.",
|
|
186
|
+
locale_strategy: "How locale is encoded in file names: file (default), suffix, directory, none."
|
|
187
|
+
},
|
|
188
|
+
note: "title_field is declared, never inferred. A consumer that guesses the title from field order or length picks the icon over the headline — set it explicitly on create AND keep it correct when renaming or removing fields."
|
|
189
|
+
},
|
|
170
190
|
model_kinds: {
|
|
171
191
|
collection: {
|
|
172
192
|
description: "Multiple entries stored as an object-map keyed by entry ID",
|
|
@@ -269,12 +289,27 @@ function registerContextTools(server, provider, projectRoot) {
|
|
|
269
289
|
file: ".contentrain/vocabulary.json",
|
|
270
290
|
format: {
|
|
271
291
|
version: "number",
|
|
272
|
-
terms: "Record<
|
|
292
|
+
terms: "Record<term-slug, Record<locale, translation>>"
|
|
293
|
+
},
|
|
294
|
+
example: {
|
|
295
|
+
version: 1,
|
|
296
|
+
terms: {
|
|
297
|
+
"sign-in": {
|
|
298
|
+
en: "Sign in",
|
|
299
|
+
tr: "Giriş yap"
|
|
300
|
+
},
|
|
301
|
+
"add-to-cart": {
|
|
302
|
+
en: "Add to cart",
|
|
303
|
+
tr: "Sepete ekle"
|
|
304
|
+
}
|
|
305
|
+
}
|
|
273
306
|
},
|
|
307
|
+
note: "OUTER key is the term (kebab-case, locale-independent); INNER key is a locale code. One term, one entry, all its translations inside. Grouping by locale at the top level is the common mistake and produces a vocabulary that matches nothing.",
|
|
274
308
|
usage: [
|
|
275
309
|
"Check vocabulary before creating dictionary entries — reuse canonical terms",
|
|
276
310
|
"If a new term is needed, consider adding it to vocabulary first",
|
|
277
|
-
"Vocabulary applies across ALL dictionary models and ALL locales"
|
|
311
|
+
"Vocabulary applies across ALL dictionary models and ALL locales",
|
|
312
|
+
"Write it with contentrain_vocabulary_save — never edit the file by hand"
|
|
278
313
|
]
|
|
279
314
|
}
|
|
280
315
|
}, null, 2)
|
|
@@ -471,7 +506,7 @@ function registerSetupTools(server, _provider, projectRoot) {
|
|
|
471
506
|
const supportedLocales = locales ?? ["en"];
|
|
472
507
|
const suggestedDomains = domains ?? suggestDomains(projectRoot);
|
|
473
508
|
const hasGit = await pathExists(join(projectRoot, ".git"));
|
|
474
|
-
const git =
|
|
509
|
+
const git = createGit(projectRoot);
|
|
475
510
|
if (!hasGit) await git.init();
|
|
476
511
|
if (!await git.raw([
|
|
477
512
|
"rev-list",
|
|
@@ -631,6 +666,20 @@ function registerSetupTools(server, _provider, projectRoot) {
|
|
|
631
666
|
const modelsCreated = [];
|
|
632
667
|
let contentCreated = 0;
|
|
633
668
|
let vocabAdded = 0;
|
|
669
|
+
const templateErrors = tmpl.models.flatMap((m) => validateModelDefinition(m).errors.map((e) => `[${m.id}] ${e}`));
|
|
670
|
+
if (templateErrors.length > 0) {
|
|
671
|
+
await tx.cleanup();
|
|
672
|
+
return {
|
|
673
|
+
content: [{
|
|
674
|
+
type: "text",
|
|
675
|
+
text: JSON.stringify({
|
|
676
|
+
error: `Scaffold template "${templateId}" is invalid`,
|
|
677
|
+
details: templateErrors
|
|
678
|
+
}, null, 2)
|
|
679
|
+
}],
|
|
680
|
+
isError: true
|
|
681
|
+
};
|
|
682
|
+
}
|
|
634
683
|
await tx.write(async (wt) => {
|
|
635
684
|
await Promise.all(tmpl.models.map(async (model) => {
|
|
636
685
|
await writeModel(wt, model);
|
|
@@ -752,12 +801,24 @@ async function mergeVocabulary(projectRoot, newTerms, _locales) {
|
|
|
752
801
|
//#endregion
|
|
753
802
|
//#region src/tools/commit-plan.ts
|
|
754
803
|
/**
|
|
804
|
+
* Context payload describing the operation, threaded into the local
|
|
805
|
+
* transaction so it can regenerate `.contentrain/context.json` on the
|
|
806
|
+
* contentrain branch after merge. Kept as a loose object so tool-specific
|
|
807
|
+
* payloads can add optional fields (`locale`, `entries`) without churning
|
|
808
|
+
* the helper's signature. Remote providers ignore it — see
|
|
809
|
+
* `commitThroughProvider` below.
|
|
810
|
+
*/
|
|
811
|
+
const COMMIT_AUTHOR = {
|
|
812
|
+
name: "Contentrain",
|
|
813
|
+
email: "ai@contentrain.io"
|
|
814
|
+
};
|
|
815
|
+
/**
|
|
755
816
|
* Commit a plan's changes through whichever provider is wired into the
|
|
756
817
|
* tool handler. Encapsulates the LocalProvider vs remote RepoProvider
|
|
757
818
|
* dispatch that every write-side tool (content_save, content_delete,
|
|
758
819
|
* model_save, model_delete) used to inline:
|
|
759
820
|
*
|
|
760
|
-
* - **
|
|
821
|
+
* - **localWorktree providers** — go through the worktree-backed transaction.
|
|
761
822
|
* `context` payload is threaded as an extra write-through and the
|
|
762
823
|
* transaction layer decides `auto-merged` vs `pending-review` based on
|
|
763
824
|
* the project's configured workflow. Selective-sync result is surfaced
|
|
@@ -780,16 +841,17 @@ async function mergeVocabulary(projectRoot, newTerms, _locales) {
|
|
|
780
841
|
*/
|
|
781
842
|
async function commitThroughProvider(provider, input) {
|
|
782
843
|
const { branch, changes, message, contextPayload } = input;
|
|
783
|
-
if (provider
|
|
844
|
+
if (provider.capabilities.localWorktree) {
|
|
784
845
|
const result = await provider.applyPlan({
|
|
785
846
|
branch,
|
|
786
847
|
changes,
|
|
787
848
|
message,
|
|
849
|
+
author: COMMIT_AUTHOR,
|
|
788
850
|
context: contextPayload
|
|
789
851
|
});
|
|
790
852
|
return {
|
|
791
853
|
commitSha: result.sha,
|
|
792
|
-
workflowAction: result.workflowAction,
|
|
854
|
+
workflowAction: result.workflowAction ?? "pending-review",
|
|
793
855
|
sync: result.sync
|
|
794
856
|
};
|
|
795
857
|
}
|
|
@@ -799,10 +861,7 @@ async function commitThroughProvider(provider, input) {
|
|
|
799
861
|
branch,
|
|
800
862
|
changes: allChanges,
|
|
801
863
|
message,
|
|
802
|
-
author:
|
|
803
|
-
name: "Contentrain",
|
|
804
|
-
email: "ai@contentrain.io"
|
|
805
|
-
},
|
|
864
|
+
author: COMMIT_AUTHOR,
|
|
806
865
|
base: CONTENTRAIN_BRANCH
|
|
807
866
|
})).sha,
|
|
808
867
|
workflowAction: "pending-review"
|
|
@@ -823,6 +882,7 @@ function registerModelTools(server, provider, projectRoot) {
|
|
|
823
882
|
]).describe("Model kind"),
|
|
824
883
|
domain: z.string().describe("Content domain (e.g. \"blog\", \"marketing\", \"system\")"),
|
|
825
884
|
i18n: z.boolean().describe("Whether this model supports localization"),
|
|
885
|
+
title_field: z.string().describe("Name of the field shown as an entry's title in listings, pickers and relation references. Must name a field on this model whose type is string, text, slug, email, url, code, markdown or richtext. Dictionary models have no fields — use \"key\"."),
|
|
826
886
|
description: z.string().optional().describe("Model description"),
|
|
827
887
|
fields: fieldDefSchema.optional().describe("Field definitions (not needed for dictionary)"),
|
|
828
888
|
content_path: z.string().optional().describe("Framework-relative path for content files (e.g. \"content/blog\", \"locales\"). When set, content is written here instead of .contentrain/content/"),
|
|
@@ -859,26 +919,25 @@ function registerModelTools(server, provider, projectRoot) {
|
|
|
859
919
|
}],
|
|
860
920
|
isError: true
|
|
861
921
|
};
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
};
|
|
875
|
-
}
|
|
922
|
+
const readiness = await provider.checkWriteReadiness?.();
|
|
923
|
+
if (readiness?.blocked) return {
|
|
924
|
+
content: [{
|
|
925
|
+
type: "text",
|
|
926
|
+
text: JSON.stringify({
|
|
927
|
+
error: readiness.message,
|
|
928
|
+
action: "blocked",
|
|
929
|
+
hint: "Merge or delete old contentrain/* branches before creating new ones."
|
|
930
|
+
}, null, 2)
|
|
931
|
+
}],
|
|
932
|
+
isError: true
|
|
933
|
+
};
|
|
876
934
|
const model = {
|
|
877
935
|
id: input.id,
|
|
878
936
|
name: input.name,
|
|
879
937
|
kind: input.kind,
|
|
880
938
|
domain: input.domain,
|
|
881
939
|
i18n: input.i18n,
|
|
940
|
+
title_field: input.title_field,
|
|
882
941
|
description: input.description,
|
|
883
942
|
fields: input.fields,
|
|
884
943
|
content_path: input.content_path,
|
|
@@ -969,20 +1028,18 @@ function registerModelTools(server, provider, projectRoot) {
|
|
|
969
1028
|
}],
|
|
970
1029
|
isError: true
|
|
971
1030
|
};
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
};
|
|
985
|
-
}
|
|
1031
|
+
const deleteReadiness = await provider.checkWriteReadiness?.();
|
|
1032
|
+
if (deleteReadiness?.blocked) return {
|
|
1033
|
+
content: [{
|
|
1034
|
+
type: "text",
|
|
1035
|
+
text: JSON.stringify({
|
|
1036
|
+
error: deleteReadiness.message,
|
|
1037
|
+
action: "blocked",
|
|
1038
|
+
hint: "Merge or delete old contentrain/* branches before creating new ones."
|
|
1039
|
+
}, null, 2)
|
|
1040
|
+
}],
|
|
1041
|
+
isError: true
|
|
1042
|
+
};
|
|
986
1043
|
const refs = await checkReferences(provider, modelId);
|
|
987
1044
|
if (refs.length > 0) return { content: [{
|
|
988
1045
|
type: "text",
|
|
@@ -1111,20 +1168,18 @@ function registerContentTools(server, provider, projectRoot) {
|
|
|
1111
1168
|
if (entry.publish_at !== void 0) entry.data["publish_at"] = entry.publish_at;
|
|
1112
1169
|
if (entry.expire_at !== void 0) entry.data["expire_at"] = entry.expire_at;
|
|
1113
1170
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
};
|
|
1127
|
-
}
|
|
1171
|
+
const readiness = await provider.checkWriteReadiness?.();
|
|
1172
|
+
if (readiness?.blocked) return {
|
|
1173
|
+
content: [{
|
|
1174
|
+
type: "text",
|
|
1175
|
+
text: JSON.stringify({
|
|
1176
|
+
error: readiness.message,
|
|
1177
|
+
action: "blocked",
|
|
1178
|
+
hint: "Merge or delete old contentrain/* branches before creating new ones."
|
|
1179
|
+
}, null, 2)
|
|
1180
|
+
}],
|
|
1181
|
+
isError: true
|
|
1182
|
+
};
|
|
1128
1183
|
const vocabulary = await readVocabulary(provider);
|
|
1129
1184
|
let plan;
|
|
1130
1185
|
try {
|
|
@@ -1244,20 +1299,18 @@ function registerContentTools(server, provider, projectRoot) {
|
|
|
1244
1299
|
}],
|
|
1245
1300
|
isError: true
|
|
1246
1301
|
};
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1302
|
+
const deleteReadiness = await provider.checkWriteReadiness?.();
|
|
1303
|
+
if (deleteReadiness?.blocked) return {
|
|
1304
|
+
content: [{
|
|
1305
|
+
type: "text",
|
|
1306
|
+
text: JSON.stringify({
|
|
1307
|
+
error: deleteReadiness.message,
|
|
1308
|
+
action: "blocked",
|
|
1309
|
+
hint: "Merge or delete old contentrain/* branches before creating new ones."
|
|
1310
|
+
}, null, 2)
|
|
1311
|
+
}],
|
|
1312
|
+
isError: true
|
|
1313
|
+
};
|
|
1261
1314
|
let deletePlan;
|
|
1262
1315
|
try {
|
|
1263
1316
|
deletePlan = await planContentDelete(provider, {
|
|
@@ -1401,6 +1454,162 @@ function touchesSavedEntries(issue, results) {
|
|
|
1401
1454
|
});
|
|
1402
1455
|
}
|
|
1403
1456
|
//#endregion
|
|
1457
|
+
//#region src/tools/vocabulary.ts
|
|
1458
|
+
/**
|
|
1459
|
+
* Vocabulary write tools.
|
|
1460
|
+
*
|
|
1461
|
+
* The vocabulary is the one piece of `.contentrain/` that had no tool. The
|
|
1462
|
+
* rules forbid hand-editing that directory, so the only way to add a canonical
|
|
1463
|
+
* term was to break the rule — which a field report duly did, having no
|
|
1464
|
+
* alternative.
|
|
1465
|
+
*
|
|
1466
|
+
* These go through the same plan → commit path as content and models, so a
|
|
1467
|
+
* vocabulary change lands on a `cr/*` branch and obeys the project's workflow
|
|
1468
|
+
* like everything else.
|
|
1469
|
+
*/
|
|
1470
|
+
function registerVocabularyTools(server, provider) {
|
|
1471
|
+
server.tool("contentrain_vocabulary_save", "Add or update canonical vocabulary terms. Terms nest as { \"term-slug\": { \"en\": \"…\", \"tr\": \"…\" } } — the OUTER key is the term, the INNER key is a locale. Merges with the existing vocabulary: a term you omit is untouched. Changes are auto-committed to git — do NOT manually edit .contentrain/vocabulary.json.", { terms: z.record(z.string(), z.record(z.string(), z.string())).describe("Terms keyed by kebab-case term slug, each holding its translations keyed by locale code. Example: { \"sign-in\": { \"en\": \"Sign in\", \"tr\": \"Giriş yap\" } }") }, TOOL_ANNOTATIONS["contentrain_vocabulary_save"], async (input) => {
|
|
1472
|
+
if (!await readConfig(provider)) return {
|
|
1473
|
+
content: [{
|
|
1474
|
+
type: "text",
|
|
1475
|
+
text: JSON.stringify({ error: "Project not initialized. Run contentrain_init first." })
|
|
1476
|
+
}],
|
|
1477
|
+
isError: true
|
|
1478
|
+
};
|
|
1479
|
+
const readiness = await provider.checkWriteReadiness?.();
|
|
1480
|
+
if (readiness?.blocked) return {
|
|
1481
|
+
content: [{
|
|
1482
|
+
type: "text",
|
|
1483
|
+
text: JSON.stringify({
|
|
1484
|
+
error: readiness.message,
|
|
1485
|
+
action: "blocked",
|
|
1486
|
+
hint: "Merge or delete old contentrain/* branches before creating new ones."
|
|
1487
|
+
}, null, 2)
|
|
1488
|
+
}],
|
|
1489
|
+
isError: true
|
|
1490
|
+
};
|
|
1491
|
+
let plan;
|
|
1492
|
+
try {
|
|
1493
|
+
plan = await planVocabularySave(provider, { terms: input.terms });
|
|
1494
|
+
} catch (error) {
|
|
1495
|
+
return {
|
|
1496
|
+
content: [{
|
|
1497
|
+
type: "text",
|
|
1498
|
+
text: JSON.stringify({
|
|
1499
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1500
|
+
hint: "Terms nest as { \"term-slug\": { \"en\": \"…\" } }. Grouping by locale at the top level is the usual mistake."
|
|
1501
|
+
}, null, 2)
|
|
1502
|
+
}],
|
|
1503
|
+
isError: true
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
const termCount = plan.result.added.length + plan.result.updated.length;
|
|
1507
|
+
try {
|
|
1508
|
+
const commit = await commitThroughProvider(provider, {
|
|
1509
|
+
branch: buildBranchName("vocabulary", "save"),
|
|
1510
|
+
changes: plan.changes,
|
|
1511
|
+
message: `[contentrain] vocabulary: ${termCount} term(s)`,
|
|
1512
|
+
contextPayload: {
|
|
1513
|
+
tool: "contentrain_vocabulary_save",
|
|
1514
|
+
model: "vocabulary"
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
return { content: [{
|
|
1518
|
+
type: "text",
|
|
1519
|
+
text: JSON.stringify({
|
|
1520
|
+
status: "committed",
|
|
1521
|
+
added: plan.result.added,
|
|
1522
|
+
updated: plan.result.updated,
|
|
1523
|
+
total_terms: plan.result.total,
|
|
1524
|
+
commit: commit.commitSha,
|
|
1525
|
+
workflow_action: commit.workflowAction,
|
|
1526
|
+
...plan.advisories.length > 0 ? { advisories: plan.advisories } : {}
|
|
1527
|
+
}, null, 2)
|
|
1528
|
+
}] };
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
return {
|
|
1531
|
+
content: [{
|
|
1532
|
+
type: "text",
|
|
1533
|
+
text: JSON.stringify({ ...normalizeOperationError(error, "vocabulary_save") })
|
|
1534
|
+
}],
|
|
1535
|
+
isError: true
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
server.tool("contentrain_vocabulary_delete", "Remove canonical vocabulary terms by slug. Content already using a term is not touched — the vocabulary only advises. Changes are auto-committed to git.", {
|
|
1540
|
+
terms: z.array(z.string()).min(1).describe("Term slugs to remove"),
|
|
1541
|
+
confirm: z.boolean().describe("Must be true — this deletes canonical terms")
|
|
1542
|
+
}, TOOL_ANNOTATIONS["contentrain_vocabulary_delete"], async (input) => {
|
|
1543
|
+
if (!input.confirm) return {
|
|
1544
|
+
content: [{
|
|
1545
|
+
type: "text",
|
|
1546
|
+
text: JSON.stringify({ error: "Deleting vocabulary terms requires confirm: true" })
|
|
1547
|
+
}],
|
|
1548
|
+
isError: true
|
|
1549
|
+
};
|
|
1550
|
+
if (!await readConfig(provider)) return {
|
|
1551
|
+
content: [{
|
|
1552
|
+
type: "text",
|
|
1553
|
+
text: JSON.stringify({ error: "Project not initialized. Run contentrain_init first." })
|
|
1554
|
+
}],
|
|
1555
|
+
isError: true
|
|
1556
|
+
};
|
|
1557
|
+
const plan = await planVocabularyDelete(provider, { terms: input.terms });
|
|
1558
|
+
if (plan.changes.length === 0) return { content: [{
|
|
1559
|
+
type: "text",
|
|
1560
|
+
text: JSON.stringify({
|
|
1561
|
+
status: "no-op",
|
|
1562
|
+
deleted: [],
|
|
1563
|
+
missing: plan.result.missing,
|
|
1564
|
+
total_terms: plan.result.total,
|
|
1565
|
+
...plan.advisories.length > 0 ? { advisories: plan.advisories } : {}
|
|
1566
|
+
}, null, 2)
|
|
1567
|
+
}] };
|
|
1568
|
+
const readiness = await provider.checkWriteReadiness?.();
|
|
1569
|
+
if (readiness?.blocked) return {
|
|
1570
|
+
content: [{
|
|
1571
|
+
type: "text",
|
|
1572
|
+
text: JSON.stringify({
|
|
1573
|
+
error: readiness.message,
|
|
1574
|
+
action: "blocked"
|
|
1575
|
+
}, null, 2)
|
|
1576
|
+
}],
|
|
1577
|
+
isError: true
|
|
1578
|
+
};
|
|
1579
|
+
try {
|
|
1580
|
+
const commit = await commitThroughProvider(provider, {
|
|
1581
|
+
branch: buildBranchName("vocabulary", "delete"),
|
|
1582
|
+
changes: plan.changes,
|
|
1583
|
+
message: `[contentrain] vocabulary: remove ${plan.result.deleted.length} term(s)`,
|
|
1584
|
+
contextPayload: {
|
|
1585
|
+
tool: "contentrain_vocabulary_delete",
|
|
1586
|
+
model: "vocabulary"
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
return { content: [{
|
|
1590
|
+
type: "text",
|
|
1591
|
+
text: JSON.stringify({
|
|
1592
|
+
status: "committed",
|
|
1593
|
+
deleted: plan.result.deleted,
|
|
1594
|
+
missing: plan.result.missing,
|
|
1595
|
+
total_terms: plan.result.total,
|
|
1596
|
+
commit: commit.commitSha,
|
|
1597
|
+
workflow_action: commit.workflowAction,
|
|
1598
|
+
...plan.advisories.length > 0 ? { advisories: plan.advisories } : {}
|
|
1599
|
+
}, null, 2)
|
|
1600
|
+
}] };
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
return {
|
|
1603
|
+
content: [{
|
|
1604
|
+
type: "text",
|
|
1605
|
+
text: JSON.stringify({ ...normalizeOperationError(error, "vocabulary_delete") })
|
|
1606
|
+
}],
|
|
1607
|
+
isError: true
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
//#endregion
|
|
1404
1613
|
//#region src/tools/workflow.ts
|
|
1405
1614
|
/**
|
|
1406
1615
|
* Resolve a merge target from either an exact branch name or a
|
|
@@ -1446,9 +1655,9 @@ async function resolveMergeBranch(git, input) {
|
|
|
1446
1655
|
}))).toSorted((left, right) => right.t - left.t)[0].b };
|
|
1447
1656
|
}
|
|
1448
1657
|
function registerWorkflowTools(server, provider, projectRoot) {
|
|
1449
|
-
server.tool("contentrain_validate", "Validate project content against model schemas. Detects required field violations, type mismatches, broken relations, secret leaks, i18n parity issues, and more. If fix:true, auto-fixes structural issues (canonical sort, orphan meta, missing locale files, stray non-i18n meta layout) — do NOT manually edit .contentrain/ files.", {
|
|
1658
|
+
server.tool("contentrain_validate", "Validate project content against model schemas. Detects required field violations, type mismatches, broken relations, secret leaks, i18n parity issues, and more. If fix:true, auto-fixes structural issues (canonical sort, orphan meta, missing locale files, stray non-i18n meta layout) and backfills a missing model title_field — do NOT manually edit .contentrain/ files.", {
|
|
1450
1659
|
model: z.string().optional().describe("Model ID to validate (omit for all models)"),
|
|
1451
|
-
fix: z.boolean().optional().describe("Auto-fix structural issues (canonical sort, orphan meta, missing locale files, stray non-i18n meta layout). Default: false")
|
|
1660
|
+
fix: z.boolean().optional().describe("Auto-fix structural issues (canonical sort, orphan meta, missing locale files, stray non-i18n meta layout) and backfill a missing model title_field. Each backfilled title_field is reported as a notice naming the rule that chose it. Default: false")
|
|
1452
1661
|
}, TOOL_ANNOTATIONS["contentrain_validate"], async (input) => {
|
|
1453
1662
|
if (input.fix && !projectRoot) return capabilityError("contentrain_validate", "localWorktree");
|
|
1454
1663
|
if (!await readConfig(provider)) return {
|
|
@@ -1559,7 +1768,7 @@ function registerWorkflowTools(server, provider, projectRoot) {
|
|
|
1559
1768
|
}],
|
|
1560
1769
|
isError: true
|
|
1561
1770
|
};
|
|
1562
|
-
const git =
|
|
1771
|
+
const git = createGit(projectRoot);
|
|
1563
1772
|
const remoteName = process.env["CONTENTRAIN_REMOTE"] ?? "origin";
|
|
1564
1773
|
let hasRemote = false;
|
|
1565
1774
|
try {
|
|
@@ -1687,7 +1896,7 @@ function registerWorkflowTools(server, provider, projectRoot) {
|
|
|
1687
1896
|
isError: true
|
|
1688
1897
|
};
|
|
1689
1898
|
try {
|
|
1690
|
-
const resolved = await resolveMergeBranch(
|
|
1899
|
+
const resolved = await resolveMergeBranch(createGit(projectRoot), input);
|
|
1691
1900
|
if ("error" in resolved) return {
|
|
1692
1901
|
content: [{
|
|
1693
1902
|
type: "text",
|
|
@@ -1734,7 +1943,7 @@ function registerWorkflowTools(server, provider, projectRoot) {
|
|
|
1734
1943
|
}, TOOL_ANNOTATIONS["contentrain_branch_list"], async (input) => {
|
|
1735
1944
|
if (!provider.capabilities.localWorktree || !projectRoot) return capabilityError("contentrain_branch_list", "localWorktree");
|
|
1736
1945
|
try {
|
|
1737
|
-
const git =
|
|
1946
|
+
const git = createGit(projectRoot);
|
|
1738
1947
|
const summary = await git.branchLocal();
|
|
1739
1948
|
const crBranches = summary.all.filter((b) => b.startsWith("cr/") && b !== CONTENTRAIN_BRANCH);
|
|
1740
1949
|
const remoteList = input.remote ? await listRemoteCrBranches(projectRoot) : void 0;
|
|
@@ -1796,7 +2005,7 @@ function registerWorkflowTools(server, provider, projectRoot) {
|
|
|
1796
2005
|
isError: true
|
|
1797
2006
|
};
|
|
1798
2007
|
try {
|
|
1799
|
-
const git =
|
|
2008
|
+
const git = createGit(projectRoot);
|
|
1800
2009
|
if (!(await git.branchLocal()).all.includes(input.branch)) {
|
|
1801
2010
|
const remoteOnly = await deleteRemoteBranch(projectRoot, input.branch);
|
|
1802
2011
|
if (remoteOnly.deleted) return { content: [{
|
|
@@ -1964,6 +2173,7 @@ function registerNormalizeTools(server, provider, projectRoot) {
|
|
|
1964
2173
|
]).describe("Model kind"),
|
|
1965
2174
|
domain: z.string().describe("Content domain (e.g. \"marketing\", \"app\")"),
|
|
1966
2175
|
i18n: z.boolean().optional().describe("Enable i18n. Default: true"),
|
|
2176
|
+
title_field: z.string().optional().describe("Field shown as an entry title. Inferred from the extracted fields when omitted; the dry run reports what was chosen. Dictionary models: \"key\"."),
|
|
1967
2177
|
fields: fieldDefZodSchema.optional().describe("Field definitions — shared schema with model_save for full parity"),
|
|
1968
2178
|
entries: z.array(z.object({
|
|
1969
2179
|
locale: z.string().optional().describe("Locale. Default: project default"),
|
|
@@ -2198,11 +2408,7 @@ function registerBulkTools(server, _provider, projectRoot) {
|
|
|
2198
2408
|
if (model.kind === "collection") {
|
|
2199
2409
|
const entries = sourceData;
|
|
2200
2410
|
const updates = {};
|
|
2201
|
-
for (const entryId of Object.keys(entries)) updates[entryId] =
|
|
2202
|
-
status: "draft",
|
|
2203
|
-
source: "agent",
|
|
2204
|
-
updated_by: "contentrain-mcp"
|
|
2205
|
-
};
|
|
2411
|
+
for (const entryId of Object.keys(entries)) updates[entryId] = mergeEntryMeta(void 0);
|
|
2206
2412
|
copiedCount = (await writeMetaEntries(wt, model, {
|
|
2207
2413
|
locale: input.target_locale,
|
|
2208
2414
|
defaultLocale: config.locales.default
|
|
@@ -2211,11 +2417,7 @@ function registerBulkTools(server, _provider, projectRoot) {
|
|
|
2211
2417
|
await writeMeta(wt, model, {
|
|
2212
2418
|
locale: input.target_locale,
|
|
2213
2419
|
defaultLocale: config.locales.default
|
|
2214
|
-
},
|
|
2215
|
-
status: "draft",
|
|
2216
|
-
source: "agent",
|
|
2217
|
-
updated_by: "contentrain-mcp"
|
|
2218
|
-
});
|
|
2420
|
+
}, mergeEntryMeta(void 0));
|
|
2219
2421
|
copiedCount = 1;
|
|
2220
2422
|
}
|
|
2221
2423
|
});
|
|
@@ -2307,11 +2509,7 @@ function registerBulkTools(server, _provider, projectRoot) {
|
|
|
2307
2509
|
for (const entryId of input.entry_ids) {
|
|
2308
2510
|
const existing = metaData?.[entryId];
|
|
2309
2511
|
if (!existing) continue;
|
|
2310
|
-
updates[entryId] =
|
|
2311
|
-
...existing,
|
|
2312
|
-
status: input.status,
|
|
2313
|
-
updated_by: "contentrain-mcp"
|
|
2314
|
-
};
|
|
2512
|
+
updates[entryId] = applyStatusChange(existing, input.status);
|
|
2315
2513
|
foundIds.add(entryId);
|
|
2316
2514
|
}
|
|
2317
2515
|
const written = await writeMetaEntries(wt, model, {
|
|
@@ -2328,11 +2526,7 @@ function registerBulkTools(server, _provider, projectRoot) {
|
|
|
2328
2526
|
await writeMeta(wt, model, {
|
|
2329
2527
|
locale,
|
|
2330
2528
|
defaultLocale: config.locales.default
|
|
2331
|
-
},
|
|
2332
|
-
...existing,
|
|
2333
|
-
status: input.status,
|
|
2334
|
-
updated_by: "contentrain-mcp"
|
|
2335
|
-
});
|
|
2529
|
+
}, applyStatusChange(existing, input.status));
|
|
2336
2530
|
updatedPerLocale[locale] = [model.id];
|
|
2337
2531
|
}
|
|
2338
2532
|
});
|
|
@@ -2612,7 +2806,7 @@ function registerMediaTools(server, provider, _projectRoot) {
|
|
|
2612
2806
|
}
|
|
2613
2807
|
//#endregion
|
|
2614
2808
|
//#region package.json
|
|
2615
|
-
var version = "
|
|
2809
|
+
var version = "3.0.1";
|
|
2616
2810
|
//#endregion
|
|
2617
2811
|
//#region src/server.ts
|
|
2618
2812
|
/**
|
|
@@ -2649,6 +2843,7 @@ function createServer(input) {
|
|
|
2649
2843
|
registerSetupTools(server, provider, projectRoot);
|
|
2650
2844
|
registerModelTools(server, provider, projectRoot);
|
|
2651
2845
|
registerContentTools(server, provider, projectRoot);
|
|
2846
|
+
registerVocabularyTools(server, provider);
|
|
2652
2847
|
registerWorkflowTools(server, provider, projectRoot);
|
|
2653
2848
|
registerNormalizeTools(server, provider, projectRoot);
|
|
2654
2849
|
registerBulkTools(server, provider, projectRoot);
|
|
@@ -2665,7 +2860,10 @@ function resolveServerContext(input) {
|
|
|
2665
2860
|
const instructions = input.instructions ?? "Contentrain is git-native content governance: models define structure; content is canonical JSON/Markdown on a dedicated branch. Start with contentrain_status; call contentrain_describe_format before creating models or content, and contentrain_describe before editing a model's entries. Every write lands on an isolated cr/* branch and content_save validates before committing. Destructive tools require confirm:true; preview contentrain_apply with dry_run:true first. Content decisions stay with the agent.";
|
|
2666
2861
|
if (input.provider) {
|
|
2667
2862
|
let projectRoot = input.projectRoot;
|
|
2668
|
-
if (!projectRoot
|
|
2863
|
+
if (!projectRoot) {
|
|
2864
|
+
const maybeRoot = input.provider.projectRoot;
|
|
2865
|
+
if (typeof maybeRoot === "string") projectRoot = maybeRoot;
|
|
2866
|
+
}
|
|
2669
2867
|
return {
|
|
2670
2868
|
provider: input.provider,
|
|
2671
2869
|
projectRoot,
|
|
@@ -2682,4 +2880,4 @@ function resolveServerContext(input) {
|
|
|
2682
2880
|
//#endregion
|
|
2683
2881
|
export { createServer as n, DEFAULT_INSTRUCTIONS as t };
|
|
2684
2882
|
|
|
2685
|
-
//# sourceMappingURL=server-
|
|
2883
|
+
//# sourceMappingURL=server-D-zJWbdI.mjs.map
|