@contentrain/mcp 0.2.0 → 0.3.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 CHANGED
@@ -90,7 +90,7 @@ Add to your MCP client configuration:
90
90
 
91
91
  ### 3. Start using
92
92
 
93
- Once configured, your AI assistant has access to 14 tools. Here's an example conversation:
93
+ Once configured, your AI assistant has access to 17 tools. Here's an example conversation:
94
94
 
95
95
  ```
96
96
  You: List all my models
@@ -209,6 +209,14 @@ You can register multiple MCP server instances — one per environment:
209
209
  | `contentrain_register_asset` | Register an existing file as an asset |
210
210
  | `contentrain_deregister_asset` | Remove an asset from the registry |
211
211
 
212
+ ### Diagnostics
213
+
214
+ | Tool | Description |
215
+ |------|-------------|
216
+ | `contentrain_doctor` | Full diagnostic scan — detects ID, schema, relation, asset, and sync issues. Use `fix=true` to auto-repair (pushes to a staging branch for review). |
217
+ | `contentrain_doctor_apply` | Merge a staged doctor fix into the target branch |
218
+ | `contentrain_doctor_discard` | Discard a staged doctor fix without merging |
219
+
212
220
  ## Environment Variables
213
221
 
214
222
  | Variable | Default | Description |
package/dist/index.d.mts CHANGED
@@ -251,6 +251,25 @@ declare class ContentrainWriter {
251
251
  transaction<T>(fn: (managers: TransactionManagers) => Promise<T>, options?: {
252
252
  message?: string;
253
253
  }): Promise<T>;
254
+ /**
255
+ * Execute operations and push to a staging branch instead of the target branch.
256
+ * Returns the result and whether any changes were pushed.
257
+ */
258
+ stagingTransaction<T>(fn: (managers: TransactionManagers) => Promise<T>, options: {
259
+ message: string;
260
+ stagingBranch: string;
261
+ }): Promise<{
262
+ result: T;
263
+ hasChanges: boolean;
264
+ }>;
265
+ /**
266
+ * Merge a staging branch into the target branch.
267
+ */
268
+ applyStagingBranch(stagingBranch: string): Promise<string>;
269
+ /**
270
+ * Delete a staging branch without merging.
271
+ */
272
+ discardStagingBranch(stagingBranch: string): Promise<void>;
254
273
  /**
255
274
  * Dry-run: copy contentrain dir to temp, run operations there, then discard.
256
275
  * The original repository is never modified.
@@ -304,6 +323,7 @@ declare class Doctor {
304
323
  private issues;
305
324
  private fixed;
306
325
  private totalEntries;
326
+ private scannedEntries;
307
327
  private idRemap;
308
328
  private contentFiles;
309
329
  private fieldsByModel;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/index.ts","../src/server.ts","../src/core/model/manager.ts","../src/core/content/manager.ts","../src/core/asset/manager.ts","../src/core/validation/validator.ts","../src/core/writer.ts","../src/core/doctor.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/index.ts","../src/server.ts","../src/core/model/manager.ts","../src/core/content/manager.ts","../src/core/asset/manager.ts","../src/core/validation/validator.ts","../src/core/writer.ts","../src/core/doctor.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -260,7 +260,7 @@ var ModelManager = class {
260
260
  };
261
261
  //#endregion
262
262
  //#region src/util/id.ts
263
- const ID_PATTERN = /^[a-f0-9]{12}$/;
263
+ const ID_PATTERN = /^[a-f0-9]{8,12}$/;
264
264
  /**
265
265
  * Generate a 12-character lowercase hexadecimal ID.
266
266
  * Matches the Contentrain platform: crypto.randomUUID().split('-').pop()
@@ -543,7 +543,7 @@ function getExpectedType(componentId, fieldType) {
543
543
  */
544
544
  function validateId(id) {
545
545
  if (typeof id !== "string") return "ID must be a string";
546
- if (!isValidId(id)) return "ID must be 12-character lowercase hexadecimal";
546
+ if (!isValidId(id)) return "ID must be 8–12 character lowercase hexadecimal";
547
547
  return null;
548
548
  }
549
549
  /**
@@ -904,6 +904,87 @@ var GitSyncManager = class {
904
904
  }
905
905
  }
906
906
  /**
907
+ * Run a staging transaction: worktree → execute → commit → push to staging branch → cleanup.
908
+ * Changes are pushed to a separate branch instead of the target branch.
909
+ */
910
+ async stagingTransaction(fn, message, stagingBranch) {
911
+ const ctx = await this.createWorktree();
912
+ try {
913
+ const result = await fn(ctx.path);
914
+ const commitHash = await this.commit(ctx, message);
915
+ if (commitHash) {
916
+ const git = simpleGit(ctx.path);
917
+ try {
918
+ await git.push(this.config.remote, `${ctx.branch}:${stagingBranch}`);
919
+ } catch (err) {
920
+ throw new GitSyncError(`Failed to push to staging branch "${stagingBranch}"`, {
921
+ branch: stagingBranch,
922
+ originalError: String(err)
923
+ });
924
+ }
925
+ }
926
+ return {
927
+ result,
928
+ hasChanges: !!commitHash
929
+ };
930
+ } finally {
931
+ await this.cleanup(ctx);
932
+ }
933
+ }
934
+ /**
935
+ * Merge a remote staging branch into the target branch.
936
+ * Tries fast-forward first, falls back to merge commit.
937
+ */
938
+ async mergeStagingToTarget(stagingBranch) {
939
+ const git = simpleGit(this.config.repoPath);
940
+ const remote = this.config.remote;
941
+ await git.fetch(remote, stagingBranch);
942
+ try {
943
+ await git.raw([
944
+ "rev-parse",
945
+ "--verify",
946
+ `${remote}/${stagingBranch}`
947
+ ]);
948
+ } catch {
949
+ throw new GitSyncError(`Staging branch "${stagingBranch}" not found on remote "${remote}"`, { branch: stagingBranch });
950
+ }
951
+ await git.fetch(remote, this.config.branch);
952
+ const ctx = await this.createWorktree();
953
+ try {
954
+ const wtGit = simpleGit(ctx.path);
955
+ try {
956
+ await wtGit.merge([`${remote}/${stagingBranch}`, "--ff-only"]);
957
+ } catch {
958
+ await wtGit.merge([
959
+ `${remote}/${stagingBranch}`,
960
+ "-m",
961
+ `${this.config.commitPrefix} apply doctor fix from ${stagingBranch}`
962
+ ]);
963
+ }
964
+ await this.pushWithRetry(ctx);
965
+ try {
966
+ await git.push(remote, `:${stagingBranch}`);
967
+ } catch {}
968
+ return (await wtGit.log({ maxCount: 1 })).latest?.hash || "";
969
+ } finally {
970
+ await this.cleanup(ctx);
971
+ }
972
+ }
973
+ /**
974
+ * Delete a remote staging branch without merging.
975
+ */
976
+ async deleteStagingBranch(stagingBranch) {
977
+ const git = simpleGit(this.config.repoPath);
978
+ try {
979
+ await git.push(this.config.remote, `:${stagingBranch}`);
980
+ } catch (err) {
981
+ throw new GitSyncError(`Failed to delete staging branch "${stagingBranch}"`, {
982
+ branch: stagingBranch,
983
+ originalError: String(err)
984
+ });
985
+ }
986
+ }
987
+ /**
907
988
  * Resolve push conflicts via structural JSON merge.
908
989
  */
909
990
  async resolveAndRetry(ctx) {
@@ -987,6 +1068,31 @@ var ContentrainWriter = class {
987
1068
  }, options?.message || "content update");
988
1069
  }
989
1070
  /**
1071
+ * Execute operations and push to a staging branch instead of the target branch.
1072
+ * Returns the result and whether any changes were pushed.
1073
+ */
1074
+ async stagingTransaction(fn, options) {
1075
+ if (this.config.dryRun) return {
1076
+ result: await this.dryRunTransaction(fn),
1077
+ hasChanges: false
1078
+ };
1079
+ return this.gitSync.stagingTransaction(async (worktreePath) => {
1080
+ return fn(this.createManagers(worktreePath));
1081
+ }, options.message, options.stagingBranch);
1082
+ }
1083
+ /**
1084
+ * Merge a staging branch into the target branch.
1085
+ */
1086
+ async applyStagingBranch(stagingBranch) {
1087
+ return this.gitSync.mergeStagingToTarget(stagingBranch);
1088
+ }
1089
+ /**
1090
+ * Delete a staging branch without merging.
1091
+ */
1092
+ async discardStagingBranch(stagingBranch) {
1093
+ return this.gitSync.deleteStagingBranch(stagingBranch);
1094
+ }
1095
+ /**
990
1096
  * Dry-run: copy contentrain dir to temp, run operations there, then discard.
991
1097
  * The original repository is never modified.
992
1098
  */
@@ -1449,6 +1555,7 @@ var Doctor = class {
1449
1555
  issues = [];
1450
1556
  fixed = 0;
1451
1557
  totalEntries = 0;
1558
+ scannedEntries = 0;
1452
1559
  idRemap = /* @__PURE__ */ new Map();
1453
1560
  contentFiles = [];
1454
1561
  fieldsByModel = /* @__PURE__ */ new Map();
@@ -1460,6 +1567,7 @@ var Doctor = class {
1460
1567
  this.issues = [];
1461
1568
  this.fixed = 0;
1462
1569
  this.totalEntries = 0;
1570
+ this.scannedEntries = 0;
1463
1571
  this.idRemap = /* @__PURE__ */ new Map();
1464
1572
  this.contentFiles = [];
1465
1573
  this.fieldsByModel = /* @__PURE__ */ new Map();
@@ -1478,6 +1586,7 @@ var Doctor = class {
1478
1586
  const fields = await readJsonFile(join(this.basePath, "models", `${model.modelId}.json`));
1479
1587
  this.fieldsByModel.set(model.modelId, fields);
1480
1588
  } catch {}
1589
+ for (const cf of this.contentFiles) if (targetModels.some((m) => m.modelId === cf.modelId)) this.scannedEntries += cf.entries.length;
1481
1590
  for (const model of targetModels) {
1482
1591
  this.checkModelStructure(model);
1483
1592
  this.checkModelContent(model, assets, fix);
@@ -1489,7 +1598,7 @@ var Doctor = class {
1489
1598
  if (!options.modelId) await this.checkOrphanDirectories(models);
1490
1599
  if (fix && this.idRemap.size > 0) this.propagateIdChanges();
1491
1600
  if (fix) await this.writeDirtyFiles();
1492
- return this.buildResult(models.length);
1601
+ return this.buildResult(targetModels.length);
1493
1602
  }
1494
1603
  async loadAllContentFiles(models) {
1495
1604
  for (const model of models) {
@@ -1610,13 +1719,14 @@ var Doctor = class {
1610
1719
  return;
1611
1720
  }
1612
1721
  if (!isValidId(entryId)) {
1613
- const newId = fix ? generateId() : void 0;
1722
+ const existingNewId = this.idRemap.get(entryId);
1723
+ const newId = fix ? existingNewId || generateId() : void 0;
1614
1724
  this.addIssue("error", "id", `Invalid ID format "${entryId}" — must be 12-char lowercase hex`, model.modelId, entryId, "ID", file, fix ? `Regenerated to "${newId}" and updating all relations` : void 0);
1615
1725
  if (fix && newId) {
1616
1726
  this.idRemap.set(entryId, newId);
1617
1727
  entry.ID = newId;
1618
1728
  cf.dirty = true;
1619
- this.fixed++;
1729
+ if (!existingNewId) this.fixed++;
1620
1730
  }
1621
1731
  }
1622
1732
  if (!entry.createdAt) {
@@ -1820,14 +1930,15 @@ var Doctor = class {
1820
1930
  });
1821
1931
  }
1822
1932
  buildResult(modelCount) {
1933
+ const unfixedErrors = this.issues.filter((i) => i.severity === "error" && !i.fix).length;
1823
1934
  return {
1824
1935
  summary: {
1825
1936
  total: this.issues.length,
1826
- errors: this.issues.filter((i) => i.severity === "error").length,
1937
+ errors: unfixedErrors,
1827
1938
  warnings: this.issues.filter((i) => i.severity === "warning").length,
1828
1939
  fixed: this.fixed,
1829
1940
  models: modelCount,
1830
- entries: this.totalEntries
1941
+ entries: this.scannedEntries
1831
1942
  },
1832
1943
  issues: this.issues
1833
1944
  };
@@ -1836,31 +1947,44 @@ var Doctor = class {
1836
1947
  //#endregion
1837
1948
  //#region src/tools/doctor.ts
1838
1949
  function registerDoctorTools(server, writer, config) {
1950
+ const targetBranch = config.branch || "contentrain";
1951
+ const stagingPrefix = `${targetBranch}--doctor-fix/`;
1952
+ function isStagingBranch(branch) {
1953
+ return branch.startsWith(stagingPrefix);
1954
+ }
1839
1955
  server.registerTool("contentrain_doctor", {
1840
1956
  description: [
1841
1957
  "Run a full diagnostic scan on all Contentrain models, content, and assets.",
1842
1958
  "Detects: duplicate/invalid IDs, type mismatches, broken relations, missing assets,",
1843
1959
  "locale ID inconsistencies, orphan directories, MD/MDX sync issues, unique constraint",
1844
1960
  "violations, missing system fields, and invalid JSON files.",
1845
- "Use fix=true to auto-repair fixable issues (via git transaction).",
1961
+ "Use fix=true to auto-repair fixable issues — changes are pushed to a staging branch",
1962
+ "for review. Use contentrain_doctor_apply to merge the staging branch into the target.",
1846
1963
  "Use modelId to scope the scan to a single model."
1847
1964
  ].join(" "),
1848
- annotations: { readOnlyHint: true },
1965
+ annotations: { readOnlyHint: false },
1849
1966
  inputSchema: {
1850
- fix: z.boolean().optional().default(false).describe("Auto-fix repairable issues (duplicate IDs, missing timestamps, invalid status). Runs in a git transaction."),
1967
+ fix: z.boolean().optional().default(false).describe("Auto-fix repairable issues (duplicate IDs, missing timestamps, invalid status). Pushes to a staging branch for review."),
1851
1968
  modelId: z.string().optional().describe("Scope scan to a specific model ID. Omit to scan all models.")
1852
1969
  }
1853
1970
  }, async ({ fix, modelId }) => {
1854
1971
  try {
1855
- if (fix) return { content: [{
1856
- type: "text",
1857
- text: formatResult(await writer.transaction(async (tx) => {
1972
+ if (fix) {
1973
+ const stagingBranch = `${stagingPrefix}${Date.now()}`;
1974
+ const { result, hasChanges } = await writer.stagingTransaction(async (tx) => {
1858
1975
  return new Doctor(tx.basePath, tx.repoPath).diagnose({
1859
1976
  fix: true,
1860
1977
  modelId
1861
1978
  });
1862
- }, { message: `doctor fix${modelId ? ` ${modelId}` : ""}` }))
1863
- }] };
1979
+ }, {
1980
+ message: `doctor fix${modelId ? ` ${modelId}` : ""}`,
1981
+ stagingBranch
1982
+ });
1983
+ return { content: [{
1984
+ type: "text",
1985
+ text: formatResult(result, hasChanges ? stagingBranch : void 0)
1986
+ }] };
1987
+ }
1864
1988
  const contentrainDir = config.contentrainDir || "contentrain";
1865
1989
  return { content: [{
1866
1990
  type: "text",
@@ -1879,11 +2003,72 @@ function registerDoctorTools(server, writer, config) {
1879
2003
  };
1880
2004
  }
1881
2005
  });
2006
+ server.registerTool("contentrain_doctor_apply", {
2007
+ description: [
2008
+ "Apply a staged doctor fix by merging the staging branch into the target branch.",
2009
+ "Use the stagingBranch value returned by contentrain_doctor fix=true.",
2010
+ "Attempts fast-forward merge first, falls back to merge commit.",
2011
+ "Deletes the staging branch after successful merge."
2012
+ ].join(" "),
2013
+ annotations: { readOnlyHint: false },
2014
+ inputSchema: { branch: z.string().describe(`The staging branch name returned by contentrain_doctor fix=true (e.g., "${stagingPrefix}1234567890")`) }
2015
+ }, async ({ branch }) => {
2016
+ try {
2017
+ if (!isStagingBranch(branch)) return {
2018
+ content: [{
2019
+ type: "text",
2020
+ text: `Error: branch must start with "${stagingPrefix}" — only doctor staging branches for the "${targetBranch}" target can be applied.`
2021
+ }],
2022
+ isError: true
2023
+ };
2024
+ return { content: [{
2025
+ type: "text",
2026
+ text: `✅ Doctor fix applied to "${targetBranch}".\nCommit: ${await writer.applyStagingBranch(branch)}\nStaging branch "${branch}" has been deleted.`
2027
+ }] };
2028
+ } catch (err) {
2029
+ return {
2030
+ content: [{
2031
+ type: "text",
2032
+ text: `Error: ${err}`
2033
+ }],
2034
+ isError: true
2035
+ };
2036
+ }
2037
+ });
2038
+ server.registerTool("contentrain_doctor_discard", {
2039
+ description: "Discard a staged doctor fix by deleting the staging branch without merging.",
2040
+ annotations: { readOnlyHint: false },
2041
+ inputSchema: { branch: z.string().describe(`The staging branch name to discard (e.g., "${stagingPrefix}1234567890")`) }
2042
+ }, async ({ branch }) => {
2043
+ try {
2044
+ if (!isStagingBranch(branch)) return {
2045
+ content: [{
2046
+ type: "text",
2047
+ text: `Error: branch must start with "${stagingPrefix}" — only doctor staging branches can be discarded.`
2048
+ }],
2049
+ isError: true
2050
+ };
2051
+ await writer.discardStagingBranch(branch);
2052
+ return { content: [{
2053
+ type: "text",
2054
+ text: `Staging branch "${branch}" has been deleted.`
2055
+ }] };
2056
+ } catch (err) {
2057
+ return {
2058
+ content: [{
2059
+ type: "text",
2060
+ text: `Error: ${err}`
2061
+ }],
2062
+ isError: true
2063
+ };
2064
+ }
2065
+ });
1882
2066
  }
1883
- function formatResult(result) {
2067
+ function formatResult(result, stagingBranch) {
1884
2068
  const { summary, issues } = result;
1885
2069
  const lines = [];
1886
- if (summary.errors === 0 && summary.warnings === 0) lines.push("✅ No issues found!");
2070
+ if (summary.errors === 0 && summary.warnings === 0 && summary.fixed === 0) lines.push("✅ No issues found!");
2071
+ else if (summary.errors === 0 && summary.fixed > 0) lines.push("✅ All fixable issues have been repaired.");
1887
2072
  else if (summary.errors === 0) lines.push("⚠️ No errors, but some warnings found.");
1888
2073
  else lines.push("❌ Issues detected.");
1889
2074
  lines.push("");
@@ -1892,6 +2077,12 @@ function formatResult(result) {
1892
2077
  lines.push(`Errors: ${summary.errors}`);
1893
2078
  lines.push(`Warnings: ${summary.warnings}`);
1894
2079
  if (summary.fixed > 0) lines.push(`Fixed: ${summary.fixed}`);
2080
+ if (stagingBranch) {
2081
+ lines.push("");
2082
+ lines.push(`📦 Staging branch: ${stagingBranch}`);
2083
+ lines.push("→ Review the changes, then run contentrain_doctor_apply to merge into the target branch.");
2084
+ lines.push("→ Or run contentrain_doctor_discard to discard without applying.");
2085
+ }
1895
2086
  if (issues.length > 0) {
1896
2087
  lines.push("");
1897
2088
  const grouped = /* @__PURE__ */ new Map();
@@ -1912,7 +2103,7 @@ function formatResult(result) {
1912
2103
  for (const [category, categoryIssues] of grouped) {
1913
2104
  lines.push(`\n### ${categoryLabels[category] || category}`);
1914
2105
  for (const issue of categoryIssues) {
1915
- const icon = issue.severity === "error" ? "❌" : "⚠️";
2106
+ const icon = issue.fix ? "✅" : issue.severity === "error" ? "❌" : "⚠️";
1916
2107
  const location = [
1917
2108
  issue.modelId,
1918
2109
  issue.entryId,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/util/json.ts","../src/errors.ts","../src/core/model/system-fields.ts","../src/core/model/manager.ts","../src/util/id.ts","../src/core/content/markdown.ts","../src/core/content/manager.ts","../src/core/asset/manager.ts","../src/core/validation/rules.ts","../src/core/validation/validator.ts","../src/core/git/merge.ts","../src/core/git/sync.ts","../src/core/writer.ts","../src/tools/model.ts","../src/tools/content.ts","../src/tools/asset.ts","../src/core/doctor.ts","../src/tools/doctor.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["import { readFile, writeFile, rename } from 'node:fs/promises'\nimport { join, dirname } from 'node:path'\nimport { randomBytes } from 'node:crypto'\nimport { ensureDir } from './fs.js'\n\n/**\n * Read and parse a JSON file. Returns empty array if file doesn't exist.\n */\nexport async function readJsonFile<T = unknown>(filePath: string): Promise<T> {\n try {\n const raw = await readFile(filePath, 'utf-8')\n return JSON.parse(raw) as T\n }\n catch (err: unknown) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n return [] as unknown as T\n }\n throw err\n }\n}\n\n/**\n * Write JSON to file atomically (temp file + rename).\n */\nexport async function writeJsonFile(filePath: string, data: unknown): Promise<void> {\n await ensureDir(dirname(filePath))\n const tmpPath = join(dirname(filePath), `.tmp-${randomBytes(4).toString('hex')}.json`)\n const content = JSON.stringify(data, null, 2) + '\\n'\n await writeFile(tmpPath, content, 'utf-8')\n await rename(tmpPath, filePath)\n}\n","export class ContentrainError extends Error {\n code: string\n context: Record<string, unknown>\n\n constructor(message: string, code: string, context: Record<string, unknown> = {}) {\n super(message)\n this.name = 'ContentrainError'\n this.code = code\n this.context = context\n }\n}\n\nexport class ValidationError extends ContentrainError {\n fieldErrors: Array<{ fieldId: string; message: string; rule: string }>\n\n constructor(message: string, fieldErrors: Array<{ fieldId: string; message: string; rule: string }>) {\n super(message, 'VALIDATION_ERROR', { fieldErrors })\n this.name = 'ValidationError'\n this.fieldErrors = fieldErrors\n }\n}\n\nexport class ModelNotFoundError extends ContentrainError {\n constructor(modelId: string) {\n super(`Model not found: ${modelId}`, 'MODEL_NOT_FOUND', { modelId })\n this.name = 'ModelNotFoundError'\n }\n}\n\nexport class EntryNotFoundError extends ContentrainError {\n constructor(modelId: string, entryId: string) {\n super(`Entry not found: ${entryId} in model ${modelId}`, 'ENTRY_NOT_FOUND', { modelId, entryId })\n this.name = 'EntryNotFoundError'\n }\n}\n\nexport class GitSyncError extends ContentrainError {\n constructor(message: string, context: Record<string, unknown> = {}) {\n super(message, 'GIT_SYNC_ERROR', context)\n this.name = 'GitSyncError'\n }\n}\n\nexport class MergeConflictError extends GitSyncError {\n conflictedFiles: string[]\n\n constructor(conflictedFiles: string[]) {\n super(`Merge conflict in files: ${conflictedFiles.join(', ')}`, { conflictedFiles })\n this.name = 'MergeConflictError'\n this.conflictedFiles = conflictedFiles\n }\n}\n\nexport class PushRejectedError extends GitSyncError {\n attemptsExhausted: boolean\n\n constructor(attemptsExhausted: boolean) {\n super(\n attemptsExhausted\n ? 'Push rejected: all retry attempts exhausted'\n : 'Push rejected by remote',\n { attemptsExhausted },\n )\n this.name = 'PushRejectedError'\n this.attemptsExhausted = attemptsExhausted\n }\n}\n\nexport class RelationIntegrityError extends ValidationError {\n constructor(fieldId: string, referencedModel: string, referencedId: string) {\n super(\n `Relation integrity error: ${referencedId} not found in model ${referencedModel}`,\n [{ fieldId, message: `Referenced entry ${referencedId} not found in ${referencedModel}`, rule: 'relation-integrity' }],\n )\n this.name = 'RelationIntegrityError'\n }\n}\n\nexport class AssetNotFoundError extends ContentrainError {\n constructor(assetPath: string) {\n super(`Asset not found in registry: ${assetPath}`, 'ASSET_NOT_FOUND', { assetPath })\n this.name = 'AssetNotFoundError'\n }\n}\n","import type { FieldDefinition } from '../../types/index.js'\n\n/**\n * Generate the 4 required system fields for a model.\n */\nexport function createSystemFields(modelId: string): FieldDefinition[] {\n return [\n {\n name: 'createdAt',\n fieldId: 'createdAt',\n componentId: 'date',\n options: {},\n validations: { 'required-field': { value: true } },\n fieldType: 'date',\n system: true,\n defaultField: false,\n modelId,\n },\n {\n name: 'updatedAt',\n fieldId: 'updatedAt',\n componentId: 'date',\n options: {},\n validations: { 'required-field': { value: true } },\n fieldType: 'date',\n system: true,\n defaultField: false,\n modelId,\n },\n {\n name: 'ID',\n fieldId: 'ID',\n componentId: 'single-line-text',\n options: {},\n validations: { 'required-field': { value: true } },\n fieldType: 'string',\n system: true,\n defaultField: false,\n modelId,\n },\n {\n name: 'status',\n fieldId: 'status',\n componentId: 'single-line-text',\n options: {},\n validations: { 'required-field': { value: true } },\n fieldType: 'string',\n system: true,\n defaultField: false,\n modelId,\n },\n ]\n}\n\n/**\n * Generate MD/MDX default fields (slug + content).\n */\nexport function createMdDefaultFields(modelId: string): FieldDefinition[] {\n return [\n {\n name: 'slug',\n fieldId: 'slug',\n componentId: 'slug',\n options: {},\n validations: { 'required-field': { value: true }, 'unique-field': { value: true } },\n fieldType: 'string',\n system: false,\n defaultField: true,\n modelId,\n },\n {\n name: 'content',\n fieldId: 'content',\n componentId: 'md-editor',\n options: {},\n validations: { 'required-field': { value: false } },\n fieldType: 'string',\n system: false,\n defaultField: true,\n modelId,\n },\n ]\n}\n","import { join } from 'node:path'\nimport type { ModelMetadata, FieldDefinition } from '../../types/index.js'\nimport { readJsonFile, writeJsonFile } from '../../util/json.js'\nimport { ensureDir } from '../../util/fs.js'\nimport { ModelNotFoundError } from '../../errors.js'\nimport { createSystemFields, createMdDefaultFields } from './system-fields.js'\n\nexport class ModelManager {\n constructor(private basePath: string) {}\n\n private get modelsDir(): string {\n return join(this.basePath, 'models')\n }\n\n private get metadataPath(): string {\n return join(this.modelsDir, 'metadata.json')\n }\n\n // ── Read Operations ──\n\n async list(): Promise<ModelMetadata[]> {\n return readJsonFile<ModelMetadata[]>(this.metadataPath)\n }\n\n async get(modelId: string): Promise<ModelMetadata> {\n const models = await this.list()\n const model = models.find(m => m.modelId === modelId)\n if (!model) throw new ModelNotFoundError(modelId)\n return model\n }\n\n async getFields(modelId: string): Promise<FieldDefinition[]> {\n await this.get(modelId) // ensure model exists\n const fieldsPath = join(this.modelsDir, `${modelId}.json`)\n return readJsonFile<FieldDefinition[]>(fieldsPath)\n }\n\n async describe(modelId: string): Promise<{\n model: ModelMetadata\n fields: FieldDefinition[]\n customFields: FieldDefinition[]\n }> {\n const model = await this.get(modelId)\n const fields = await this.getFields(modelId)\n const customFields = fields.filter(f => !f.system && !f.defaultField)\n return { model, fields, customFields }\n }\n\n // ── Write Operations ──\n\n async create(input: {\n name: string\n description?: string\n modelId: string\n localization: boolean\n type: 'JSON' | 'MD' | 'MDX'\n path?: string\n fields?: Omit<FieldDefinition, 'modelId' | 'system' | 'defaultField'>[]\n }): Promise<ModelMetadata> {\n const models = await this.list()\n\n if (models.some(m => m.modelId === input.modelId)) {\n throw new Error(`Model already exists: ${input.modelId}`)\n }\n\n // Create metadata entry\n const metadata: ModelMetadata = {\n name: input.name,\n description: input.description,\n modelId: input.modelId,\n path: input.path,\n localization: input.localization,\n type: input.type,\n createdBy: 'user',\n isServerless: false,\n }\n\n models.push(metadata)\n await writeJsonFile(this.metadataPath, models)\n\n // Build field definitions\n const systemFields = createSystemFields(input.modelId)\n const mdFields = (input.type === 'MD' || input.type === 'MDX')\n ? createMdDefaultFields(input.modelId)\n : []\n const customFields: FieldDefinition[] = (input.fields || []).map(f => ({\n ...f,\n modelId: input.modelId,\n system: false,\n defaultField: false,\n }))\n\n const allFields = [...systemFields, ...mdFields, ...customFields]\n await ensureDir(this.modelsDir)\n await writeJsonFile(join(this.modelsDir, `${input.modelId}.json`), allFields)\n\n // Create empty content directory and file\n const contentDir = join(this.basePath, input.modelId)\n await ensureDir(contentDir)\n\n if (input.localization) {\n // We don't know which locales yet — create dir only, files created on first content write\n }\n else {\n await writeJsonFile(join(contentDir, `${input.modelId}.json`), [])\n }\n\n return metadata\n }\n\n async addField(modelId: string, field: Omit<FieldDefinition, 'modelId' | 'system' | 'defaultField'>): Promise<FieldDefinition> {\n const fields = await this.getFields(modelId)\n\n if (fields.some(f => f.fieldId === field.fieldId)) {\n throw new Error(`Field already exists: ${field.fieldId} in model ${modelId}`)\n }\n\n const newField: FieldDefinition = {\n ...field,\n modelId,\n system: false,\n defaultField: false,\n }\n\n fields.push(newField)\n await writeJsonFile(join(this.modelsDir, `${modelId}.json`), fields)\n\n return newField\n }\n\n async removeField(modelId: string, fieldId: string): Promise<void> {\n const fields = await this.getFields(modelId)\n const field = fields.find(f => f.fieldId === fieldId)\n\n if (!field) throw new Error(`Field not found: ${fieldId} in model ${modelId}`)\n if (field.system) throw new Error(`Cannot remove system field: ${fieldId}`)\n\n const filtered = fields.filter(f => f.fieldId !== fieldId)\n await writeJsonFile(join(this.modelsDir, `${modelId}.json`), filtered)\n }\n\n async delete(modelId: string): Promise<void> {\n const models = await this.list()\n const filtered = models.filter(m => m.modelId !== modelId)\n\n if (filtered.length === models.length) {\n throw new ModelNotFoundError(modelId)\n }\n\n await writeJsonFile(this.metadataPath, filtered)\n\n // Remove field definitions\n const { removeFile, removeDir } = await import('../../util/fs.js')\n await removeFile(join(this.modelsDir, `${modelId}.json`))\n\n // Remove content directory\n await removeDir(join(this.basePath, modelId))\n }\n}\n","import { randomUUID } from 'node:crypto'\n\nconst ID_PATTERN = /^[a-f0-9]{12}$/\n\n/**\n * Generate a 12-character lowercase hexadecimal ID.\n * Matches the Contentrain platform: crypto.randomUUID().split('-').pop()\n */\nexport function generateId(): string {\n return randomUUID().split('-').pop()!\n}\n\n/**\n * Validate that a string matches the Contentrain ID format.\n */\nexport function isValidId(id: string): boolean {\n return ID_PATTERN.test(id)\n}\n","import { join } from 'node:path'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport type { MDContentEntry, ModelMetadata } from '../../types/index.js'\nimport { ensureDir, removeFile } from '../../util/fs.js'\n\n/**\n * Build frontmatter string from entry data (excluding 'content' field).\n */\nfunction buildFrontmatter(entry: Record<string, unknown>): string {\n const lines: string[] = ['---']\n for (const [key, value] of Object.entries(entry)) {\n if (key === 'content') continue\n if (typeof value === 'string') {\n lines.push(`${key}: '${value}'`)\n }\n else if (typeof value === 'boolean' || typeof value === 'number') {\n lines.push(`${key}: ${value}`)\n }\n else if (Array.isArray(value)) {\n lines.push(`${key}: ${JSON.stringify(value)}`)\n }\n }\n lines.push('---')\n return lines.join('\\n')\n}\n\n/**\n * Get the markdown file extension based on model type.\n */\nfunction getExtension(type: 'MD' | 'MDX'): string {\n return type === 'MDX' ? '.mdx' : '.md'\n}\n\n/**\n * Resolve the markdown file path for an entry.\n */\nfunction resolveMarkdownPath(\n repoPath: string,\n model: ModelMetadata,\n entry: MDContentEntry,\n locale?: string,\n): string {\n const basePath = entry.path || model.path || ''\n const ext = getExtension(model.type as 'MD' | 'MDX')\n\n if (locale) {\n return join(repoPath, basePath, locale, `${entry.slug}${ext}`)\n }\n return join(repoPath, basePath, `${entry.slug}${ext}`)\n}\n\nexport class MarkdownSync {\n constructor(private repoPath: string) {}\n\n /**\n * Write a markdown file for an MD/MDX entry.\n */\n async write(\n model: ModelMetadata,\n entry: MDContentEntry,\n content: string,\n locale?: string,\n ): Promise<string> {\n const filePath = resolveMarkdownPath(this.repoPath, model, entry, locale)\n await ensureDir(join(filePath, '..'))\n\n const frontmatter = buildFrontmatter(entry)\n const body = content || ''\n const fileContent = `${frontmatter}\\n\\n${body}\\n`\n\n await writeFile(filePath, fileContent, 'utf-8')\n return filePath\n }\n\n /**\n * Read markdown content from a file.\n */\n async read(\n model: ModelMetadata,\n entry: MDContentEntry,\n locale?: string,\n ): Promise<string> {\n const filePath = resolveMarkdownPath(this.repoPath, model, entry, locale)\n try {\n const raw = await readFile(filePath, 'utf-8')\n // Extract content after frontmatter\n const match = raw.match(/^---\\n[\\s\\S]*?\\n---\\n\\n?([\\s\\S]*)$/)\n return match ? match[1].trim() : ''\n }\n catch {\n return ''\n }\n }\n\n /**\n * Delete a markdown file for an entry.\n */\n async delete(\n model: ModelMetadata,\n entry: MDContentEntry,\n locale?: string,\n ): Promise<void> {\n const filePath = resolveMarkdownPath(this.repoPath, model, entry, locale)\n await removeFile(filePath)\n }\n}\n","import { join } from 'node:path'\nimport type { ContentEntry, MDContentEntry, ModelMetadata } from '../../types/index.js'\nimport { readJsonFile, writeJsonFile } from '../../util/json.js'\nimport { ensureDir, listJsonFiles } from '../../util/fs.js'\nimport { generateId } from '../../util/id.js'\nimport { EntryNotFoundError } from '../../errors.js'\nimport { ModelManager } from '../model/manager.js'\nimport { MarkdownSync } from './markdown.js'\n\nexport class ContentManager {\n private modelManager: ModelManager\n private markdownSync: MarkdownSync\n\n constructor(\n private basePath: string,\n private repoPath: string,\n ) {\n this.modelManager = new ModelManager(basePath)\n this.markdownSync = new MarkdownSync(repoPath)\n }\n\n // ── Path Helpers ──\n\n private contentDir(modelId: string): string {\n return join(this.basePath, modelId)\n }\n\n private contentFilePath(modelId: string, locale?: string): string {\n const dir = this.contentDir(modelId)\n return locale\n ? join(dir, `${locale}.json`)\n : join(dir, `${modelId}.json`)\n }\n\n // ── Read Operations ──\n\n async list(modelId: string, locale?: string): Promise<ContentEntry[]> {\n const model = await this.modelManager.get(modelId)\n const filePath = this.resolveContentPath(model, locale)\n return readJsonFile<ContentEntry[]>(filePath)\n }\n\n async get(modelId: string, entryId: string, locale?: string): Promise<ContentEntry> {\n const entries = await this.list(modelId, locale)\n const entry = entries.find(e => e.ID === entryId)\n if (!entry) throw new EntryNotFoundError(modelId, entryId)\n return entry\n }\n\n async getLocales(modelId: string): Promise<string[]> {\n const model = await this.modelManager.get(modelId)\n if (!model.localization) return []\n const files = await listJsonFiles(this.contentDir(modelId))\n // Exclude modelId.json (non-localized file name)\n return files.filter(f => f !== modelId)\n }\n\n // ── Write Operations ──\n\n async create(\n modelId: string,\n data: Record<string, unknown>,\n options?: { locale?: string; id?: string; status?: 'draft' | 'publish'; content?: string },\n ): Promise<string> {\n const model = await this.modelManager.get(modelId)\n const id = options?.id || generateId()\n const now = new Date().toISOString()\n\n const entry: ContentEntry = {\n ID: id,\n createdAt: now,\n updatedAt: now,\n status: options?.status || 'draft',\n scheduled: false,\n ...data,\n }\n\n if (model.localization) {\n await this.createLocalized(model, entry, options?.locale, options?.content)\n }\n else {\n await this.appendEntry(model, entry, undefined, options?.content)\n }\n\n return id\n }\n\n async update(\n modelId: string,\n entryId: string,\n data: Record<string, unknown>,\n options?: { locale?: string; content?: string },\n ): Promise<ContentEntry> {\n const model = await this.modelManager.get(modelId)\n const locale = model.localization ? options?.locale : undefined\n const filePath = this.resolveContentPath(model, locale)\n const entries = await readJsonFile<ContentEntry[]>(filePath)\n\n const index = entries.findIndex(e => e.ID === entryId)\n if (index === -1) throw new EntryNotFoundError(modelId, entryId)\n\n // Preserve createdAt, update updatedAt\n const updated: ContentEntry = {\n ...entries[index],\n ...data,\n ID: entryId,\n createdAt: entries[index].createdAt,\n updatedAt: new Date().toISOString(),\n }\n\n entries[index] = updated\n await writeJsonFile(filePath, entries)\n\n // Sync markdown if MD/MDX\n if ((model.type === 'MD' || model.type === 'MDX') && 'slug' in updated) {\n const mdContent = options?.content ?? await this.markdownSync.read(model, updated as MDContentEntry, locale)\n await this.markdownSync.write(model, updated as MDContentEntry, mdContent, locale)\n }\n\n return updated\n }\n\n async delete(modelId: string, entryId: string): Promise<void> {\n const model = await this.modelManager.get(modelId)\n\n if (model.localization) {\n const locales = await this.getLocales(modelId)\n for (const locale of locales) {\n await this.deleteFromFile(model, entryId, locale)\n }\n }\n else {\n await this.deleteFromFile(model, entryId)\n }\n }\n\n // ── Private Helpers ──\n\n private resolveContentPath(model: ModelMetadata, locale?: string): string {\n if (model.localization && locale) {\n return this.contentFilePath(model.modelId, locale)\n }\n if (model.localization && !locale) {\n // Default: try to find any locale file\n return this.contentFilePath(model.modelId, model.modelId)\n }\n return this.contentFilePath(model.modelId)\n }\n\n private async createLocalized(\n model: ModelMetadata,\n entry: ContentEntry,\n locale?: string,\n content?: string,\n ): Promise<void> {\n if (locale) {\n // Create for a specific locale\n await this.appendEntry(model, entry, locale, content)\n }\n else {\n // Get existing locales or default\n const locales = await this.getLocales(model.modelId)\n if (locales.length === 0) {\n // No locale files yet — cannot determine locales\n throw new Error(\n `Model \"${model.modelId}\" is localized but no locale files exist. `\n + 'Specify a locale (e.g., locale: \"en\") to create the first entry.',\n )\n }\n // Create entry in ALL locale files with the same ID\n for (const loc of locales) {\n await this.appendEntry(model, entry, loc, content)\n }\n }\n }\n\n private async appendEntry(\n model: ModelMetadata,\n entry: ContentEntry,\n locale?: string,\n content?: string,\n ): Promise<void> {\n const filePath = this.resolveContentPath(model, locale)\n await ensureDir(join(filePath, '..'))\n\n const entries = await readJsonFile<ContentEntry[]>(filePath)\n\n // Check for duplicate ID\n if (entries.some(e => e.ID === entry.ID)) {\n throw new Error(`Duplicate ID: ${entry.ID} already exists in ${model.modelId}`)\n }\n\n entries.push(entry)\n await writeJsonFile(filePath, entries)\n\n // Create markdown file if MD/MDX\n if ((model.type === 'MD' || model.type === 'MDX') && 'slug' in entry) {\n await this.markdownSync.write(model, entry as MDContentEntry, content || '', locale)\n }\n }\n\n private async deleteFromFile(model: ModelMetadata, entryId: string, locale?: string): Promise<void> {\n const filePath = this.resolveContentPath(model, locale)\n const entries = await readJsonFile<ContentEntry[]>(filePath)\n\n const entry = entries.find(e => e.ID === entryId)\n if (!entry) return // Already deleted or doesn't exist in this locale\n\n const filtered = entries.filter(e => e.ID !== entryId)\n await writeJsonFile(filePath, filtered)\n\n // Delete markdown file if MD/MDX\n if ((model.type === 'MD' || model.type === 'MDX') && 'slug' in entry) {\n await this.markdownSync.delete(model, entry as MDContentEntry, locale)\n }\n }\n}\n","import { join } from 'node:path'\nimport { stat } from 'node:fs/promises'\nimport type { AssetEntry, WriterConfig } from '../../types/index.js'\nimport { readJsonFile, writeJsonFile } from '../../util/json.js'\nimport { fileExists } from '../../util/fs.js'\nimport { AssetNotFoundError } from '../../errors.js'\n\nexport class AssetManager {\n private assetsPath: string\n\n constructor(\n private basePath: string,\n private repoPath: string,\n private config: WriterConfig,\n ) {\n this.assetsPath = join(basePath, 'assets.json')\n }\n\n async list(): Promise<AssetEntry[]> {\n return readJsonFile<AssetEntry[]>(this.assetsPath)\n }\n\n async get(assetPath: string): Promise<AssetEntry | undefined> {\n const assets = await this.list()\n return assets.find(a => a.path === assetPath)\n }\n\n async register(input: {\n path: string\n mimetype: string\n alt?: string\n }): Promise<AssetEntry> {\n // Verify file physically exists\n const fullPath = join(this.repoPath, input.path)\n if (!await fileExists(fullPath)) {\n throw new Error(`Asset file does not exist at: ${input.path}`)\n }\n\n const assets = await this.list()\n\n // Check for duplicate\n if (assets.some(a => a.path === input.path)) {\n throw new Error(`Asset already registered: ${input.path}`)\n }\n\n // Get file size\n const stats = await stat(fullPath)\n const now = new Date().toISOString()\n\n const entry: AssetEntry = {\n path: input.path,\n mimetype: input.mimetype,\n size: stats.size,\n alt: input.alt || '',\n meta: {\n user: {\n name: this.config.author?.name || 'AI Agent',\n email: this.config.author?.email || 'agent@contentrain.io',\n },\n createdAt: now,\n updatedAt: now,\n },\n }\n\n assets.push(entry)\n await writeJsonFile(this.assetsPath, assets)\n\n return entry\n }\n\n async deregister(assetPath: string): Promise<void> {\n const assets = await this.list()\n const filtered = assets.filter(a => a.path !== assetPath)\n\n if (filtered.length === assets.length) {\n throw new AssetNotFoundError(assetPath)\n }\n\n await writeJsonFile(this.assetsPath, filtered)\n }\n\n async update(assetPath: string, updates: { alt?: string; mimetype?: string }): Promise<AssetEntry> {\n const assets = await this.list()\n const index = assets.findIndex(a => a.path === assetPath)\n\n if (index === -1) throw new AssetNotFoundError(assetPath)\n\n if (updates.alt !== undefined) assets[index].alt = updates.alt\n if (updates.mimetype !== undefined) assets[index].mimetype = updates.mimetype\n if (assets[index].meta) {\n assets[index].meta!.updatedAt = new Date().toISOString()\n }\n\n await writeJsonFile(this.assetsPath, assets)\n return assets[index]\n }\n}\n","import type { ComponentId, FieldType } from '../../types/index.js'\nimport { isValidId } from '../../util/id.js'\n\nconst VALID_STATUSES = ['draft', 'publish', 'changed'] as const\n\n/**\n * Map componentId to expected runtime JS type.\n */\nexport function getExpectedType(componentId: ComponentId, fieldType: FieldType): string {\n switch (componentId) {\n // String components\n case 'single-line-text':\n case 'multi-line-text':\n case 'email':\n case 'url':\n case 'slug':\n case 'color':\n case 'json':\n case 'md-editor':\n case 'rich-text-editor':\n case 'phone-number': // phone-number is string, NOT number\n return 'string'\n\n // Number components\n case 'integer':\n case 'decimal':\n case 'rating':\n case 'percent':\n return 'number'\n\n // Boolean components\n case 'checkbox':\n case 'switch':\n return 'boolean'\n\n // Date components — stored as ISO string\n case 'date':\n case 'date-time':\n return 'string'\n\n // Media — stored as path string\n case 'media':\n return 'string'\n\n // Relations\n case 'one-to-one':\n return 'string'\n case 'one-to-many':\n return 'array'\n\n default:\n return fieldType === 'array' ? 'array' : 'string'\n }\n}\n\n/**\n * Validate an ID matches Contentrain format (12-char lowercase hex).\n */\nexport function validateId(id: unknown): string | null {\n if (typeof id !== 'string') return 'ID must be a string'\n if (!isValidId(id)) return 'ID must be 12-character lowercase hexadecimal'\n return null\n}\n\n/**\n * Validate a status value.\n */\nexport function validateStatus(status: unknown): string | null {\n if (typeof status !== 'string') return 'status must be a string'\n if (!VALID_STATUSES.includes(status as typeof VALID_STATUSES[number])) {\n return `status must be one of: ${VALID_STATUSES.join(', ')}`\n }\n return null\n}\n\n/**\n * Validate a value matches the expected runtime type.\n */\nexport function validateFieldValue(value: unknown, expectedType: string, fieldId: string): string | null {\n if (value === null || value === undefined) return null // handled by required check\n\n switch (expectedType) {\n case 'string':\n if (typeof value !== 'string') return `${fieldId} must be a string`\n break\n case 'number':\n if (typeof value !== 'number') return `${fieldId} must be a number`\n break\n case 'boolean':\n if (typeof value !== 'boolean') return `${fieldId} must be a boolean`\n break\n case 'array':\n if (!Array.isArray(value)) return `${fieldId} must be an array`\n break\n }\n\n return null\n}\n\n/**\n * Validate integer specifically.\n */\nexport function validateInteger(value: unknown, fieldId: string): string | null {\n if (typeof value === 'number' && !Number.isInteger(value)) {\n return `${fieldId} must be an integer`\n }\n return null\n}\n","import { join } from 'node:path'\nimport type { ContentEntry, ValidationResult, ValidationFieldError } from '../../types/index.js'\nimport { readJsonFile } from '../../util/json.js'\nimport { ModelManager } from '../model/manager.js'\nimport { getExpectedType, validateFieldValue, validateInteger, validateId, validateStatus } from './rules.js'\n\nexport class Validator {\n private modelManager: ModelManager\n\n constructor(private basePath: string) {\n this.modelManager = new ModelManager(basePath)\n }\n\n /**\n * Validate content data against model field definitions.\n */\n async validate(\n modelId: string,\n data: Record<string, unknown>,\n options?: { isUpdate?: boolean },\n ): Promise<ValidationResult> {\n const errors: ValidationFieldError[] = []\n const { fields, model } = await this.modelManager.describe(modelId)\n\n const nonSystemFields = fields.filter(f => !f.system)\n\n for (const field of nonSystemFields) {\n const value = data[field.fieldId]\n\n // Required check (skip on update if field not provided)\n if (field.validations?.['required-field']?.value) {\n if (!options?.isUpdate && (value === undefined || value === null || value === '')) {\n // For MD/MDX, content field is not required in JSON\n if (field.fieldId === 'content' && (model.type === 'MD' || model.type === 'MDX')) {\n continue\n }\n errors.push({\n fieldId: field.fieldId,\n message: `${field.name} is required`,\n rule: 'required-field',\n })\n continue\n }\n }\n\n if (value === undefined || value === null) continue\n\n // Type check\n const expectedType = getExpectedType(field.componentId, field.fieldType)\n const typeError = validateFieldValue(value, expectedType, field.fieldId)\n if (typeError) {\n errors.push({ fieldId: field.fieldId, message: typeError, rule: 'type-check' })\n continue\n }\n\n // Integer check\n if (field.componentId === 'integer') {\n const intError = validateInteger(value, field.fieldId)\n if (intError) {\n errors.push({ fieldId: field.fieldId, message: intError, rule: 'integer-check' })\n }\n }\n\n // Relation integrity check\n if (field.componentId === 'one-to-one' && typeof value === 'string') {\n const refModel = field.options?.reference?.form?.reference?.value\n if (refModel) {\n const integrityError = await this.checkRelationIntegrity(refModel, value)\n if (integrityError) {\n errors.push({ fieldId: field.fieldId, message: integrityError, rule: 'relation-integrity' })\n }\n }\n }\n\n if (field.componentId === 'one-to-many' && Array.isArray(value)) {\n const refModel = field.options?.reference?.form?.reference?.value\n if (refModel) {\n for (const refId of value) {\n if (typeof refId === 'string') {\n const integrityError = await this.checkRelationIntegrity(refModel, refId)\n if (integrityError) {\n errors.push({ fieldId: field.fieldId, message: integrityError, rule: 'relation-integrity' })\n }\n }\n }\n }\n }\n\n // Media path check\n if (field.componentId === 'media' && typeof value === 'string' && value !== '') {\n const assetError = await this.checkAssetExists(value)\n if (assetError) {\n errors.push({ fieldId: field.fieldId, message: assetError, rule: 'asset-exists' })\n }\n }\n }\n\n // Unique field check\n if (!options?.isUpdate) {\n const uniqueFields = nonSystemFields.filter(f => f.validations?.['unique-field']?.value)\n for (const field of uniqueFields) {\n const value = data[field.fieldId]\n if (value === undefined || value === null) continue\n\n const uniqueError = await this.checkUniqueness(modelId, field.fieldId, value)\n if (uniqueError) {\n errors.push({ fieldId: field.fieldId, message: uniqueError, rule: 'unique-field' })\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n }\n\n /**\n * Validate ID format.\n */\n validateId(id: string): ValidationResult {\n const error = validateId(id)\n if (error) {\n return { valid: false, errors: [{ fieldId: 'ID', message: error, rule: 'id-format' }] }\n }\n return { valid: true, errors: [] }\n }\n\n /**\n * Validate status value.\n */\n validateStatus(status: string): ValidationResult {\n const error = validateStatus(status)\n if (error) {\n return { valid: false, errors: [{ fieldId: 'status', message: error, rule: 'status-value' }] }\n }\n return { valid: true, errors: [] }\n }\n\n // ── Private Helpers ──\n\n private async checkRelationIntegrity(refModelId: string, refEntryId: string): Promise<string | null> {\n try {\n await this.modelManager.get(refModelId) // verify model exists\n // Try to find the entry in non-localized or any locale file\n const contentDir = join(this.basePath, refModelId)\n const { listJsonFiles } = await import('../../util/fs.js')\n const files = await listJsonFiles(contentDir)\n\n for (const file of files) {\n const entries = await readJsonFile<ContentEntry[]>(join(contentDir, `${file}.json`))\n if (entries.some(e => e.ID === refEntryId)) return null\n }\n\n return `Referenced entry ${refEntryId} not found in model ${refModelId}`\n }\n catch {\n return `Referenced model ${refModelId} not found`\n }\n }\n\n private async checkAssetExists(assetPath: string): Promise<string | null> {\n try {\n const assets = await readJsonFile<Array<{ path: string }>>(join(this.basePath, 'assets.json'))\n if (assets.some(a => a.path === assetPath)) return null\n return `Asset not found in registry: ${assetPath}`\n }\n catch {\n return `Could not read assets.json`\n }\n }\n\n private async checkUniqueness(modelId: string, fieldId: string, value: unknown): Promise<string | null> {\n try {\n const contentDir = join(this.basePath, modelId)\n const { listJsonFiles } = await import('../../util/fs.js')\n const files = await listJsonFiles(contentDir)\n\n for (const file of files) {\n const entries = await readJsonFile<ContentEntry[]>(join(contentDir, `${file}.json`))\n if (entries.some(e => e[fieldId] === value)) {\n return `${fieldId} value \"${value}\" already exists — must be unique`\n }\n }\n return null\n }\n catch {\n return null\n }\n }\n}\n","import type { ContentEntry } from '../../types/index.js'\n\n/**\n * Three-way structural merge for JSON arrays indexed by ID.\n *\n * Strategy:\n * - New entries (in ours but not base): APPEND to result\n * - Deleted entries (in base but not in ours): REMOVE from result\n * - Modified entries (same ID, different fields): apply our field-level changes over theirs\n * - Entries only in theirs (added by another writer): KEEP\n */\nexport function structuralMerge(\n base: ContentEntry[],\n ours: ContentEntry[],\n theirs: ContentEntry[],\n): ContentEntry[] {\n const baseMap = indexById(base)\n const oursMap = indexById(ours)\n\n const result: ContentEntry[] = []\n const processedIds = new Set<string>()\n\n // 1. Process entries from theirs (remote) as the base ordering\n for (const theirEntry of theirs) {\n const id = theirEntry.ID\n processedIds.add(id)\n\n const baseEntry = baseMap.get(id)\n const ourEntry = oursMap.get(id)\n\n if (!ourEntry && baseEntry) {\n // We deleted it — skip (don't include in result)\n continue\n }\n\n if (ourEntry && baseEntry) {\n // Both have it — merge field-level changes\n result.push(mergeEntry(baseEntry, ourEntry, theirEntry))\n }\n else {\n // We don't have it (and it wasn't in base) — it's theirs only, keep it\n result.push(theirEntry)\n }\n }\n\n // 2. Add our new entries (in ours but not in base and not in theirs)\n for (const ourEntry of ours) {\n if (!processedIds.has(ourEntry.ID)) {\n result.push(ourEntry)\n }\n }\n\n return result\n}\n\n/**\n * Merge a single entry using three-way field comparison.\n * Our changes take precedence over theirs when both modified the same field.\n */\nfunction mergeEntry(\n base: ContentEntry,\n ours: ContentEntry,\n theirs: ContentEntry,\n): ContentEntry {\n const merged: ContentEntry = { ...theirs }\n\n for (const key of Object.keys(ours)) {\n const baseVal = JSON.stringify(base[key])\n const ourVal = JSON.stringify(ours[key])\n const theirVal = JSON.stringify(theirs[key])\n\n if (ourVal !== baseVal && theirVal === baseVal) {\n // We changed it, they didn't — take ours\n merged[key] = ours[key]\n }\n else if (ourVal !== baseVal && theirVal !== baseVal) {\n // Both changed — our change wins (last-writer-wins for same field)\n merged[key] = ours[key]\n }\n // If only they changed, theirs is already in merged via spread\n }\n\n return merged\n}\n\nfunction indexById(entries: ContentEntry[]): Map<string, ContentEntry> {\n const map = new Map<string, ContentEntry>()\n for (const entry of entries) {\n map.set(entry.ID, entry)\n }\n return map\n}\n","import { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { randomBytes } from 'node:crypto'\nimport { simpleGit, type SimpleGit } from 'simple-git'\nimport type { WorktreeContext, WriterConfig, ContentEntry } from '../../types/index.js'\nimport { GitSyncError, PushRejectedError } from '../../errors.js'\nimport { readJsonFile, writeJsonFile } from '../../util/json.js'\nimport { removeDir } from '../../util/fs.js'\nimport { structuralMerge } from './merge.js'\n\nexport class GitSyncManager {\n private config: Required<Pick<WriterConfig, 'remote' | 'retryAttempts' | 'commitPrefix'>> & WriterConfig\n\n constructor(config: WriterConfig) {\n this.config = {\n ...config,\n remote: config.remote || 'origin',\n retryAttempts: config.retryAttempts || 3,\n commitPrefix: config.commitPrefix || '[contentrain]',\n }\n }\n\n /**\n * Create an isolated git worktree for safe operations.\n */\n async createWorktree(): Promise<WorktreeContext> {\n const id = randomBytes(4).toString('hex')\n const worktreePath = join(tmpdir(), `contentrain-mcp-${id}`)\n const git = simpleGit(this.config.repoPath)\n\n try {\n // Fetch latest\n await git.fetch(this.config.remote, this.config.branch)\n\n // Create worktree from remote branch\n const remoteBranch = `${this.config.remote}/${this.config.branch}`\n const localBranch = `contentrain-work-${id}`\n\n await git.raw([\n 'worktree', 'add',\n '-b', localBranch,\n worktreePath,\n remoteBranch,\n ])\n\n return {\n path: worktreePath,\n branch: localBranch,\n id,\n }\n }\n catch (err) {\n throw new GitSyncError(`Failed to create worktree: ${err}`, {\n branch: this.config.branch,\n worktreePath,\n })\n }\n }\n\n /**\n * Commit all changes in the worktree.\n */\n async commit(ctx: WorktreeContext, message: string): Promise<string> {\n const git = simpleGit(ctx.path)\n const fullMessage = `${this.config.commitPrefix} ${message}`\n\n await git.add('-A')\n\n // Check if there are actual changes\n const status = await git.status()\n if (status.files.length === 0) {\n return '' // Nothing to commit\n }\n\n const authorArgs: string[] = []\n if (this.config.author) {\n authorArgs.push('--author', `${this.config.author.name} <${this.config.author.email}>`)\n }\n\n const result = await git.commit(fullMessage, undefined, Object.fromEntries(\n authorArgs.length ? [['--author', `${this.config.author!.name} <${this.config.author!.email}>`]] : [],\n ))\n\n return result.commit\n }\n\n /**\n * Push with rebase-retry on conflict.\n */\n async pushWithRetry(ctx: WorktreeContext): Promise<void> {\n const git = simpleGit(ctx.path)\n const maxAttempts = this.config.retryAttempts\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n // Push our local branch to the remote target branch\n await git.push(this.config.remote, `${ctx.branch}:${this.config.branch}`)\n return // Success\n }\n catch {\n if (attempt === maxAttempts) {\n throw new PushRejectedError(true)\n }\n\n // Fetch latest and try structural merge\n try {\n await this.resolveAndRetry(ctx)\n }\n catch (mergeErr) {\n if (attempt === maxAttempts) {\n throw new GitSyncError(`Failed to resolve conflicts after ${maxAttempts} attempts`, {\n originalError: String(mergeErr),\n })\n }\n }\n }\n }\n }\n\n /**\n * Cleanup: remove the worktree.\n */\n async cleanup(ctx: WorktreeContext): Promise<void> {\n try {\n const git = simpleGit(this.config.repoPath)\n await git.raw(['worktree', 'remove', ctx.path, '--force'])\n }\n catch {\n // Force cleanup if git worktree remove fails\n await removeDir(ctx.path)\n }\n\n // Remove the temporary branch\n try {\n const git = simpleGit(this.config.repoPath)\n await git.raw(['branch', '-D', ctx.branch])\n }\n catch {\n // Branch may already be gone\n }\n }\n\n /**\n * Run a full transaction: worktree → execute → commit → push → cleanup.\n */\n async transaction<T>(\n fn: (worktreePath: string) => Promise<T>,\n message: string,\n ): Promise<T> {\n const ctx = await this.createWorktree()\n\n try {\n const result = await fn(ctx.path)\n\n const commitHash = await this.commit(ctx, message)\n if (commitHash) {\n await this.pushWithRetry(ctx)\n }\n\n return result\n }\n finally {\n await this.cleanup(ctx)\n }\n }\n\n // ── Private ──\n\n /**\n * Resolve push conflicts via structural JSON merge.\n */\n private async resolveAndRetry(ctx: WorktreeContext): Promise<void> {\n const git = simpleGit(ctx.path)\n const remoteBranch = `${this.config.remote}/${this.config.branch}`\n\n // Fetch latest remote state\n await git.fetch(this.config.remote, this.config.branch)\n\n // Find merge base\n const mergeBase = await git.raw(['merge-base', ctx.branch, remoteBranch])\n const baseCommit = mergeBase.trim()\n\n // Get list of conflicting files (JSON files in contentrain/)\n const diffOutput = await git.raw(['diff', '--name-only', `${remoteBranch}...${ctx.branch}`])\n const changedFiles = diffOutput.trim().split('\\n').filter((f: string) => f.endsWith('.json'))\n\n // For each changed JSON file, do structural merge\n for (const file of changedFiles) {\n if (!file.includes('contentrain/') && !file.startsWith('contentrain/')) continue\n\n try {\n // Read all three versions\n const basePath = join(ctx.path, file)\n const base = await this.readGitVersion<ContentEntry[]>(git, baseCommit, file)\n const ours = await readJsonFile<ContentEntry[]>(basePath)\n const theirsRaw = await git.raw(['show', `${remoteBranch}:${file}`])\n const theirs = JSON.parse(theirsRaw) as ContentEntry[]\n\n // Structural merge\n const merged = structuralMerge(base, ours, theirs)\n await writeJsonFile(basePath, merged)\n }\n catch {\n // If structural merge fails for a file, keep our version\n }\n }\n\n // Reset to remote and apply our merged state\n await git.raw(['reset', '--soft', remoteBranch])\n await git.add('-A')\n\n const status = await git.status()\n if (status.files.length > 0) {\n await git.commit(`${this.config.commitPrefix} merge: resolve concurrent changes`)\n }\n }\n\n private async readGitVersion<T>(git: SimpleGit, commit: string, filePath: string): Promise<T> {\n try {\n const raw = await git.raw(['show', `${commit}:${filePath}`])\n return JSON.parse(raw) as T\n }\n catch {\n return [] as unknown as T\n }\n }\n}\n","import { join } from 'node:path'\nimport { mkdtemp, cp } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport type { WriterConfig } from '../types/index.js'\nimport { ModelManager } from './model/manager.js'\nimport { ContentManager } from './content/manager.js'\nimport { AssetManager } from './asset/manager.js'\nimport { Validator } from './validation/validator.js'\nimport { GitSyncManager } from './git/sync.js'\nimport { removeDir } from '../util/fs.js'\n\n/**\n * Main orchestrator — coordinates all managers and git sync.\n *\n * Three modes:\n * 1. Read mode: reads from repoPath directly (no git sync)\n * 2. Transaction mode: creates a worktree, applies changes, commits + pushes\n * 3. Dry-run mode: copies contentrain dir to temp, applies changes there, then discards\n */\nexport class ContentrainWriter {\n private config: WriterConfig\n private contentrainDir: string\n\n // Direct-mode managers (operate on repoPath — reads only)\n readonly model: ModelManager\n readonly content: ContentManager\n readonly asset: AssetManager\n readonly validator: Validator\n private gitSync: GitSyncManager\n\n constructor(config: WriterConfig) {\n this.config = config\n this.contentrainDir = config.contentrainDir || 'contentrain'\n const basePath = join(config.repoPath, this.contentrainDir)\n\n this.model = new ModelManager(basePath)\n this.content = new ContentManager(basePath, config.repoPath)\n this.asset = new AssetManager(basePath, config.repoPath, config)\n this.validator = new Validator(basePath)\n this.gitSync = new GitSyncManager(config)\n }\n\n /**\n * Execute operations in a git-synced transaction.\n * Creates worktree → runs operations → validates → commits → pushes.\n * In dry-run mode, copies to a temp directory and discards after execution.\n */\n async transaction<T>(\n fn: (managers: TransactionManagers) => Promise<T>,\n options?: { message?: string },\n ): Promise<T> {\n if (this.config.dryRun) {\n return this.dryRunTransaction(fn)\n }\n\n return this.gitSync.transaction(\n async (worktreePath: string) => {\n const managers = this.createManagers(worktreePath)\n return fn(managers)\n },\n options?.message || 'content update',\n )\n }\n\n /**\n * Dry-run: copy contentrain dir to temp, run operations there, then discard.\n * The original repository is never modified.\n */\n private async dryRunTransaction<T>(\n fn: (managers: TransactionManagers) => Promise<T>,\n ): Promise<T> {\n const tempRepoPath = await mkdtemp(join(tmpdir(), 'contentrain-dry-'))\n const tempContentrainDir = join(tempRepoPath, this.contentrainDir)\n const sourceContentrainDir = join(this.config.repoPath, this.contentrainDir)\n\n try {\n await cp(sourceContentrainDir, tempContentrainDir, { recursive: true })\n const managers = this.createManagers(tempRepoPath)\n return await fn(managers)\n }\n finally {\n await removeDir(tempRepoPath)\n }\n }\n\n /**\n * Create managers for a specific repo path (used in transactions).\n */\n private createManagers(repoPath: string): TransactionManagers {\n const basePath = join(repoPath, this.contentrainDir)\n return {\n basePath,\n repoPath,\n model: new ModelManager(basePath),\n content: new ContentManager(basePath, repoPath),\n asset: new AssetManager(basePath, repoPath, this.config),\n validator: new Validator(basePath),\n }\n }\n}\n\nexport interface TransactionManagers {\n basePath: string\n repoPath: string\n model: ModelManager\n content: ContentManager\n asset: AssetManager\n validator: Validator\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainWriter } from '../core/writer.js'\n\nexport function registerModelTools(server: McpServer, writer: ContentrainWriter): void {\n // ── List Models ──\n server.registerTool(\n 'contentrain_list_models',\n {\n description: 'List all Contentrain models with their metadata (type, localization, etc.)',\n },\n async () => {\n try {\n const models = await writer.model.list()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(models, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Describe Model ──\n server.registerTool(\n 'contentrain_describe_model',\n {\n description: 'Get detailed schema for a model including all field definitions, types, validations, and options. Call this BEFORE creating or updating content to understand required fields.',\n inputSchema: {\n modelId: z.string().describe('The model ID to describe'),\n },\n },\n async ({ modelId }) => {\n try {\n const desc = await writer.model.describe(modelId)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(desc, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Create Model ──\n server.registerTool(\n 'contentrain_create_model',\n {\n description: 'Create a new Contentrain model with field definitions. System fields (ID, createdAt, updatedAt, status) are auto-added. For MD/MDX models, slug and content fields are also auto-added.',\n inputSchema: {\n name: z.string().describe('Display name of the model'),\n modelId: z.string().regex(/^[a-z0-9-]+$/).describe('Unique model ID in kebab-case'),\n type: z.enum(['JSON', 'MD', 'MDX']).describe('Content type'),\n localization: z.boolean().describe('Whether the model supports multiple languages'),\n description: z.string().optional().describe('Model description'),\n path: z.string().optional().describe('Base path for MD/MDX files'),\n fields: z.array(z.object({\n name: z.string(),\n fieldId: z.string(),\n componentId: z.string(),\n fieldType: z.string(),\n options: z.record(z.unknown()).optional(),\n validations: z.record(z.unknown()).optional(),\n })).optional().describe('Custom field definitions (system fields are auto-added)'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n return tx.model.create({\n name: params.name,\n modelId: params.modelId,\n type: params.type,\n localization: params.localization,\n description: params.description,\n path: params.path,\n fields: params.fields as any,\n })\n }, { message: `create model: ${params.modelId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, model: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Add Field ──\n server.registerTool(\n 'contentrain_add_field',\n {\n description: 'Add a new field to an existing model.',\n inputSchema: {\n modelId: z.string().describe('Target model ID'),\n name: z.string().describe('Display name of the field'),\n fieldId: z.string().describe('Unique field identifier'),\n componentId: z.string().describe('UI component (single-line-text, integer, one-to-one, media, etc.)'),\n fieldType: z.string().describe('Data type (string, number, boolean, relation, media, date, array)'),\n options: z.record(z.unknown()).optional().describe('Field options (title-field, default-value, reference, etc.)'),\n validations: z.record(z.unknown()).optional().describe('Field validations (required-field, unique-field)'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n return tx.model.addField(params.modelId, {\n name: params.name,\n fieldId: params.fieldId,\n componentId: params.componentId as any,\n fieldType: params.fieldType as any,\n options: (params.options || {}) as any,\n validations: (params.validations || {}) as any,\n })\n }, { message: `add field ${params.fieldId} to ${params.modelId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, field: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Delete Model ──\n server.registerTool(\n 'contentrain_delete_model',\n {\n description: 'Delete a model and all its content. WARNING: This is destructive and cannot be undone.',\n annotations: { destructiveHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID to delete'),\n },\n },\n async ({ modelId }) => {\n try {\n await writer.transaction(async (tx) => {\n await tx.model.delete(modelId)\n }, { message: `delete model: ${modelId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, deleted: modelId }) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainWriter } from '../core/writer.js'\n\nexport function registerContentTools(server: McpServer, writer: ContentrainWriter): void {\n // ── List Content ──\n server.registerTool(\n 'contentrain_list_content',\n {\n description: 'List all content entries for a model. For localized models, specify a locale.',\n annotations: { readOnlyHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID to list content from'),\n locale: z.string().optional().describe('Locale code (e.g., \"en\", \"tr\") for localized models'),\n },\n },\n async ({ modelId, locale }) => {\n try {\n const entries = await writer.content.list(modelId, locale)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ total: entries.length, entries }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Get Content ──\n server.registerTool(\n 'contentrain_get_content',\n {\n description: 'Get a single content entry by ID.',\n annotations: { readOnlyHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID'),\n entryId: z.string().describe('Entry ID (12-char hex)'),\n locale: z.string().optional().describe('Locale code for localized models'),\n },\n },\n async ({ modelId, entryId, locale }) => {\n try {\n const entry = await writer.content.get(modelId, entryId, locale)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(entry, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Create Content ──\n server.registerTool(\n 'contentrain_create_content',\n {\n description: 'Create a new content entry. System fields (ID, createdAt, updatedAt, status) are auto-generated. For localized models, specify locale to create in that language. Call contentrain_describe_model first to know required fields.',\n inputSchema: {\n modelId: z.string().describe('Target model ID'),\n data: z.record(z.unknown()).describe('Content data object with field values matching model schema'),\n locale: z.string().optional().describe('Locale code (e.g., \"en\") — required for localized models'),\n status: z.enum(['draft', 'publish']).optional().describe('Entry status (default: \"draft\")'),\n content: z.string().optional().describe('Markdown body content (only for MD/MDX models)'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n const validation = await tx.validator.validate(params.modelId, params.data as Record<string, unknown>)\n if (!validation.valid) {\n throw new Error(`Validation failed:\\n${validation.errors.map(e => ` - ${e.fieldId}: ${e.message}`).join('\\n')}`)\n }\n\n return tx.content.create(params.modelId, params.data as Record<string, unknown>, {\n locale: params.locale,\n status: params.status,\n content: params.content,\n })\n }, { message: `create ${params.modelId} entry` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, id: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Update Content ──\n server.registerTool(\n 'contentrain_update_content',\n {\n description: 'Update an existing content entry. Only provide fields you want to change. updatedAt is auto-set.',\n inputSchema: {\n modelId: z.string().describe('Model ID'),\n entryId: z.string().describe('Entry ID to update (12-char hex)'),\n data: z.record(z.unknown()).describe('Fields to update'),\n locale: z.string().optional().describe('Locale code for localized models'),\n content: z.string().optional().describe('Updated markdown body (only for MD/MDX models)'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n const validation = await tx.validator.validate(\n params.modelId,\n params.data as Record<string, unknown>,\n { isUpdate: true },\n )\n if (!validation.valid) {\n throw new Error(`Validation failed:\\n${validation.errors.map(e => ` - ${e.fieldId}: ${e.message}`).join('\\n')}`)\n }\n\n return tx.content.update(params.modelId, params.entryId, params.data as Record<string, unknown>, {\n locale: params.locale,\n content: params.content,\n })\n }, { message: `update ${params.modelId}/${params.entryId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, entry: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Delete Content ──\n server.registerTool(\n 'contentrain_delete_content',\n {\n description: 'Delete a content entry by ID. For localized models, removes from all locale files. For MD/MDX, also deletes markdown files.',\n annotations: { destructiveHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID'),\n entryId: z.string().describe('Entry ID to delete (12-char hex)'),\n },\n },\n async ({ modelId, entryId }) => {\n try {\n await writer.transaction(async (tx) => {\n await tx.content.delete(modelId, entryId)\n }, { message: `delete ${modelId}/${entryId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, deleted: entryId }) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Validate Content ──\n server.registerTool(\n 'contentrain_validate',\n {\n description: 'Validate content data against model schema without writing. Use for dry-run checks.',\n annotations: { readOnlyHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID to validate against'),\n data: z.record(z.unknown()).describe('Content data to validate'),\n },\n },\n async ({ modelId, data }) => {\n try {\n const result = await writer.validator.validate(modelId, data as Record<string, unknown>)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainWriter } from '../core/writer.js'\n\nexport function registerAssetTools(server: McpServer, writer: ContentrainWriter): void {\n // ── List Assets ──\n server.registerTool(\n 'contentrain_list_assets',\n {\n description: 'List all registered assets in assets.json.',\n annotations: { readOnlyHint: true },\n },\n async () => {\n try {\n const assets = await writer.asset.list()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ total: assets.length, assets }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Register Asset ──\n server.registerTool(\n 'contentrain_register_asset',\n {\n description: 'Register a new asset in assets.json. The file must already exist at the specified path in the repository.',\n inputSchema: {\n path: z.string().describe('Relative path to the asset file (e.g., \"contentrain/static/hero.png\")'),\n mimetype: z.string().describe('MIME type (e.g., \"image/png\", \"image/jpeg\", \"application/pdf\")'),\n alt: z.string().optional().describe('Alt text for the asset'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n return tx.asset.register({\n path: params.path,\n mimetype: params.mimetype,\n alt: params.alt,\n })\n }, { message: `register asset: ${params.path}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, asset: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Deregister Asset ──\n server.registerTool(\n 'contentrain_deregister_asset',\n {\n description: 'Remove an asset from assets.json registry. Does NOT delete the physical file.',\n annotations: { destructiveHint: true },\n inputSchema: {\n path: z.string().describe('Asset path to deregister'),\n },\n },\n async ({ path }) => {\n try {\n await writer.transaction(async (tx) => {\n await tx.asset.deregister(path)\n }, { message: `deregister asset: ${path}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, deregistered: path }) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n}\n","import { join } from 'node:path'\nimport { readFile } from 'node:fs/promises'\nimport type {\n ModelMetadata,\n FieldDefinition,\n ContentEntry,\n AssetEntry,\n MDContentEntry,\n} from '../types/index.js'\nimport { readJsonFile, writeJsonFile } from '../util/json.js'\nimport { listJsonFiles, fileExists } from '../util/fs.js'\nimport { isValidId, generateId } from '../util/id.js'\nimport { getExpectedType, validateFieldValue, validateInteger } from './validation/rules.js'\n\n// ── Types ──\n\nexport type IssueSeverity = 'error' | 'warning'\n\nexport type IssueCategory =\n | 'structure'\n | 'id'\n | 'schema'\n | 'relation'\n | 'asset'\n | 'sync'\n\nexport interface DoctorIssue {\n severity: IssueSeverity\n category: IssueCategory\n modelId?: string\n entryId?: string\n field?: string\n file?: string\n message: string\n fix?: string\n}\n\nexport interface DoctorResult {\n summary: {\n total: number\n errors: number\n warnings: number\n fixed: number\n models: number\n entries: number\n }\n issues: DoctorIssue[]\n}\n\nexport interface DoctorOptions {\n fix?: boolean\n modelId?: string\n}\n\n// Internal tracking for loaded content files\ninterface ContentFile {\n modelId: string\n fileName: string\n filePath: string\n entries: ContentEntry[]\n dirty: boolean\n}\n\n// ── Doctor Engine ──\n\nexport class Doctor {\n private issues: DoctorIssue[] = []\n private fixed = 0\n private totalEntries = 0\n\n // ID remap: oldId → newId (for relation propagation)\n private idRemap = new Map<string, string>()\n\n // All loaded content files (for cross-model relation propagation)\n private contentFiles: ContentFile[] = []\n\n // All field definitions by modelId (for relation field lookup)\n private fieldsByModel = new Map<string, FieldDefinition[]>()\n\n constructor(\n private basePath: string,\n private repoPath: string,\n ) {}\n\n async diagnose(options: DoctorOptions = {}): Promise<DoctorResult> {\n this.issues = []\n this.fixed = 0\n this.totalEntries = 0\n this.idRemap = new Map()\n this.contentFiles = []\n this.fieldsByModel = new Map()\n\n const fix = options.fix ?? false\n\n // 1. Check metadata.json is valid\n const models = await this.checkMetadata()\n if (!models) {\n return this.buildResult(0)\n }\n\n // 2. Filter to specific model if requested\n const targetModels = options.modelId\n ? models.filter(m => m.modelId === options.modelId)\n : models\n\n if (options.modelId && targetModels.length === 0) {\n this.addIssue('error', 'structure', `Model \"${options.modelId}\" not found in metadata.json`)\n return this.buildResult(models.length)\n }\n\n // 3. Check structure: field files exist for all models\n await this.checkFieldFiles(models, targetModels)\n\n // 4. Load assets once for media checks\n const assets = await this.loadAssets()\n\n // 5. Load ALL content files upfront (needed for relation propagation in fix mode)\n await this.loadAllContentFiles(fix ? models : targetModels)\n\n // 6. Load ALL field definitions (needed for relation propagation)\n for (const model of models) {\n try {\n const fields = await readJsonFile<FieldDefinition[]>(join(this.basePath, 'models', `${model.modelId}.json`))\n this.fieldsByModel.set(model.modelId, fields)\n }\n catch {\n // Already reported in checkFieldFiles\n }\n }\n\n // 7. For each target model, run all checks (collects ID remaps but doesn't write yet)\n for (const model of targetModels) {\n this.checkModelStructure(model)\n this.checkModelContent(model, assets, fix)\n // MD/MDX sync needs async file checks\n if (model.type === 'MD' || model.type === 'MDX') {\n const modelFiles = this.contentFiles.filter(f => f.modelId === model.modelId)\n await this.checkMarkdownSync(model, modelFiles)\n }\n }\n\n // 8. Check orphan content directories\n if (!options.modelId) {\n await this.checkOrphanDirectories(models)\n }\n\n // 9. Propagate ID changes to all relation fields across ALL models\n if (fix && this.idRemap.size > 0) {\n this.propagateIdChanges()\n }\n\n // 10. Write all dirty content files\n if (fix) {\n await this.writeDirtyFiles()\n }\n\n return this.buildResult(models.length)\n }\n\n // ── Content Loading ──\n\n private async loadAllContentFiles(models: ModelMetadata[]): Promise<void> {\n for (const model of models) {\n const contentDir = join(this.basePath, model.modelId)\n const fileNames = await listJsonFiles(contentDir)\n\n for (const fileName of fileNames) {\n const filePath = join(contentDir, `${fileName}.json`)\n let entries: ContentEntry[]\n try {\n const raw = await readFile(filePath, 'utf-8')\n entries = JSON.parse(raw) as ContentEntry[]\n if (!Array.isArray(entries)) {\n this.addIssue('error', 'structure', 'Content file is not an array', model.modelId, undefined, undefined, `${model.modelId}/${fileName}.json`)\n continue\n }\n }\n catch {\n this.addIssue('error', 'structure', 'Invalid JSON in content file', model.modelId, undefined, undefined, `${model.modelId}/${fileName}.json`)\n continue\n }\n\n this.totalEntries += entries.length\n this.contentFiles.push({\n modelId: model.modelId,\n fileName,\n filePath,\n entries,\n dirty: false,\n })\n }\n }\n }\n\n // ── Structural Checks ──\n\n private async checkMetadata(): Promise<ModelMetadata[] | null> {\n const metadataPath = join(this.basePath, 'models', 'metadata.json')\n try {\n const raw = await readFile(metadataPath, 'utf-8')\n try {\n const models = JSON.parse(raw) as ModelMetadata[]\n if (!Array.isArray(models)) {\n this.addIssue('error', 'structure', 'metadata.json is not an array', undefined, undefined, undefined, 'models/metadata.json')\n return null\n }\n\n // Check for duplicate modelIds in metadata\n const seen = new Map<string, number>()\n for (const m of models) {\n const count = (seen.get(m.modelId) ?? 0) + 1\n seen.set(m.modelId, count)\n }\n for (const [modelId, count] of seen) {\n if (count > 1) {\n this.addIssue('error', 'structure', `Duplicate modelId \"${modelId}\" in metadata.json (${count} times)`, modelId, undefined, undefined, 'models/metadata.json')\n }\n }\n\n return models\n }\n catch {\n this.addIssue('error', 'structure', 'metadata.json contains invalid JSON', undefined, undefined, undefined, 'models/metadata.json')\n return null\n }\n }\n catch {\n this.addIssue('error', 'structure', 'metadata.json not found — is this a Contentrain project?', undefined, undefined, undefined, 'models/metadata.json')\n return null\n }\n }\n\n private async checkFieldFiles(allModels: ModelMetadata[], targetModels: ModelMetadata[]): Promise<void> {\n for (const model of targetModels) {\n const fieldPath = join(this.basePath, 'models', `${model.modelId}.json`)\n if (!await fileExists(fieldPath)) {\n this.addIssue('error', 'structure', `Field definition file missing for model \"${model.modelId}\"`, model.modelId, undefined, undefined, `models/${model.modelId}.json`)\n }\n }\n\n // Check for field files without metadata entry\n const metaIds = new Set(allModels.map(m => m.modelId))\n const fieldFiles = await listJsonFiles(join(this.basePath, 'models'))\n for (const file of fieldFiles) {\n if (file === 'metadata') continue\n if (!metaIds.has(file)) {\n this.addIssue('warning', 'structure', `Field definition file \"${file}.json\" has no matching model in metadata.json`, file, undefined, undefined, `models/${file}.json`)\n }\n }\n }\n\n private async checkOrphanDirectories(models: ModelMetadata[]): Promise<void> {\n const modelIds = new Set(models.map(m => m.modelId))\n const { readdir, stat } = await import('node:fs/promises')\n\n try {\n const entries = await readdir(this.basePath)\n for (const entry of entries) {\n if (entry === 'models' || entry === 'assets.json' || entry === 'static' || entry.startsWith('.')) continue\n const fullPath = join(this.basePath, entry)\n const s = await stat(fullPath)\n if (s.isDirectory() && !modelIds.has(entry)) {\n this.addIssue('warning', 'structure', `Content directory \"${entry}/\" has no matching model in metadata.json`, entry)\n }\n }\n }\n catch {\n // basePath doesn't exist or can't be read\n }\n }\n\n // ── Model Checks ──\n\n private checkModelStructure(model: ModelMetadata): void {\n const fields = this.fieldsByModel.get(model.modelId)\n if (!fields) return\n\n // Check system fields exist in field definition\n const requiredSystemIds = ['createdAt', 'updatedAt', 'ID', 'status']\n for (const sysId of requiredSystemIds) {\n if (!fields.some(f => f.fieldId === sysId && f.system)) {\n this.addIssue('error', 'schema', `Missing system field \"${sysId}\" in field definitions`, model.modelId, undefined, undefined, `models/${model.modelId}.json`)\n }\n }\n\n if (model.type === 'MD' || model.type === 'MDX') {\n if (!fields.some(f => f.fieldId === 'slug')) {\n this.addIssue('error', 'schema', 'MD/MDX model missing required \"slug\" field', model.modelId, undefined, undefined, `models/${model.modelId}.json`)\n }\n if (!fields.some(f => f.fieldId === 'content')) {\n this.addIssue('warning', 'schema', 'MD/MDX model missing \"content\" field definition', model.modelId, undefined, undefined, `models/${model.modelId}.json`)\n }\n }\n }\n\n private checkModelContent(model: ModelMetadata, assets: AssetEntry[], fix: boolean): void {\n const fields = this.fieldsByModel.get(model.modelId)\n if (!fields) return\n\n const modelFiles = this.contentFiles.filter(f => f.modelId === model.modelId)\n\n if (modelFiles.length === 0 && !model.localization) {\n this.addIssue('warning', 'structure', `No content files found for model \"${model.modelId}\"`, model.modelId)\n return\n }\n\n for (const cf of modelFiles) {\n // Duplicate ID detection within same file\n const fileIdSet = new Map<string, number>()\n for (const entry of cf.entries) {\n const count = (fileIdSet.get(entry.ID) ?? 0) + 1\n fileIdSet.set(entry.ID, count)\n }\n\n for (const [id, count] of fileIdSet) {\n if (count > 1) {\n this.addIssue('error', 'id', `Duplicate ID \"${id}\" appears ${count} times in same file`, model.modelId, id, undefined, `${model.modelId}/${cf.fileName}.json`,\n fix ? 'Will regenerate duplicate IDs and update relations' : undefined)\n\n if (fix) {\n this.fixDuplicateIds(cf, id)\n }\n }\n }\n\n // Check each entry\n for (const entry of cf.entries) {\n this.checkEntry(model, fields, entry, assets, `${model.modelId}/${cf.fileName}.json`, fix, cf)\n }\n\n // Check unique constraints\n this.checkUniqueConstraints(model, fields, cf.entries, `${model.modelId}/${cf.fileName}.json`)\n }\n\n // Localized model: verify ID consistency across locale files\n if (model.localization && modelFiles.length > 1) {\n this.checkLocaleIdConsistency(model, modelFiles)\n }\n }\n\n // ── Entry-Level Checks ──\n\n private checkEntry(\n model: ModelMetadata,\n fields: FieldDefinition[],\n entry: ContentEntry,\n assets: AssetEntry[],\n file: string,\n fix: boolean,\n cf: ContentFile,\n ): void {\n const entryId = entry.ID\n\n // ID format\n if (!entryId || typeof entryId !== 'string') {\n this.addIssue('error', 'id', 'Entry missing ID field', model.modelId, undefined, 'ID', file)\n return\n }\n if (!isValidId(entryId)) {\n const newId = fix ? generateId() : undefined\n this.addIssue('error', 'id', `Invalid ID format \"${entryId}\" — must be 12-char lowercase hex`, model.modelId, entryId, 'ID', file,\n fix ? `Regenerated to \"${newId}\" and updating all relations` : undefined)\n if (fix && newId) {\n this.idRemap.set(entryId, newId)\n entry.ID = newId\n cf.dirty = true\n this.fixed++\n }\n }\n\n // System fields presence\n if (!entry.createdAt) {\n this.addIssue('error', 'schema', 'Missing createdAt', model.modelId, entryId, 'createdAt', file,\n fix ? 'Will set to current timestamp' : undefined)\n if (fix) {\n entry.createdAt = new Date().toISOString()\n cf.dirty = true\n this.fixed++\n }\n }\n if (!entry.updatedAt) {\n this.addIssue('error', 'schema', 'Missing updatedAt', model.modelId, entryId, 'updatedAt', file,\n fix ? 'Will set to current timestamp' : undefined)\n if (fix) {\n entry.updatedAt = new Date().toISOString()\n cf.dirty = true\n this.fixed++\n }\n }\n if (!entry.status) {\n this.addIssue('error', 'schema', 'Missing status', model.modelId, entryId, 'status', file,\n fix ? 'Will set to \"draft\"' : undefined)\n if (fix) {\n entry.status = 'draft'\n cf.dirty = true\n this.fixed++\n }\n }\n else if (!['draft', 'publish', 'changed'].includes(entry.status)) {\n this.addIssue('error', 'schema', `Invalid status \"${entry.status}\" — must be draft, publish, or changed`, model.modelId, entryId, 'status', file,\n fix ? 'Will set to \"draft\"' : undefined)\n if (fix) {\n entry.status = 'draft'\n cf.dirty = true\n this.fixed++\n }\n }\n\n // Validate createdAt/updatedAt are valid ISO dates\n if (entry.createdAt && isNaN(Date.parse(entry.createdAt))) {\n this.addIssue('error', 'schema', `Invalid createdAt date format \"${entry.createdAt}\"`, model.modelId, entryId, 'createdAt', file)\n }\n if (entry.updatedAt && isNaN(Date.parse(entry.updatedAt))) {\n this.addIssue('error', 'schema', `Invalid updatedAt date format \"${entry.updatedAt}\"`, model.modelId, entryId, 'updatedAt', file)\n }\n\n // Field-level checks (non-system fields)\n const nonSystemFields = fields.filter(f => !f.system)\n for (const field of nonSystemFields) {\n const value = entry[field.fieldId]\n\n // Required check\n if (field.validations?.['required-field']?.value) {\n if (value === undefined || value === null || value === '') {\n if (field.fieldId === 'content' && (model.type === 'MD' || model.type === 'MDX')) continue\n this.addIssue('error', 'schema', `Required field \"${field.fieldId}\" is empty`, model.modelId, entryId, field.fieldId, file)\n continue\n }\n }\n\n if (value === undefined || value === null) continue\n\n // Type check\n const expectedType = getExpectedType(field.componentId, field.fieldType)\n const typeError = validateFieldValue(value, expectedType, field.fieldId)\n if (typeError) {\n this.addIssue('error', 'schema', `Type mismatch: ${typeError}`, model.modelId, entryId, field.fieldId, file)\n continue\n }\n\n // Integer check\n if (field.componentId === 'integer') {\n const intError = validateInteger(value, field.fieldId)\n if (intError) {\n this.addIssue('error', 'schema', intError, model.modelId, entryId, field.fieldId, file)\n }\n }\n\n // Relation integrity\n if (field.componentId === 'one-to-one' && typeof value === 'string') {\n this.checkRelation(model.modelId, entryId, field, value, file)\n }\n if (field.componentId === 'one-to-many' && Array.isArray(value)) {\n for (const refId of value) {\n if (typeof refId === 'string') {\n this.checkRelation(model.modelId, entryId, field, refId, file)\n }\n else {\n this.addIssue('error', 'relation', 'one-to-many array contains non-string value', model.modelId, entryId, field.fieldId, file)\n }\n }\n }\n\n // Relation field missing reference config\n if ((field.componentId === 'one-to-one' || field.componentId === 'one-to-many') && !field.options?.reference?.form?.reference?.value) {\n this.addIssue('warning', 'relation', `Relation field \"${field.fieldId}\" missing options.reference.form.reference.value`, model.modelId, undefined, field.fieldId, `models/${model.modelId}.json`)\n }\n\n // Asset check\n if (field.componentId === 'media' && typeof value === 'string' && value !== '') {\n if (!assets.some(a => a.path === value)) {\n this.addIssue('error', 'asset', `Media path \"${value}\" not found in assets.json`, model.modelId, entryId, field.fieldId, file)\n }\n }\n }\n }\n\n private checkRelation(\n modelId: string,\n entryId: string,\n field: FieldDefinition,\n refId: string,\n file: string,\n ): void {\n const refModelId = field.options?.reference?.form?.reference?.value\n if (!refModelId) return\n\n // Validate referenced ID format\n if (!isValidId(refId)) {\n this.addIssue('error', 'relation', `Relation references invalid ID \"${refId}\"`, modelId, entryId, field.fieldId, file)\n return\n }\n\n // Check referenced entry exists in loaded content\n const refFiles = this.contentFiles.filter(f => f.modelId === refModelId)\n const found = refFiles.some(rf => rf.entries.some(e => e.ID === refId))\n\n if (!found) {\n this.addIssue('error', 'relation', `Referenced entry \"${refId}\" not found in model \"${refModelId}\"`, modelId, entryId, field.fieldId, file)\n }\n }\n\n // ── Cross-Entry Checks ──\n\n private checkUniqueConstraints(\n model: ModelMetadata,\n fields: FieldDefinition[],\n entries: ContentEntry[],\n file: string,\n ): void {\n const uniqueFields = fields.filter(f => f.validations?.['unique-field']?.value && !f.system)\n for (const field of uniqueFields) {\n const seen = new Map<unknown, string>()\n for (const entry of entries) {\n const value = entry[field.fieldId]\n if (value === undefined || value === null || value === '') continue\n const key = typeof value === 'object' ? JSON.stringify(value) : value\n const existing = seen.get(key)\n if (existing) {\n this.addIssue('error', 'schema', `Unique constraint violated: \"${field.fieldId}\" value \"${value}\" duplicated between entries ${existing} and ${entry.ID}`, model.modelId, entry.ID, field.fieldId, file)\n }\n else {\n seen.set(key, entry.ID)\n }\n }\n }\n }\n\n private checkLocaleIdConsistency(\n model: ModelMetadata,\n modelFiles: ContentFile[],\n ): void {\n if (modelFiles.length < 2) return\n\n const referenceFile = modelFiles[0]\n const referenceIds = new Set(referenceFile.entries.map(e => e.ID))\n\n for (let i = 1; i < modelFiles.length; i++) {\n const otherFile = modelFiles[i]\n const otherIds = new Set(otherFile.entries.map(e => e.ID))\n\n for (const id of referenceIds) {\n if (!otherIds.has(id)) {\n this.addIssue('error', 'id', `Entry \"${id}\" exists in ${referenceFile.fileName}.json but missing in ${otherFile.fileName}.json`, model.modelId, id, undefined, `${model.modelId}/${otherFile.fileName}.json`)\n }\n }\n\n for (const id of otherIds) {\n if (!referenceIds.has(id)) {\n this.addIssue('error', 'id', `Entry \"${id}\" exists in ${otherFile.fileName}.json but missing in ${referenceFile.fileName}.json`, model.modelId, id, undefined, `${model.modelId}/${referenceFile.fileName}.json`)\n }\n }\n }\n }\n\n // ── MD/MDX Sync ──\n\n private async checkMarkdownSync(model: ModelMetadata, modelFiles: ContentFile[]): Promise<void> {\n for (const cf of modelFiles) {\n const isLocalized = model.localization && cf.fileName !== model.modelId\n const locale = isLocalized ? cf.fileName : undefined\n\n for (const entry of cf.entries) {\n if (!('slug' in entry) || !entry.slug) {\n this.addIssue('error', 'sync', 'MD/MDX entry missing \"slug\" field', model.modelId, entry.ID, 'slug', `${model.modelId}/${cf.fileName}.json`)\n continue\n }\n\n const mdEntry = entry as MDContentEntry\n const ext = model.type === 'MDX' ? '.mdx' : '.md'\n const baseMdPath = mdEntry.path || model.path || ''\n const mdPath = locale\n ? join(this.repoPath, baseMdPath, locale, `${mdEntry.slug}${ext}`)\n : join(this.repoPath, baseMdPath, `${mdEntry.slug}${ext}`)\n\n if (!await fileExists(mdPath)) {\n this.addIssue('error', 'sync', `Markdown file missing for entry \"${mdEntry.slug}\"`, model.modelId, entry.ID, undefined, mdPath.replace(this.repoPath + '/', ''))\n }\n\n // Check content body is NOT stored in JSON index\n if ('content' in entry && entry.content && typeof entry.content === 'string' && (entry.content as string).length > 0) {\n this.addIssue('warning', 'sync', 'MD/MDX JSON index should not contain \"content\" body — body belongs in markdown file', model.modelId, entry.ID, 'content', `${model.modelId}/${cf.fileName}.json`)\n }\n }\n }\n }\n\n // ── Asset Checks ──\n\n private async loadAssets(): Promise<AssetEntry[]> {\n try {\n return await readJsonFile<AssetEntry[]>(join(this.basePath, 'assets.json'))\n }\n catch {\n this.addIssue('warning', 'structure', 'assets.json not found or invalid')\n return []\n }\n }\n\n // ── Fix: ID Propagation ──\n\n /**\n * After all ID fixes are collected in idRemap, propagate changes\n * to ALL relation fields across ALL loaded content files.\n *\n * This is the critical two-way binding: when entry A's ID changes,\n * every relation field in every model that points to A must be updated.\n */\n private propagateIdChanges(): void {\n if (this.idRemap.size === 0) return\n\n for (const cf of this.contentFiles) {\n const fields = this.fieldsByModel.get(cf.modelId)\n if (!fields) continue\n\n // Find relation fields in this model\n const relationFields = fields.filter(f =>\n f.componentId === 'one-to-one' || f.componentId === 'one-to-many',\n )\n if (relationFields.length === 0) continue\n\n for (const entry of cf.entries) {\n for (const field of relationFields) {\n const value = entry[field.fieldId]\n if (value === undefined || value === null) continue\n\n if (field.componentId === 'one-to-one' && typeof value === 'string') {\n const newId = this.idRemap.get(value)\n if (newId) {\n this.addIssue('warning', 'relation',\n `Updated relation \"${field.fieldId}\": \"${value}\" → \"${newId}\"`,\n cf.modelId, entry.ID, field.fieldId, `${cf.modelId}/${cf.fileName}.json`,\n 'Propagated ID change to relation')\n entry[field.fieldId] = newId\n cf.dirty = true\n this.fixed++\n }\n }\n\n if (field.componentId === 'one-to-many' && Array.isArray(value)) {\n let changed = false\n const updated = (value as string[]).map(refId => {\n const newId = this.idRemap.get(refId)\n if (newId) {\n changed = true\n return newId\n }\n return refId\n })\n if (changed) {\n this.addIssue('warning', 'relation',\n `Updated one-to-many relation \"${field.fieldId}\": propagated ${this.idRemap.size} ID change(s)`,\n cf.modelId, entry.ID, field.fieldId, `${cf.modelId}/${cf.fileName}.json`,\n 'Propagated ID changes to relation array')\n entry[field.fieldId] = updated\n cf.dirty = true\n this.fixed++\n }\n }\n }\n }\n }\n }\n\n // ── Fix: Duplicate IDs ──\n\n private fixDuplicateIds(cf: ContentFile, duplicateId: string): void {\n let first = true\n for (const entry of cf.entries) {\n if (entry.ID === duplicateId) {\n if (first) {\n first = false\n continue\n }\n const newId = generateId()\n this.idRemap.set(`${duplicateId}:dup:${newId}`, newId) // track but don't remap the original\n entry.ID = newId\n cf.dirty = true\n this.fixed++\n }\n }\n }\n\n // ── Write Back ──\n\n private async writeDirtyFiles(): Promise<void> {\n for (const cf of this.contentFiles) {\n if (cf.dirty) {\n await writeJsonFile(cf.filePath, cf.entries)\n }\n }\n }\n\n // ── Utilities ──\n\n private addIssue(\n severity: IssueSeverity,\n category: IssueCategory,\n message: string,\n modelId?: string,\n entryId?: string,\n field?: string,\n file?: string,\n fix?: string,\n ): void {\n this.issues.push({ severity, category, modelId, entryId, field, file, message, fix })\n }\n\n private buildResult(modelCount: number): DoctorResult {\n return {\n summary: {\n total: this.issues.length,\n errors: this.issues.filter(i => i.severity === 'error').length,\n warnings: this.issues.filter(i => i.severity === 'warning').length,\n fixed: this.fixed,\n models: modelCount,\n entries: this.totalEntries,\n },\n issues: this.issues,\n }\n }\n}\n","import { z } from 'zod'\nimport { join } from 'node:path'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainWriter } from '../core/writer.js'\nimport type { WriterConfig } from '../types/index.js'\nimport { Doctor, type DoctorResult } from '../core/doctor.js'\n\nexport function registerDoctorTools(server: McpServer, writer: ContentrainWriter, config: WriterConfig): void {\n server.registerTool(\n 'contentrain_doctor',\n {\n description: [\n 'Run a full diagnostic scan on all Contentrain models, content, and assets.',\n 'Detects: duplicate/invalid IDs, type mismatches, broken relations, missing assets,',\n 'locale ID inconsistencies, orphan directories, MD/MDX sync issues, unique constraint',\n 'violations, missing system fields, and invalid JSON files.',\n 'Use fix=true to auto-repair fixable issues (via git transaction).',\n 'Use modelId to scope the scan to a single model.',\n ].join(' '),\n annotations: { readOnlyHint: true },\n inputSchema: {\n fix: z.boolean().optional().default(false).describe('Auto-fix repairable issues (duplicate IDs, missing timestamps, invalid status). Runs in a git transaction.'),\n modelId: z.string().optional().describe('Scope scan to a specific model ID. Omit to scan all models.'),\n },\n },\n async ({ fix, modelId }) => {\n try {\n if (fix) {\n // Fix mode: run inside a transaction so changes are committed\n const result = await writer.transaction(async (tx) => {\n const doctor = new Doctor(tx.basePath, tx.repoPath)\n return doctor.diagnose({ fix: true, modelId })\n }, { message: `doctor fix${modelId ? ` ${modelId}` : ''}` })\n\n return {\n content: [{ type: 'text' as const, text: formatResult(result) }],\n }\n }\n\n // Read-only mode: scan directly\n const contentrainDir = config.contentrainDir || 'contentrain'\n const basePath = join(config.repoPath, contentrainDir)\n const doctor = new Doctor(basePath, config.repoPath)\n const result = await doctor.diagnose({ fix: false, modelId })\n\n return {\n content: [{ type: 'text' as const, text: formatResult(result) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n}\n\nfunction formatResult(result: DoctorResult): string {\n const { summary, issues } = result\n\n const lines: string[] = []\n\n // Header\n if (summary.errors === 0 && summary.warnings === 0) {\n lines.push('✅ No issues found!')\n }\n else if (summary.errors === 0) {\n lines.push('⚠️ No errors, but some warnings found.')\n }\n else {\n lines.push('❌ Issues detected.')\n }\n\n // Summary\n lines.push('')\n lines.push(`Models scanned: ${summary.models}`)\n lines.push(`Entries scanned: ${summary.entries}`)\n lines.push(`Errors: ${summary.errors}`)\n lines.push(`Warnings: ${summary.warnings}`)\n if (summary.fixed > 0) {\n lines.push(`Fixed: ${summary.fixed}`)\n }\n\n // Issues grouped by category\n if (issues.length > 0) {\n lines.push('')\n\n const grouped = new Map<string, typeof issues>()\n for (const issue of issues) {\n const key = issue.category\n const arr = grouped.get(key) ?? []\n arr.push(issue)\n grouped.set(key, arr)\n }\n\n const categoryLabels: Record<string, string> = {\n structure: '📁 Structure',\n id: '🔑 IDs',\n schema: '📋 Schema',\n relation: '🔗 Relations',\n asset: '🖼️ Assets',\n sync: '📝 MD/MDX Sync',\n }\n\n for (const [category, categoryIssues] of grouped) {\n lines.push(`\\n### ${categoryLabels[category] || category}`)\n for (const issue of categoryIssues) {\n const icon = issue.severity === 'error' ? '❌' : '⚠️'\n const location = [issue.modelId, issue.entryId, issue.field].filter(Boolean).join(' → ')\n const filePart = issue.file ? ` (${issue.file})` : ''\n const fixPart = issue.fix ? ` [FIX: ${issue.fix}]` : ''\n lines.push(` ${icon} ${location ? `[${location}]` : ''}${filePart} ${issue.message}${fixPart}`)\n }\n }\n }\n\n // Also include raw JSON for programmatic consumption\n lines.push('\\n---\\n')\n lines.push(JSON.stringify(result, null, 2))\n\n return lines.join('\\n')\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { ContentrainWriter } from './core/writer.js'\nimport type { WriterConfig } from './types/index.js'\nimport { registerModelTools } from './tools/model.js'\nimport { registerContentTools } from './tools/content.js'\nimport { registerAssetTools } from './tools/asset.js'\nimport { registerDoctorTools } from './tools/doctor.js'\n\nexport function createServer(config: WriterConfig): McpServer {\n const server = new McpServer({\n name: 'contentrain',\n version: '0.1.1',\n })\n\n const writer = new ContentrainWriter(config)\n\n // Register all tools\n registerModelTools(server, writer)\n registerContentTools(server, writer)\n registerAssetTools(server, writer)\n registerDoctorTools(server, writer, config)\n\n return server\n}\n","#!/usr/bin/env node\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createServer } from './server.js'\n\n// Parse config from environment variables or CLI args\nconst repoPath = process.env.CONTENTRAIN_REPO_PATH || process.cwd()\nconst branch = process.env.CONTENTRAIN_BRANCH || 'contentrain'\nconst remote = process.env.CONTENTRAIN_REMOTE || 'origin'\nconst contentrainDir = process.env.CONTENTRAIN_DIR || 'contentrain'\nconst dryRun = process.env.CONTENTRAIN_DRY_RUN === 'true'\nconst authorName = process.env.CONTENTRAIN_AUTHOR_NAME\nconst authorEmail = process.env.CONTENTRAIN_AUTHOR_EMAIL\n\nconst server = createServer({\n repoPath,\n branch,\n remote,\n contentrainDir,\n dryRun,\n author: authorName && authorEmail\n ? { name: authorName, email: authorEmail }\n : undefined,\n})\n\nasync function main(): Promise<void> {\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((err) => {\n console.error('Failed to start Contentrain MCP server:', err)\n process.exit(1)\n})\n\n// Re-export for programmatic use\nexport { createServer } from './server.js'\nexport { ContentrainWriter } from './core/writer.js'\nexport { Doctor } from './core/doctor.js'\nexport type * from './types/index.js'\n"],"mappings":";;;;;;;;;;;;;;AAQA,eAAsB,aAA0B,UAA8B;AAC5E,KAAI;EACF,MAAM,MAAM,MAAM,SAAS,UAAU,QAAQ;AAC7C,SAAO,KAAK,MAAM,IAAI;UAEjB,KAAc;AACnB,MAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,SACnF,QAAO,EAAE;AAEX,QAAM;;;;;;AAOV,eAAsB,cAAc,UAAkB,MAA8B;AAClF,OAAM,UAAU,QAAQ,SAAS,CAAC;CAClC,MAAM,UAAU,KAAK,QAAQ,SAAS,EAAE,QAAQ,YAAY,EAAE,CAAC,SAAS,MAAM,CAAC,OAAO;AAEtF,OAAM,UAAU,SADA,KAAK,UAAU,MAAM,MAAM,EAAE,GAAG,MACd,QAAQ;AAC1C,OAAM,OAAO,SAAS,SAAS;;;;AC7BjC,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CACA;CAEA,YAAY,SAAiB,MAAc,UAAmC,EAAE,EAAE;AAChF,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,OAAO;AACZ,OAAK,UAAU;;;AAcnB,IAAa,qBAAb,cAAwC,iBAAiB;CACvD,YAAY,SAAiB;AAC3B,QAAM,oBAAoB,WAAW,mBAAmB,EAAE,SAAS,CAAC;AACpE,OAAK,OAAO;;;AAIhB,IAAa,qBAAb,cAAwC,iBAAiB;CACvD,YAAY,SAAiB,SAAiB;AAC5C,QAAM,oBAAoB,QAAQ,YAAY,WAAW,mBAAmB;GAAE;GAAS;GAAS,CAAC;AACjG,OAAK,OAAO;;;AAIhB,IAAa,eAAb,cAAkC,iBAAiB;CACjD,YAAY,SAAiB,UAAmC,EAAE,EAAE;AAClE,QAAM,SAAS,kBAAkB,QAAQ;AACzC,OAAK,OAAO;;;AAchB,IAAa,oBAAb,cAAuC,aAAa;CAClD;CAEA,YAAY,mBAA4B;AACtC,QACE,oBACI,gDACA,2BACJ,EAAE,mBAAmB,CACtB;AACD,OAAK,OAAO;AACZ,OAAK,oBAAoB;;;AAc7B,IAAa,qBAAb,cAAwC,iBAAiB;CACvD,YAAY,WAAmB;AAC7B,QAAM,gCAAgC,aAAa,mBAAmB,EAAE,WAAW,CAAC;AACpF,OAAK,OAAO;;;;;;;;AC5EhB,SAAgB,mBAAmB,SAAoC;AACrE,QAAO;EACL;GACE,MAAM;GACN,SAAS;GACT,aAAa;GACb,SAAS,EAAE;GACX,aAAa,EAAE,kBAAkB,EAAE,OAAO,MAAM,EAAE;GAClD,WAAW;GACX,QAAQ;GACR,cAAc;GACd;GACD;EACD;GACE,MAAM;GACN,SAAS;GACT,aAAa;GACb,SAAS,EAAE;GACX,aAAa,EAAE,kBAAkB,EAAE,OAAO,MAAM,EAAE;GAClD,WAAW;GACX,QAAQ;GACR,cAAc;GACd;GACD;EACD;GACE,MAAM;GACN,SAAS;GACT,aAAa;GACb,SAAS,EAAE;GACX,aAAa,EAAE,kBAAkB,EAAE,OAAO,MAAM,EAAE;GAClD,WAAW;GACX,QAAQ;GACR,cAAc;GACd;GACD;EACD;GACE,MAAM;GACN,SAAS;GACT,aAAa;GACb,SAAS,EAAE;GACX,aAAa,EAAE,kBAAkB,EAAE,OAAO,MAAM,EAAE;GAClD,WAAW;GACX,QAAQ;GACR,cAAc;GACd;GACD;EACF;;;;;AAMH,SAAgB,sBAAsB,SAAoC;AACxE,QAAO,CACL;EACE,MAAM;EACN,SAAS;EACT,aAAa;EACb,SAAS,EAAE;EACX,aAAa;GAAE,kBAAkB,EAAE,OAAO,MAAM;GAAE,gBAAgB,EAAE,OAAO,MAAM;GAAE;EACnF,WAAW;EACX,QAAQ;EACR,cAAc;EACd;EACD,EACD;EACE,MAAM;EACN,SAAS;EACT,aAAa;EACb,SAAS,EAAE;EACX,aAAa,EAAE,kBAAkB,EAAE,OAAO,OAAO,EAAE;EACnD,WAAW;EACX,QAAQ;EACR,cAAc;EACd;EACD,CACF;;;;AC1EH,IAAa,eAAb,MAA0B;CACxB,YAAY,UAA0B;AAAlB,OAAA,WAAA;;CAEpB,IAAY,YAAoB;AAC9B,SAAO,KAAK,KAAK,UAAU,SAAS;;CAGtC,IAAY,eAAuB;AACjC,SAAO,KAAK,KAAK,WAAW,gBAAgB;;CAK9C,MAAM,OAAiC;AACrC,SAAO,aAA8B,KAAK,aAAa;;CAGzD,MAAM,IAAI,SAAyC;EAEjD,MAAM,SADS,MAAM,KAAK,MAAM,EACX,MAAK,MAAK,EAAE,YAAY,QAAQ;AACrD,MAAI,CAAC,MAAO,OAAM,IAAI,mBAAmB,QAAQ;AACjD,SAAO;;CAGT,MAAM,UAAU,SAA6C;AAC3D,QAAM,KAAK,IAAI,QAAQ;AAEvB,SAAO,aADY,KAAK,KAAK,WAAW,GAAG,QAAQ,OAAO,CACR;;CAGpD,MAAM,SAAS,SAIZ;EACD,MAAM,QAAQ,MAAM,KAAK,IAAI,QAAQ;EACrC,MAAM,SAAS,MAAM,KAAK,UAAU,QAAQ;AAE5C,SAAO;GAAE;GAAO;GAAQ,cADH,OAAO,QAAO,MAAK,CAAC,EAAE,UAAU,CAAC,EAAE,aAAa;GAC/B;;CAKxC,MAAM,OAAO,OAQc;EACzB,MAAM,SAAS,MAAM,KAAK,MAAM;AAEhC,MAAI,OAAO,MAAK,MAAK,EAAE,YAAY,MAAM,QAAQ,CAC/C,OAAM,IAAI,MAAM,yBAAyB,MAAM,UAAU;EAI3D,MAAM,WAA0B;GAC9B,MAAM,MAAM;GACZ,aAAa,MAAM;GACnB,SAAS,MAAM;GACf,MAAM,MAAM;GACZ,cAAc,MAAM;GACpB,MAAM,MAAM;GACZ,WAAW;GACX,cAAc;GACf;AAED,SAAO,KAAK,SAAS;AACrB,QAAM,cAAc,KAAK,cAAc,OAAO;EAG9C,MAAM,eAAe,mBAAmB,MAAM,QAAQ;EACtD,MAAM,WAAY,MAAM,SAAS,QAAQ,MAAM,SAAS,QACpD,sBAAsB,MAAM,QAAQ,GACpC,EAAE;EACN,MAAM,gBAAmC,MAAM,UAAU,EAAE,EAAE,KAAI,OAAM;GACrE,GAAG;GACH,SAAS,MAAM;GACf,QAAQ;GACR,cAAc;GACf,EAAE;EAEH,MAAM,YAAY;GAAC,GAAG;GAAc,GAAG;GAAU,GAAG;GAAa;AACjE,QAAM,UAAU,KAAK,UAAU;AAC/B,QAAM,cAAc,KAAK,KAAK,WAAW,GAAG,MAAM,QAAQ,OAAO,EAAE,UAAU;EAG7E,MAAM,aAAa,KAAK,KAAK,UAAU,MAAM,QAAQ;AACrD,QAAM,UAAU,WAAW;AAE3B,MAAI,MAAM,cAAc,OAItB,OAAM,cAAc,KAAK,YAAY,GAAG,MAAM,QAAQ,OAAO,EAAE,EAAE,CAAC;AAGpE,SAAO;;CAGT,MAAM,SAAS,SAAiB,OAA+F;EAC7H,MAAM,SAAS,MAAM,KAAK,UAAU,QAAQ;AAE5C,MAAI,OAAO,MAAK,MAAK,EAAE,YAAY,MAAM,QAAQ,CAC/C,OAAM,IAAI,MAAM,yBAAyB,MAAM,QAAQ,YAAY,UAAU;EAG/E,MAAM,WAA4B;GAChC,GAAG;GACH;GACA,QAAQ;GACR,cAAc;GACf;AAED,SAAO,KAAK,SAAS;AACrB,QAAM,cAAc,KAAK,KAAK,WAAW,GAAG,QAAQ,OAAO,EAAE,OAAO;AAEpE,SAAO;;CAGT,MAAM,YAAY,SAAiB,SAAgC;EACjE,MAAM,SAAS,MAAM,KAAK,UAAU,QAAQ;EAC5C,MAAM,QAAQ,OAAO,MAAK,MAAK,EAAE,YAAY,QAAQ;AAErD,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,oBAAoB,QAAQ,YAAY,UAAU;AAC9E,MAAI,MAAM,OAAQ,OAAM,IAAI,MAAM,+BAA+B,UAAU;EAE3E,MAAM,WAAW,OAAO,QAAO,MAAK,EAAE,YAAY,QAAQ;AAC1D,QAAM,cAAc,KAAK,KAAK,WAAW,GAAG,QAAQ,OAAO,EAAE,SAAS;;CAGxE,MAAM,OAAO,SAAgC;EAC3C,MAAM,SAAS,MAAM,KAAK,MAAM;EAChC,MAAM,WAAW,OAAO,QAAO,MAAK,EAAE,YAAY,QAAQ;AAE1D,MAAI,SAAS,WAAW,OAAO,OAC7B,OAAM,IAAI,mBAAmB,QAAQ;AAGvC,QAAM,cAAc,KAAK,cAAc,SAAS;EAGhD,MAAM,EAAE,YAAY,cAAc,MAAM,OAAO,qBAAA,MAAA,MAAA,EAAA,EAAA;AAC/C,QAAM,WAAW,KAAK,KAAK,WAAW,GAAG,QAAQ,OAAO,CAAC;AAGzD,QAAM,UAAU,KAAK,KAAK,UAAU,QAAQ,CAAC;;;;;AC1JjD,MAAM,aAAa;;;;;AAMnB,SAAgB,aAAqB;AACnC,QAAO,YAAY,CAAC,MAAM,IAAI,CAAC,KAAK;;;;;AAMtC,SAAgB,UAAU,IAAqB;AAC7C,QAAO,WAAW,KAAK,GAAG;;;;;;;ACR5B,SAAS,iBAAiB,OAAwC;CAChE,MAAM,QAAkB,CAAC,MAAM;AAC/B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;AAChD,MAAI,QAAQ,UAAW;AACvB,MAAI,OAAO,UAAU,SACnB,OAAM,KAAK,GAAG,IAAI,KAAK,MAAM,GAAG;WAEzB,OAAO,UAAU,aAAa,OAAO,UAAU,SACtD,OAAM,KAAK,GAAG,IAAI,IAAI,QAAQ;WAEvB,MAAM,QAAQ,MAAM,CAC3B,OAAM,KAAK,GAAG,IAAI,IAAI,KAAK,UAAU,MAAM,GAAG;;AAGlD,OAAM,KAAK,MAAM;AACjB,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAS,aAAa,MAA4B;AAChD,QAAO,SAAS,QAAQ,SAAS;;;;;AAMnC,SAAS,oBACP,UACA,OACA,OACA,QACQ;CACR,MAAM,WAAW,MAAM,QAAQ,MAAM,QAAQ;CAC7C,MAAM,MAAM,aAAa,MAAM,KAAqB;AAEpD,KAAI,OACF,QAAO,KAAK,UAAU,UAAU,QAAQ,GAAG,MAAM,OAAO,MAAM;AAEhE,QAAO,KAAK,UAAU,UAAU,GAAG,MAAM,OAAO,MAAM;;AAGxD,IAAa,eAAb,MAA0B;CACxB,YAAY,UAA0B;AAAlB,OAAA,WAAA;;;;;CAKpB,MAAM,MACJ,OACA,OACA,SACA,QACiB;EACjB,MAAM,WAAW,oBAAoB,KAAK,UAAU,OAAO,OAAO,OAAO;AACzE,QAAM,UAAU,KAAK,UAAU,KAAK,CAAC;AAMrC,QAAM,UAAU,UAFI,GAFA,iBAAiB,MAAM,CAER,MADtB,WAAW,GACsB,KAEP,QAAQ;AAC/C,SAAO;;;;;CAMT,MAAM,KACJ,OACA,OACA,QACiB;EACjB,MAAM,WAAW,oBAAoB,KAAK,UAAU,OAAO,OAAO,OAAO;AACzE,MAAI;GAGF,MAAM,SAFM,MAAM,SAAS,UAAU,QAAQ,EAE3B,MAAM,qCAAqC;AAC7D,UAAO,QAAQ,MAAM,GAAG,MAAM,GAAG;UAE7B;AACJ,UAAO;;;;;;CAOX,MAAM,OACJ,OACA,OACA,QACe;AAEf,QAAM,WADW,oBAAoB,KAAK,UAAU,OAAO,OAAO,OAAO,CAC/C;;;;;AC9F9B,IAAa,iBAAb,MAA4B;CAC1B;CACA;CAEA,YACE,UACA,UACA;AAFQ,OAAA,WAAA;AACA,OAAA,WAAA;AAER,OAAK,eAAe,IAAI,aAAa,SAAS;AAC9C,OAAK,eAAe,IAAI,aAAa,SAAS;;CAKhD,WAAmB,SAAyB;AAC1C,SAAO,KAAK,KAAK,UAAU,QAAQ;;CAGrC,gBAAwB,SAAiB,QAAyB;EAChE,MAAM,MAAM,KAAK,WAAW,QAAQ;AACpC,SAAO,SACH,KAAK,KAAK,GAAG,OAAO,OAAO,GAC3B,KAAK,KAAK,GAAG,QAAQ,OAAO;;CAKlC,MAAM,KAAK,SAAiB,QAA0C;EACpE,MAAM,QAAQ,MAAM,KAAK,aAAa,IAAI,QAAQ;AAElD,SAAO,aADU,KAAK,mBAAmB,OAAO,OAAO,CACV;;CAG/C,MAAM,IAAI,SAAiB,SAAiB,QAAwC;EAElF,MAAM,SADU,MAAM,KAAK,KAAK,SAAS,OAAO,EAC1B,MAAK,MAAK,EAAE,OAAO,QAAQ;AACjD,MAAI,CAAC,MAAO,OAAM,IAAI,mBAAmB,SAAS,QAAQ;AAC1D,SAAO;;CAGT,MAAM,WAAW,SAAoC;AAEnD,MAAI,EADU,MAAM,KAAK,aAAa,IAAI,QAAQ,EACvC,aAAc,QAAO,EAAE;AAGlC,UAFc,MAAM,cAAc,KAAK,WAAW,QAAQ,CAAC,EAE9C,QAAO,MAAK,MAAM,QAAQ;;CAKzC,MAAM,OACJ,SACA,MACA,SACiB;EACjB,MAAM,QAAQ,MAAM,KAAK,aAAa,IAAI,QAAQ;EAClD,MAAM,KAAK,SAAS,MAAM,YAAY;EACtC,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;EAEpC,MAAM,QAAsB;GAC1B,IAAI;GACJ,WAAW;GACX,WAAW;GACX,QAAQ,SAAS,UAAU;GAC3B,WAAW;GACX,GAAG;GACJ;AAED,MAAI,MAAM,aACR,OAAM,KAAK,gBAAgB,OAAO,OAAO,SAAS,QAAQ,SAAS,QAAQ;MAG3E,OAAM,KAAK,YAAY,OAAO,OAAO,KAAA,GAAW,SAAS,QAAQ;AAGnE,SAAO;;CAGT,MAAM,OACJ,SACA,SACA,MACA,SACuB;EACvB,MAAM,QAAQ,MAAM,KAAK,aAAa,IAAI,QAAQ;EAClD,MAAM,SAAS,MAAM,eAAe,SAAS,SAAS,KAAA;EACtD,MAAM,WAAW,KAAK,mBAAmB,OAAO,OAAO;EACvD,MAAM,UAAU,MAAM,aAA6B,SAAS;EAE5D,MAAM,QAAQ,QAAQ,WAAU,MAAK,EAAE,OAAO,QAAQ;AACtD,MAAI,UAAU,GAAI,OAAM,IAAI,mBAAmB,SAAS,QAAQ;EAGhE,MAAM,UAAwB;GAC5B,GAAG,QAAQ;GACX,GAAG;GACH,IAAI;GACJ,WAAW,QAAQ,OAAO;GAC1B,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;AAED,UAAQ,SAAS;AACjB,QAAM,cAAc,UAAU,QAAQ;AAGtC,OAAK,MAAM,SAAS,QAAQ,MAAM,SAAS,UAAU,UAAU,SAAS;GACtE,MAAM,YAAY,SAAS,WAAW,MAAM,KAAK,aAAa,KAAK,OAAO,SAA2B,OAAO;AAC5G,SAAM,KAAK,aAAa,MAAM,OAAO,SAA2B,WAAW,OAAO;;AAGpF,SAAO;;CAGT,MAAM,OAAO,SAAiB,SAAgC;EAC5D,MAAM,QAAQ,MAAM,KAAK,aAAa,IAAI,QAAQ;AAElD,MAAI,MAAM,cAAc;GACtB,MAAM,UAAU,MAAM,KAAK,WAAW,QAAQ;AAC9C,QAAK,MAAM,UAAU,QACnB,OAAM,KAAK,eAAe,OAAO,SAAS,OAAO;QAInD,OAAM,KAAK,eAAe,OAAO,QAAQ;;CAM7C,mBAA2B,OAAsB,QAAyB;AACxE,MAAI,MAAM,gBAAgB,OACxB,QAAO,KAAK,gBAAgB,MAAM,SAAS,OAAO;AAEpD,MAAI,MAAM,gBAAgB,CAAC,OAEzB,QAAO,KAAK,gBAAgB,MAAM,SAAS,MAAM,QAAQ;AAE3D,SAAO,KAAK,gBAAgB,MAAM,QAAQ;;CAG5C,MAAc,gBACZ,OACA,OACA,QACA,SACe;AACf,MAAI,OAEF,OAAM,KAAK,YAAY,OAAO,OAAO,QAAQ,QAAQ;OAElD;GAEH,MAAM,UAAU,MAAM,KAAK,WAAW,MAAM,QAAQ;AACpD,OAAI,QAAQ,WAAW,EAErB,OAAM,IAAI,MACR,UAAU,MAAM,QAAQ,4GAEzB;AAGH,QAAK,MAAM,OAAO,QAChB,OAAM,KAAK,YAAY,OAAO,OAAO,KAAK,QAAQ;;;CAKxD,MAAc,YACZ,OACA,OACA,QACA,SACe;EACf,MAAM,WAAW,KAAK,mBAAmB,OAAO,OAAO;AACvD,QAAM,UAAU,KAAK,UAAU,KAAK,CAAC;EAErC,MAAM,UAAU,MAAM,aAA6B,SAAS;AAG5D,MAAI,QAAQ,MAAK,MAAK,EAAE,OAAO,MAAM,GAAG,CACtC,OAAM,IAAI,MAAM,iBAAiB,MAAM,GAAG,qBAAqB,MAAM,UAAU;AAGjF,UAAQ,KAAK,MAAM;AACnB,QAAM,cAAc,UAAU,QAAQ;AAGtC,OAAK,MAAM,SAAS,QAAQ,MAAM,SAAS,UAAU,UAAU,MAC7D,OAAM,KAAK,aAAa,MAAM,OAAO,OAAyB,WAAW,IAAI,OAAO;;CAIxF,MAAc,eAAe,OAAsB,SAAiB,QAAgC;EAClG,MAAM,WAAW,KAAK,mBAAmB,OAAO,OAAO;EACvD,MAAM,UAAU,MAAM,aAA6B,SAAS;EAE5D,MAAM,QAAQ,QAAQ,MAAK,MAAK,EAAE,OAAO,QAAQ;AACjD,MAAI,CAAC,MAAO;AAGZ,QAAM,cAAc,UADH,QAAQ,QAAO,MAAK,EAAE,OAAO,QAAQ,CACf;AAGvC,OAAK,MAAM,SAAS,QAAQ,MAAM,SAAS,UAAU,UAAU,MAC7D,OAAM,KAAK,aAAa,OAAO,OAAO,OAAyB,OAAO;;;;;AC9M5E,IAAa,eAAb,MAA0B;CACxB;CAEA,YACE,UACA,UACA,QACA;AAHQ,OAAA,WAAA;AACA,OAAA,WAAA;AACA,OAAA,SAAA;AAER,OAAK,aAAa,KAAK,UAAU,cAAc;;CAGjD,MAAM,OAA8B;AAClC,SAAO,aAA2B,KAAK,WAAW;;CAGpD,MAAM,IAAI,WAAoD;AAE5D,UADe,MAAM,KAAK,MAAM,EAClB,MAAK,MAAK,EAAE,SAAS,UAAU;;CAG/C,MAAM,SAAS,OAIS;EAEtB,MAAM,WAAW,KAAK,KAAK,UAAU,MAAM,KAAK;AAChD,MAAI,CAAC,MAAM,WAAW,SAAS,CAC7B,OAAM,IAAI,MAAM,iCAAiC,MAAM,OAAO;EAGhE,MAAM,SAAS,MAAM,KAAK,MAAM;AAGhC,MAAI,OAAO,MAAK,MAAK,EAAE,SAAS,MAAM,KAAK,CACzC,OAAM,IAAI,MAAM,6BAA6B,MAAM,OAAO;EAI5D,MAAM,QAAQ,MAAM,KAAK,SAAS;EAClC,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;EAEpC,MAAM,QAAoB;GACxB,MAAM,MAAM;GACZ,UAAU,MAAM;GAChB,MAAM,MAAM;GACZ,KAAK,MAAM,OAAO;GAClB,MAAM;IACJ,MAAM;KACJ,MAAM,KAAK,OAAO,QAAQ,QAAQ;KAClC,OAAO,KAAK,OAAO,QAAQ,SAAS;KACrC;IACD,WAAW;IACX,WAAW;IACZ;GACF;AAED,SAAO,KAAK,MAAM;AAClB,QAAM,cAAc,KAAK,YAAY,OAAO;AAE5C,SAAO;;CAGT,MAAM,WAAW,WAAkC;EACjD,MAAM,SAAS,MAAM,KAAK,MAAM;EAChC,MAAM,WAAW,OAAO,QAAO,MAAK,EAAE,SAAS,UAAU;AAEzD,MAAI,SAAS,WAAW,OAAO,OAC7B,OAAM,IAAI,mBAAmB,UAAU;AAGzC,QAAM,cAAc,KAAK,YAAY,SAAS;;CAGhD,MAAM,OAAO,WAAmB,SAAmE;EACjG,MAAM,SAAS,MAAM,KAAK,MAAM;EAChC,MAAM,QAAQ,OAAO,WAAU,MAAK,EAAE,SAAS,UAAU;AAEzD,MAAI,UAAU,GAAI,OAAM,IAAI,mBAAmB,UAAU;AAEzD,MAAI,QAAQ,QAAQ,KAAA,EAAW,QAAO,OAAO,MAAM,QAAQ;AAC3D,MAAI,QAAQ,aAAa,KAAA,EAAW,QAAO,OAAO,WAAW,QAAQ;AACrE,MAAI,OAAO,OAAO,KAChB,QAAO,OAAO,KAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAG1D,QAAM,cAAc,KAAK,YAAY,OAAO;AAC5C,SAAO,OAAO;;;;;AC3FlB,MAAM,iBAAiB;CAAC;CAAS;CAAW;CAAU;;;;AAKtD,SAAgB,gBAAgB,aAA0B,WAA8B;AACtF,SAAQ,aAAR;EAEE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,eACH,QAAO;EAGT,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,UACH,QAAO;EAGT,KAAK;EACL,KAAK,SACH,QAAO;EAGT,KAAK;EACL,KAAK,YACH,QAAO;EAGT,KAAK,QACH,QAAO;EAGT,KAAK,aACH,QAAO;EACT,KAAK,cACH,QAAO;EAET,QACE,QAAO,cAAc,UAAU,UAAU;;;;;;AAO/C,SAAgB,WAAW,IAA4B;AACrD,KAAI,OAAO,OAAO,SAAU,QAAO;AACnC,KAAI,CAAC,UAAU,GAAG,CAAE,QAAO;AAC3B,QAAO;;;;;AAMT,SAAgB,eAAe,QAAgC;AAC7D,KAAI,OAAO,WAAW,SAAU,QAAO;AACvC,KAAI,CAAC,eAAe,SAAS,OAAwC,CACnE,QAAO,0BAA0B,eAAe,KAAK,KAAK;AAE5D,QAAO;;;;;AAMT,SAAgB,mBAAmB,OAAgB,cAAsB,SAAgC;AACvG,KAAI,UAAU,QAAQ,UAAU,KAAA,EAAW,QAAO;AAElD,SAAQ,cAAR;EACE,KAAK;AACH,OAAI,OAAO,UAAU,SAAU,QAAO,GAAG,QAAQ;AACjD;EACF,KAAK;AACH,OAAI,OAAO,UAAU,SAAU,QAAO,GAAG,QAAQ;AACjD;EACF,KAAK;AACH,OAAI,OAAO,UAAU,UAAW,QAAO,GAAG,QAAQ;AAClD;EACF,KAAK;AACH,OAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO,GAAG,QAAQ;AAC7C;;AAGJ,QAAO;;;;;AAMT,SAAgB,gBAAgB,OAAgB,SAAgC;AAC9E,KAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,MAAM,CACvD,QAAO,GAAG,QAAQ;AAEpB,QAAO;;;;ACpGT,IAAa,YAAb,MAAuB;CACrB;CAEA,YAAY,UAA0B;AAAlB,OAAA,WAAA;AAClB,OAAK,eAAe,IAAI,aAAa,SAAS;;;;;CAMhD,MAAM,SACJ,SACA,MACA,SAC2B;EAC3B,MAAM,SAAiC,EAAE;EACzC,MAAM,EAAE,QAAQ,UAAU,MAAM,KAAK,aAAa,SAAS,QAAQ;EAEnE,MAAM,kBAAkB,OAAO,QAAO,MAAK,CAAC,EAAE,OAAO;AAErD,OAAK,MAAM,SAAS,iBAAiB;GACnC,MAAM,QAAQ,KAAK,MAAM;AAGzB,OAAI,MAAM,cAAc,mBAAmB;QACrC,CAAC,SAAS,aAAa,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,KAAK;AAEjF,SAAI,MAAM,YAAY,cAAc,MAAM,SAAS,QAAQ,MAAM,SAAS,OACxE;AAEF,YAAO,KAAK;MACV,SAAS,MAAM;MACf,SAAS,GAAG,MAAM,KAAK;MACvB,MAAM;MACP,CAAC;AACF;;;AAIJ,OAAI,UAAU,KAAA,KAAa,UAAU,KAAM;GAI3C,MAAM,YAAY,mBAAmB,OADhB,gBAAgB,MAAM,aAAa,MAAM,UAAU,EACd,MAAM,QAAQ;AACxE,OAAI,WAAW;AACb,WAAO,KAAK;KAAE,SAAS,MAAM;KAAS,SAAS;KAAW,MAAM;KAAc,CAAC;AAC/E;;AAIF,OAAI,MAAM,gBAAgB,WAAW;IACnC,MAAM,WAAW,gBAAgB,OAAO,MAAM,QAAQ;AACtD,QAAI,SACF,QAAO,KAAK;KAAE,SAAS,MAAM;KAAS,SAAS;KAAU,MAAM;KAAiB,CAAC;;AAKrF,OAAI,MAAM,gBAAgB,gBAAgB,OAAO,UAAU,UAAU;IACnE,MAAM,WAAW,MAAM,SAAS,WAAW,MAAM,WAAW;AAC5D,QAAI,UAAU;KACZ,MAAM,iBAAiB,MAAM,KAAK,uBAAuB,UAAU,MAAM;AACzE,SAAI,eACF,QAAO,KAAK;MAAE,SAAS,MAAM;MAAS,SAAS;MAAgB,MAAM;MAAsB,CAAC;;;AAKlG,OAAI,MAAM,gBAAgB,iBAAiB,MAAM,QAAQ,MAAM,EAAE;IAC/D,MAAM,WAAW,MAAM,SAAS,WAAW,MAAM,WAAW;AAC5D,QAAI;UACG,MAAM,SAAS,MAClB,KAAI,OAAO,UAAU,UAAU;MAC7B,MAAM,iBAAiB,MAAM,KAAK,uBAAuB,UAAU,MAAM;AACzE,UAAI,eACF,QAAO,KAAK;OAAE,SAAS,MAAM;OAAS,SAAS;OAAgB,MAAM;OAAsB,CAAC;;;;AAQtG,OAAI,MAAM,gBAAgB,WAAW,OAAO,UAAU,YAAY,UAAU,IAAI;IAC9E,MAAM,aAAa,MAAM,KAAK,iBAAiB,MAAM;AACrD,QAAI,WACF,QAAO,KAAK;KAAE,SAAS,MAAM;KAAS,SAAS;KAAY,MAAM;KAAgB,CAAC;;;AAMxF,MAAI,CAAC,SAAS,UAAU;GACtB,MAAM,eAAe,gBAAgB,QAAO,MAAK,EAAE,cAAc,iBAAiB,MAAM;AACxF,QAAK,MAAM,SAAS,cAAc;IAChC,MAAM,QAAQ,KAAK,MAAM;AACzB,QAAI,UAAU,KAAA,KAAa,UAAU,KAAM;IAE3C,MAAM,cAAc,MAAM,KAAK,gBAAgB,SAAS,MAAM,SAAS,MAAM;AAC7E,QAAI,YACF,QAAO,KAAK;KAAE,SAAS,MAAM;KAAS,SAAS;KAAa,MAAM;KAAgB,CAAC;;;AAKzF,SAAO;GACL,OAAO,OAAO,WAAW;GACzB;GACD;;;;;CAMH,WAAW,IAA8B;EACvC,MAAM,QAAQ,WAAW,GAAG;AAC5B,MAAI,MACF,QAAO;GAAE,OAAO;GAAO,QAAQ,CAAC;IAAE,SAAS;IAAM,SAAS;IAAO,MAAM;IAAa,CAAC;GAAE;AAEzF,SAAO;GAAE,OAAO;GAAM,QAAQ,EAAE;GAAE;;;;;CAMpC,eAAe,QAAkC;EAC/C,MAAM,QAAQ,eAAe,OAAO;AACpC,MAAI,MACF,QAAO;GAAE,OAAO;GAAO,QAAQ,CAAC;IAAE,SAAS;IAAU,SAAS;IAAO,MAAM;IAAgB,CAAC;GAAE;AAEhG,SAAO;GAAE,OAAO;GAAM,QAAQ,EAAE;GAAE;;CAKpC,MAAc,uBAAuB,YAAoB,YAA4C;AACnG,MAAI;AACF,SAAM,KAAK,aAAa,IAAI,WAAW;GAEvC,MAAM,aAAa,KAAK,KAAK,UAAU,WAAW;GAClD,MAAM,EAAE,kBAAkB,MAAM,OAAO,qBAAA,MAAA,MAAA,EAAA,EAAA;GACvC,MAAM,QAAQ,MAAM,cAAc,WAAW;AAE7C,QAAK,MAAM,QAAQ,MAEjB,MADgB,MAAM,aAA6B,KAAK,YAAY,GAAG,KAAK,OAAO,CAAC,EACxE,MAAK,MAAK,EAAE,OAAO,WAAW,CAAE,QAAO;AAGrD,UAAO,oBAAoB,WAAW,sBAAsB;UAExD;AACJ,UAAO,oBAAoB,WAAW;;;CAI1C,MAAc,iBAAiB,WAA2C;AACxE,MAAI;AAEF,QADe,MAAM,aAAsC,KAAK,KAAK,UAAU,cAAc,CAAC,EACnF,MAAK,MAAK,EAAE,SAAS,UAAU,CAAE,QAAO;AACnD,UAAO,gCAAgC;UAEnC;AACJ,UAAO;;;CAIX,MAAc,gBAAgB,SAAiB,SAAiB,OAAwC;AACtG,MAAI;GACF,MAAM,aAAa,KAAK,KAAK,UAAU,QAAQ;GAC/C,MAAM,EAAE,kBAAkB,MAAM,OAAO,qBAAA,MAAA,MAAA,EAAA,EAAA;GACvC,MAAM,QAAQ,MAAM,cAAc,WAAW;AAE7C,QAAK,MAAM,QAAQ,MAEjB,MADgB,MAAM,aAA6B,KAAK,YAAY,GAAG,KAAK,OAAO,CAAC,EACxE,MAAK,MAAK,EAAE,aAAa,MAAM,CACzC,QAAO,GAAG,QAAQ,UAAU,MAAM;AAGtC,UAAO;UAEH;AACJ,UAAO;;;;;;;;;;;;;;;AChLb,SAAgB,gBACd,MACA,MACA,QACgB;CAChB,MAAM,UAAU,UAAU,KAAK;CAC/B,MAAM,UAAU,UAAU,KAAK;CAE/B,MAAM,SAAyB,EAAE;CACjC,MAAM,+BAAe,IAAI,KAAa;AAGtC,MAAK,MAAM,cAAc,QAAQ;EAC/B,MAAM,KAAK,WAAW;AACtB,eAAa,IAAI,GAAG;EAEpB,MAAM,YAAY,QAAQ,IAAI,GAAG;EACjC,MAAM,WAAW,QAAQ,IAAI,GAAG;AAEhC,MAAI,CAAC,YAAY,UAEf;AAGF,MAAI,YAAY,UAEd,QAAO,KAAK,WAAW,WAAW,UAAU,WAAW,CAAC;MAIxD,QAAO,KAAK,WAAW;;AAK3B,MAAK,MAAM,YAAY,KACrB,KAAI,CAAC,aAAa,IAAI,SAAS,GAAG,CAChC,QAAO,KAAK,SAAS;AAIzB,QAAO;;;;;;AAOT,SAAS,WACP,MACA,MACA,QACc;CACd,MAAM,SAAuB,EAAE,GAAG,QAAQ;AAE1C,MAAK,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;EACnC,MAAM,UAAU,KAAK,UAAU,KAAK,KAAK;EACzC,MAAM,SAAS,KAAK,UAAU,KAAK,KAAK;EACxC,MAAM,WAAW,KAAK,UAAU,OAAO,KAAK;AAE5C,MAAI,WAAW,WAAW,aAAa,QAErC,QAAO,OAAO,KAAK;WAEZ,WAAW,WAAW,aAAa,QAE1C,QAAO,OAAO,KAAK;;AAKvB,QAAO;;AAGT,SAAS,UAAU,SAAoD;CACrE,MAAM,sBAAM,IAAI,KAA2B;AAC3C,MAAK,MAAM,SAAS,QAClB,KAAI,IAAI,MAAM,IAAI,MAAM;AAE1B,QAAO;;;;AChFT,IAAa,iBAAb,MAA4B;CAC1B;CAEA,YAAY,QAAsB;AAChC,OAAK,SAAS;GACZ,GAAG;GACH,QAAQ,OAAO,UAAU;GACzB,eAAe,OAAO,iBAAiB;GACvC,cAAc,OAAO,gBAAgB;GACtC;;;;;CAMH,MAAM,iBAA2C;EAC/C,MAAM,KAAK,YAAY,EAAE,CAAC,SAAS,MAAM;EACzC,MAAM,eAAe,KAAK,QAAQ,EAAE,mBAAmB,KAAK;EAC5D,MAAM,MAAM,UAAU,KAAK,OAAO,SAAS;AAE3C,MAAI;AAEF,SAAM,IAAI,MAAM,KAAK,OAAO,QAAQ,KAAK,OAAO,OAAO;GAGvD,MAAM,eAAe,GAAG,KAAK,OAAO,OAAO,GAAG,KAAK,OAAO;GAC1D,MAAM,cAAc,oBAAoB;AAExC,SAAM,IAAI,IAAI;IACZ;IAAY;IACZ;IAAM;IACN;IACA;IACD,CAAC;AAEF,UAAO;IACL,MAAM;IACN,QAAQ;IACR;IACD;WAEI,KAAK;AACV,SAAM,IAAI,aAAa,8BAA8B,OAAO;IAC1D,QAAQ,KAAK,OAAO;IACpB;IACD,CAAC;;;;;;CAON,MAAM,OAAO,KAAsB,SAAkC;EACnE,MAAM,MAAM,UAAU,IAAI,KAAK;EAC/B,MAAM,cAAc,GAAG,KAAK,OAAO,aAAa,GAAG;AAEnD,QAAM,IAAI,IAAI,KAAK;AAInB,OADe,MAAM,IAAI,QAAQ,EACtB,MAAM,WAAW,EAC1B,QAAO;EAGT,MAAM,aAAuB,EAAE;AAC/B,MAAI,KAAK,OAAO,OACd,YAAW,KAAK,YAAY,GAAG,KAAK,OAAO,OAAO,KAAK,IAAI,KAAK,OAAO,OAAO,MAAM,GAAG;AAOzF,UAJe,MAAM,IAAI,OAAO,aAAa,KAAA,GAAW,OAAO,YAC7D,WAAW,SAAS,CAAC,CAAC,YAAY,GAAG,KAAK,OAAO,OAAQ,KAAK,IAAI,KAAK,OAAO,OAAQ,MAAM,GAAG,CAAC,GAAG,EAAE,CACtG,CAAC,EAEY;;;;;CAMhB,MAAM,cAAc,KAAqC;EACvD,MAAM,MAAM,UAAU,IAAI,KAAK;EAC/B,MAAM,cAAc,KAAK,OAAO;AAEhC,OAAK,IAAI,UAAU,GAAG,WAAW,aAAa,UAC5C,KAAI;AAEF,SAAM,IAAI,KAAK,KAAK,OAAO,QAAQ,GAAG,IAAI,OAAO,GAAG,KAAK,OAAO,SAAS;AACzE;UAEI;AACJ,OAAI,YAAY,YACd,OAAM,IAAI,kBAAkB,KAAK;AAInC,OAAI;AACF,UAAM,KAAK,gBAAgB,IAAI;YAE1B,UAAU;AACf,QAAI,YAAY,YACd,OAAM,IAAI,aAAa,qCAAqC,YAAY,YAAY,EAClF,eAAe,OAAO,SAAS,EAChC,CAAC;;;;;;;CAUZ,MAAM,QAAQ,KAAqC;AACjD,MAAI;AAEF,SADY,UAAU,KAAK,OAAO,SAAS,CACjC,IAAI;IAAC;IAAY;IAAU,IAAI;IAAM;IAAU,CAAC;UAEtD;AAEJ,SAAM,UAAU,IAAI,KAAK;;AAI3B,MAAI;AAEF,SADY,UAAU,KAAK,OAAO,SAAS,CACjC,IAAI;IAAC;IAAU;IAAM,IAAI;IAAO,CAAC;UAEvC;;;;;CAQR,MAAM,YACJ,IACA,SACY;EACZ,MAAM,MAAM,MAAM,KAAK,gBAAgB;AAEvC,MAAI;GACF,MAAM,SAAS,MAAM,GAAG,IAAI,KAAK;AAGjC,OADmB,MAAM,KAAK,OAAO,KAAK,QAAQ,CAEhD,OAAM,KAAK,cAAc,IAAI;AAG/B,UAAO;YAED;AACN,SAAM,KAAK,QAAQ,IAAI;;;;;;CAS3B,MAAc,gBAAgB,KAAqC;EACjE,MAAM,MAAM,UAAU,IAAI,KAAK;EAC/B,MAAM,eAAe,GAAG,KAAK,OAAO,OAAO,GAAG,KAAK,OAAO;AAG1D,QAAM,IAAI,MAAM,KAAK,OAAO,QAAQ,KAAK,OAAO,OAAO;EAIvD,MAAM,cADY,MAAM,IAAI,IAAI;GAAC;GAAc,IAAI;GAAQ;GAAa,CAAC,EAC5C,MAAM;EAInC,MAAM,gBADa,MAAM,IAAI,IAAI;GAAC;GAAQ;GAAe,GAAG,aAAa,KAAK,IAAI;GAAS,CAAC,EAC5D,MAAM,CAAC,MAAM,KAAK,CAAC,QAAQ,MAAc,EAAE,SAAS,QAAQ,CAAC;AAG7F,OAAK,MAAM,QAAQ,cAAc;AAC/B,OAAI,CAAC,KAAK,SAAS,eAAe,IAAI,CAAC,KAAK,WAAW,eAAe,CAAE;AAExE,OAAI;IAEF,MAAM,WAAW,KAAK,IAAI,MAAM,KAAK;IACrC,MAAM,OAAO,MAAM,KAAK,eAA+B,KAAK,YAAY,KAAK;IAC7E,MAAM,OAAO,MAAM,aAA6B,SAAS;IACzD,MAAM,YAAY,MAAM,IAAI,IAAI,CAAC,QAAQ,GAAG,aAAa,GAAG,OAAO,CAAC;AAKpE,UAAM,cAAc,UADL,gBAAgB,MAAM,MAHtB,KAAK,MAAM,UAAU,CAGc,CACb;WAEjC;;AAMR,QAAM,IAAI,IAAI;GAAC;GAAS;GAAU;GAAa,CAAC;AAChD,QAAM,IAAI,IAAI,KAAK;AAGnB,OADe,MAAM,IAAI,QAAQ,EACtB,MAAM,SAAS,EACxB,OAAM,IAAI,OAAO,GAAG,KAAK,OAAO,aAAa,oCAAoC;;CAIrF,MAAc,eAAkB,KAAgB,QAAgB,UAA8B;AAC5F,MAAI;GACF,MAAM,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAC;AAC5D,UAAO,KAAK,MAAM,IAAI;UAElB;AACJ,UAAO,EAAE;;;;;;;;;;;;;;AC5Mf,IAAa,oBAAb,MAA+B;CAC7B;CACA;CAGA;CACA;CACA;CACA;CACA;CAEA,YAAY,QAAsB;AAChC,OAAK,SAAS;AACd,OAAK,iBAAiB,OAAO,kBAAkB;EAC/C,MAAM,WAAW,KAAK,OAAO,UAAU,KAAK,eAAe;AAE3D,OAAK,QAAQ,IAAI,aAAa,SAAS;AACvC,OAAK,UAAU,IAAI,eAAe,UAAU,OAAO,SAAS;AAC5D,OAAK,QAAQ,IAAI,aAAa,UAAU,OAAO,UAAU,OAAO;AAChE,OAAK,YAAY,IAAI,UAAU,SAAS;AACxC,OAAK,UAAU,IAAI,eAAe,OAAO;;;;;;;CAQ3C,MAAM,YACJ,IACA,SACY;AACZ,MAAI,KAAK,OAAO,OACd,QAAO,KAAK,kBAAkB,GAAG;AAGnC,SAAO,KAAK,QAAQ,YAClB,OAAO,iBAAyB;AAE9B,UAAO,GADU,KAAK,eAAe,aAAa,CAC/B;KAErB,SAAS,WAAW,iBACrB;;;;;;CAOH,MAAc,kBACZ,IACY;EACZ,MAAM,eAAe,MAAM,QAAQ,KAAK,QAAQ,EAAE,mBAAmB,CAAC;EACtE,MAAM,qBAAqB,KAAK,cAAc,KAAK,eAAe;EAClE,MAAM,uBAAuB,KAAK,KAAK,OAAO,UAAU,KAAK,eAAe;AAE5E,MAAI;AACF,SAAM,GAAG,sBAAsB,oBAAoB,EAAE,WAAW,MAAM,CAAC;AAEvE,UAAO,MAAM,GADI,KAAK,eAAe,aAAa,CACzB;YAEnB;AACN,SAAM,UAAU,aAAa;;;;;;CAOjC,eAAuB,UAAuC;EAC5D,MAAM,WAAW,KAAK,UAAU,KAAK,eAAe;AACpD,SAAO;GACL;GACA;GACA,OAAO,IAAI,aAAa,SAAS;GACjC,SAAS,IAAI,eAAe,UAAU,SAAS;GAC/C,OAAO,IAAI,aAAa,UAAU,UAAU,KAAK,OAAO;GACxD,WAAW,IAAI,UAAU,SAAS;GACnC;;;;;AC7FL,SAAgB,mBAAmB,QAAmB,QAAiC;AAErF,QAAO,aACL,2BACA,EACE,aAAa,8EACd,EACD,YAAY;AACV,MAAI;GACF,MAAM,SAAS,MAAM,OAAO,MAAM,MAAM;AACxC,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;IAAE,CAAC,EAC5E;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,8BACA;EACE,aAAa;EACb,aAAa,EACX,SAAS,EAAE,QAAQ,CAAC,SAAS,2BAA2B,EACzD;EACF,EACD,OAAO,EAAE,cAAc;AACrB,MAAI;GACF,MAAM,OAAO,MAAM,OAAO,MAAM,SAAS,QAAQ;AACjD,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU,MAAM,MAAM,EAAE;IAAE,CAAC,EAC1E;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,4BACA;EACE,aAAa;EACb,aAAa;GACX,MAAM,EAAE,QAAQ,CAAC,SAAS,4BAA4B;GACtD,SAAS,EAAE,QAAQ,CAAC,MAAM,eAAe,CAAC,SAAS,gCAAgC;GACnF,MAAM,EAAE,KAAK;IAAC;IAAQ;IAAM;IAAM,CAAC,CAAC,SAAS,eAAe;GAC5D,cAAc,EAAE,SAAS,CAAC,SAAS,gDAAgD;GACnF,aAAa,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,oBAAoB;GAChE,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,6BAA6B;GAClE,QAAQ,EAAE,MAAM,EAAE,OAAO;IACvB,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,QAAQ;IACnB,aAAa,EAAE,QAAQ;IACvB,WAAW,EAAE,QAAQ;IACrB,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,UAAU;IACzC,aAAa,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,UAAU;IAC9C,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,0DAA0D;GACnF;EACF,EACD,OAAO,WAAW;AAChB,MAAI;GACF,MAAM,SAAS,MAAM,OAAO,YAAY,OAAO,OAAO;AACpD,WAAO,GAAG,MAAM,OAAO;KACrB,MAAM,OAAO;KACb,SAAS,OAAO;KAChB,MAAM,OAAO;KACb,cAAc,OAAO;KACrB,aAAa,OAAO;KACpB,MAAM,OAAO;KACb,QAAQ,OAAO;KAChB,CAAC;MACD,EAAE,SAAS,iBAAiB,OAAO,WAAW,CAAC;AAElD,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,SAAS;KAAM,OAAO;KAAQ,EAAE,MAAM,EAAE;IAAE,CAAC,EACtG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,yBACA;EACE,aAAa;EACb,aAAa;GACX,SAAS,EAAE,QAAQ,CAAC,SAAS,kBAAkB;GAC/C,MAAM,EAAE,QAAQ,CAAC,SAAS,4BAA4B;GACtD,SAAS,EAAE,QAAQ,CAAC,SAAS,0BAA0B;GACvD,aAAa,EAAE,QAAQ,CAAC,SAAS,oEAAoE;GACrG,WAAW,EAAE,QAAQ,CAAC,SAAS,oEAAoE;GACnG,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,UAAU,CAAC,SAAS,8DAA8D;GACjH,aAAa,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,UAAU,CAAC,SAAS,mDAAmD;GAC3G;EACF,EACD,OAAO,WAAW;AAChB,MAAI;GACF,MAAM,SAAS,MAAM,OAAO,YAAY,OAAO,OAAO;AACpD,WAAO,GAAG,MAAM,SAAS,OAAO,SAAS;KACvC,MAAM,OAAO;KACb,SAAS,OAAO;KAChB,aAAa,OAAO;KACpB,WAAW,OAAO;KAClB,SAAU,OAAO,WAAW,EAAE;KAC9B,aAAc,OAAO,eAAe,EAAE;KACvC,CAAC;MACD,EAAE,SAAS,aAAa,OAAO,QAAQ,MAAM,OAAO,WAAW,CAAC;AAEnE,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,SAAS;KAAM,OAAO;KAAQ,EAAE,MAAM,EAAE;IAAE,CAAC,EACtG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,4BACA;EACE,aAAa;EACb,aAAa,EAAE,iBAAiB,MAAM;EACtC,aAAa,EACX,SAAS,EAAE,QAAQ,CAAC,SAAS,qBAAqB,EACnD;EACF,EACD,OAAO,EAAE,cAAc;AACrB,MAAI;AACF,SAAM,OAAO,YAAY,OAAO,OAAO;AACrC,UAAM,GAAG,MAAM,OAAO,QAAQ;MAC7B,EAAE,SAAS,iBAAiB,WAAW,CAAC;AAE3C,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,SAAS;KAAM,SAAS;KAAS,CAAC;IAAE,CAAC,EAChG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;;;;ACtJH,SAAgB,qBAAqB,QAAmB,QAAiC;AAEvF,QAAO,aACL,4BACA;EACE,aAAa;EACb,aAAa,EAAE,cAAc,MAAM;EACnC,aAAa;GACX,SAAS,EAAE,QAAQ,CAAC,SAAS,gCAAgC;GAC7D,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,0DAAsD;GAC9F;EACF,EACD,OAAO,EAAE,SAAS,aAAa;AAC7B,MAAI;GACF,MAAM,UAAU,MAAM,OAAO,QAAQ,KAAK,SAAS,OAAO;AAC1D,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,OAAO,QAAQ;KAAQ;KAAS,EAAE,MAAM,EAAE;IAAE,CAAC,EACxG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,2BACA;EACE,aAAa;EACb,aAAa,EAAE,cAAc,MAAM;EACnC,aAAa;GACX,SAAS,EAAE,QAAQ,CAAC,SAAS,WAAW;GACxC,SAAS,EAAE,QAAQ,CAAC,SAAS,yBAAyB;GACtD,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,mCAAmC;GAC3E;EACF,EACD,OAAO,EAAE,SAAS,SAAS,aAAa;AACtC,MAAI;GACF,MAAM,QAAQ,MAAM,OAAO,QAAQ,IAAI,SAAS,SAAS,OAAO;AAChE,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU,OAAO,MAAM,EAAE;IAAE,CAAC,EAC3E;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,8BACA;EACE,aAAa;EACb,aAAa;GACX,SAAS,EAAE,QAAQ,CAAC,SAAS,kBAAkB;GAC/C,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,SAAS,8DAA8D;GACnG,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,6DAA2D;GAClG,QAAQ,EAAE,KAAK,CAAC,SAAS,UAAU,CAAC,CAAC,UAAU,CAAC,SAAS,oCAAkC;GAC3F,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,iDAAiD;GAC1F;EACF,EACD,OAAO,WAAW;AAChB,MAAI;GACF,MAAM,SAAS,MAAM,OAAO,YAAY,OAAO,OAAO;IACpD,MAAM,aAAa,MAAM,GAAG,UAAU,SAAS,OAAO,SAAS,OAAO,KAAgC;AACtG,QAAI,CAAC,WAAW,MACd,OAAM,IAAI,MAAM,uBAAuB,WAAW,OAAO,KAAI,MAAK,OAAO,EAAE,QAAQ,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,GAAG;AAGnH,WAAO,GAAG,QAAQ,OAAO,OAAO,SAAS,OAAO,MAAiC;KAC/E,QAAQ,OAAO;KACf,QAAQ,OAAO;KACf,SAAS,OAAO;KACjB,CAAC;MACD,EAAE,SAAS,UAAU,OAAO,QAAQ,SAAS,CAAC;AAEjD,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,SAAS;KAAM,IAAI;KAAQ,EAAE,MAAM,EAAE;IAAE,CAAC,EACnG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,8BACA;EACE,aAAa;EACb,aAAa;GACX,SAAS,EAAE,QAAQ,CAAC,SAAS,WAAW;GACxC,SAAS,EAAE,QAAQ,CAAC,SAAS,mCAAmC;GAChE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,SAAS,mBAAmB;GACxD,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,mCAAmC;GAC1E,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,iDAAiD;GAC1F;EACF,EACD,OAAO,WAAW;AAChB,MAAI;GACF,MAAM,SAAS,MAAM,OAAO,YAAY,OAAO,OAAO;IACpD,MAAM,aAAa,MAAM,GAAG,UAAU,SACpC,OAAO,SACP,OAAO,MACP,EAAE,UAAU,MAAM,CACnB;AACD,QAAI,CAAC,WAAW,MACd,OAAM,IAAI,MAAM,uBAAuB,WAAW,OAAO,KAAI,MAAK,OAAO,EAAE,QAAQ,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,GAAG;AAGnH,WAAO,GAAG,QAAQ,OAAO,OAAO,SAAS,OAAO,SAAS,OAAO,MAAiC;KAC/F,QAAQ,OAAO;KACf,SAAS,OAAO;KACjB,CAAC;MACD,EAAE,SAAS,UAAU,OAAO,QAAQ,GAAG,OAAO,WAAW,CAAC;AAE7D,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,SAAS;KAAM,OAAO;KAAQ,EAAE,MAAM,EAAE;IAAE,CAAC,EACtG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,8BACA;EACE,aAAa;EACb,aAAa,EAAE,iBAAiB,MAAM;EACtC,aAAa;GACX,SAAS,EAAE,QAAQ,CAAC,SAAS,WAAW;GACxC,SAAS,EAAE,QAAQ,CAAC,SAAS,mCAAmC;GACjE;EACF,EACD,OAAO,EAAE,SAAS,cAAc;AAC9B,MAAI;AACF,SAAM,OAAO,YAAY,OAAO,OAAO;AACrC,UAAM,GAAG,QAAQ,OAAO,SAAS,QAAQ;MACxC,EAAE,SAAS,UAAU,QAAQ,GAAG,WAAW,CAAC;AAE/C,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,SAAS;KAAM,SAAS;KAAS,CAAC;IAAE,CAAC,EAChG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,wBACA;EACE,aAAa;EACb,aAAa,EAAE,cAAc,MAAM;EACnC,aAAa;GACX,SAAS,EAAE,QAAQ,CAAC,SAAS,+BAA+B;GAC5D,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,SAAS,2BAA2B;GACjE;EACF,EACD,OAAO,EAAE,SAAS,WAAW;AAC3B,MAAI;GACF,MAAM,SAAS,MAAM,OAAO,UAAU,SAAS,SAAS,KAAgC;AACxF,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;IAAE,CAAC,EAC5E;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;;;;AClLH,SAAgB,mBAAmB,QAAmB,QAAiC;AAErF,QAAO,aACL,2BACA;EACE,aAAa;EACb,aAAa,EAAE,cAAc,MAAM;EACpC,EACD,YAAY;AACV,MAAI;GACF,MAAM,SAAS,MAAM,OAAO,MAAM,MAAM;AACxC,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,OAAO,OAAO;KAAQ;KAAQ,EAAE,MAAM,EAAE;IAAE,CAAC,EACtG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,8BACA;EACE,aAAa;EACb,aAAa;GACX,MAAM,EAAE,QAAQ,CAAC,SAAS,0EAAwE;GAClG,UAAU,EAAE,QAAQ,CAAC,SAAS,uEAAiE;GAC/F,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,yBAAyB;GAC9D;EACF,EACD,OAAO,WAAW;AAChB,MAAI;GACF,MAAM,SAAS,MAAM,OAAO,YAAY,OAAO,OAAO;AACpD,WAAO,GAAG,MAAM,SAAS;KACvB,MAAM,OAAO;KACb,UAAU,OAAO;KACjB,KAAK,OAAO;KACb,CAAC;MACD,EAAE,SAAS,mBAAmB,OAAO,QAAQ,CAAC;AAEjD,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,SAAS;KAAM,OAAO;KAAQ,EAAE,MAAM,EAAE;IAAE,CAAC,EACtG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;AAGD,QAAO,aACL,gCACA;EACE,aAAa;EACb,aAAa,EAAE,iBAAiB,MAAM;EACtC,aAAa,EACX,MAAM,EAAE,QAAQ,CAAC,SAAS,2BAA2B,EACtD;EACF,EACD,OAAO,EAAE,WAAW;AAClB,MAAI;AACF,SAAM,OAAO,YAAY,OAAO,OAAO;AACrC,UAAM,GAAG,MAAM,WAAW,KAAK;MAC9B,EAAE,SAAS,qBAAqB,QAAQ,CAAC;AAE5C,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,KAAK,UAAU;KAAE,SAAS;KAAM,cAAc;KAAM,CAAC;IAAE,CAAC,EAClG;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;;;;ACfH,IAAa,SAAb,MAAoB;CAClB,SAAgC,EAAE;CAClC,QAAgB;CAChB,eAAuB;CAGvB,0BAAkB,IAAI,KAAqB;CAG3C,eAAsC,EAAE;CAGxC,gCAAwB,IAAI,KAAgC;CAE5D,YACE,UACA,UACA;AAFQ,OAAA,WAAA;AACA,OAAA,WAAA;;CAGV,MAAM,SAAS,UAAyB,EAAE,EAAyB;AACjE,OAAK,SAAS,EAAE;AAChB,OAAK,QAAQ;AACb,OAAK,eAAe;AACpB,OAAK,0BAAU,IAAI,KAAK;AACxB,OAAK,eAAe,EAAE;AACtB,OAAK,gCAAgB,IAAI,KAAK;EAE9B,MAAM,MAAM,QAAQ,OAAO;EAG3B,MAAM,SAAS,MAAM,KAAK,eAAe;AACzC,MAAI,CAAC,OACH,QAAO,KAAK,YAAY,EAAE;EAI5B,MAAM,eAAe,QAAQ,UACzB,OAAO,QAAO,MAAK,EAAE,YAAY,QAAQ,QAAQ,GACjD;AAEJ,MAAI,QAAQ,WAAW,aAAa,WAAW,GAAG;AAChD,QAAK,SAAS,SAAS,aAAa,UAAU,QAAQ,QAAQ,8BAA8B;AAC5F,UAAO,KAAK,YAAY,OAAO,OAAO;;AAIxC,QAAM,KAAK,gBAAgB,QAAQ,aAAa;EAGhD,MAAM,SAAS,MAAM,KAAK,YAAY;AAGtC,QAAM,KAAK,oBAAoB,MAAM,SAAS,aAAa;AAG3D,OAAK,MAAM,SAAS,OAClB,KAAI;GACF,MAAM,SAAS,MAAM,aAAgC,KAAK,KAAK,UAAU,UAAU,GAAG,MAAM,QAAQ,OAAO,CAAC;AAC5G,QAAK,cAAc,IAAI,MAAM,SAAS,OAAO;UAEzC;AAMR,OAAK,MAAM,SAAS,cAAc;AAChC,QAAK,oBAAoB,MAAM;AAC/B,QAAK,kBAAkB,OAAO,QAAQ,IAAI;AAE1C,OAAI,MAAM,SAAS,QAAQ,MAAM,SAAS,OAAO;IAC/C,MAAM,aAAa,KAAK,aAAa,QAAO,MAAK,EAAE,YAAY,MAAM,QAAQ;AAC7E,UAAM,KAAK,kBAAkB,OAAO,WAAW;;;AAKnD,MAAI,CAAC,QAAQ,QACX,OAAM,KAAK,uBAAuB,OAAO;AAI3C,MAAI,OAAO,KAAK,QAAQ,OAAO,EAC7B,MAAK,oBAAoB;AAI3B,MAAI,IACF,OAAM,KAAK,iBAAiB;AAG9B,SAAO,KAAK,YAAY,OAAO,OAAO;;CAKxC,MAAc,oBAAoB,QAAwC;AACxE,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,aAAa,KAAK,KAAK,UAAU,MAAM,QAAQ;GACrD,MAAM,YAAY,MAAM,cAAc,WAAW;AAEjD,QAAK,MAAM,YAAY,WAAW;IAChC,MAAM,WAAW,KAAK,YAAY,GAAG,SAAS,OAAO;IACrD,IAAI;AACJ,QAAI;KACF,MAAM,MAAM,MAAM,SAAS,UAAU,QAAQ;AAC7C,eAAU,KAAK,MAAM,IAAI;AACzB,SAAI,CAAC,MAAM,QAAQ,QAAQ,EAAE;AAC3B,WAAK,SAAS,SAAS,aAAa,gCAAgC,MAAM,SAAS,KAAA,GAAW,KAAA,GAAW,GAAG,MAAM,QAAQ,GAAG,SAAS,OAAO;AAC7I;;YAGE;AACJ,UAAK,SAAS,SAAS,aAAa,gCAAgC,MAAM,SAAS,KAAA,GAAW,KAAA,GAAW,GAAG,MAAM,QAAQ,GAAG,SAAS,OAAO;AAC7I;;AAGF,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,aAAa,KAAK;KACrB,SAAS,MAAM;KACf;KACA;KACA;KACA,OAAO;KACR,CAAC;;;;CAOR,MAAc,gBAAiD;EAC7D,MAAM,eAAe,KAAK,KAAK,UAAU,UAAU,gBAAgB;AACnE,MAAI;GACF,MAAM,MAAM,MAAM,SAAS,cAAc,QAAQ;AACjD,OAAI;IACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAI,CAAC,MAAM,QAAQ,OAAO,EAAE;AAC1B,UAAK,SAAS,SAAS,aAAa,iCAAiC,KAAA,GAAW,KAAA,GAAW,KAAA,GAAW,uBAAuB;AAC7H,YAAO;;IAIT,MAAM,uBAAO,IAAI,KAAqB;AACtC,SAAK,MAAM,KAAK,QAAQ;KACtB,MAAM,SAAS,KAAK,IAAI,EAAE,QAAQ,IAAI,KAAK;AAC3C,UAAK,IAAI,EAAE,SAAS,MAAM;;AAE5B,SAAK,MAAM,CAAC,SAAS,UAAU,KAC7B,KAAI,QAAQ,EACV,MAAK,SAAS,SAAS,aAAa,sBAAsB,QAAQ,sBAAsB,MAAM,UAAU,SAAS,KAAA,GAAW,KAAA,GAAW,uBAAuB;AAIlK,WAAO;WAEH;AACJ,SAAK,SAAS,SAAS,aAAa,uCAAuC,KAAA,GAAW,KAAA,GAAW,KAAA,GAAW,uBAAuB;AACnI,WAAO;;UAGL;AACJ,QAAK,SAAS,SAAS,aAAa,4DAA4D,KAAA,GAAW,KAAA,GAAW,KAAA,GAAW,uBAAuB;AACxJ,UAAO;;;CAIX,MAAc,gBAAgB,WAA4B,cAA8C;AACtG,OAAK,MAAM,SAAS,aAElB,KAAI,CAAC,MAAM,WADO,KAAK,KAAK,UAAU,UAAU,GAAG,MAAM,QAAQ,OAAO,CACxC,CAC9B,MAAK,SAAS,SAAS,aAAa,4CAA4C,MAAM,QAAQ,IAAI,MAAM,SAAS,KAAA,GAAW,KAAA,GAAW,UAAU,MAAM,QAAQ,OAAO;EAK1K,MAAM,UAAU,IAAI,IAAI,UAAU,KAAI,MAAK,EAAE,QAAQ,CAAC;EACtD,MAAM,aAAa,MAAM,cAAc,KAAK,KAAK,UAAU,SAAS,CAAC;AACrE,OAAK,MAAM,QAAQ,YAAY;AAC7B,OAAI,SAAS,WAAY;AACzB,OAAI,CAAC,QAAQ,IAAI,KAAK,CACpB,MAAK,SAAS,WAAW,aAAa,0BAA0B,KAAK,gDAAgD,MAAM,KAAA,GAAW,KAAA,GAAW,UAAU,KAAK,OAAO;;;CAK7K,MAAc,uBAAuB,QAAwC;EAC3E,MAAM,WAAW,IAAI,IAAI,OAAO,KAAI,MAAK,EAAE,QAAQ,CAAC;EACpD,MAAM,EAAE,SAAS,SAAS,MAAM,OAAO;AAEvC,MAAI;GACF,MAAM,UAAU,MAAM,QAAQ,KAAK,SAAS;AAC5C,QAAK,MAAM,SAAS,SAAS;AAC3B,QAAI,UAAU,YAAY,UAAU,iBAAiB,UAAU,YAAY,MAAM,WAAW,IAAI,CAAE;AAGlG,SADU,MAAM,KADC,KAAK,KAAK,UAAU,MAAM,CACb,EACxB,aAAa,IAAI,CAAC,SAAS,IAAI,MAAM,CACzC,MAAK,SAAS,WAAW,aAAa,sBAAsB,MAAM,4CAA4C,MAAM;;UAIpH;;CAOR,oBAA4B,OAA4B;EACtD,MAAM,SAAS,KAAK,cAAc,IAAI,MAAM,QAAQ;AACpD,MAAI,CAAC,OAAQ;AAIb,OAAK,MAAM,SADe;GAAC;GAAa;GAAa;GAAM;GAAS,CAElE,KAAI,CAAC,OAAO,MAAK,MAAK,EAAE,YAAY,SAAS,EAAE,OAAO,CACpD,MAAK,SAAS,SAAS,UAAU,yBAAyB,MAAM,yBAAyB,MAAM,SAAS,KAAA,GAAW,KAAA,GAAW,UAAU,MAAM,QAAQ,OAAO;AAIjK,MAAI,MAAM,SAAS,QAAQ,MAAM,SAAS,OAAO;AAC/C,OAAI,CAAC,OAAO,MAAK,MAAK,EAAE,YAAY,OAAO,CACzC,MAAK,SAAS,SAAS,UAAU,gDAA8C,MAAM,SAAS,KAAA,GAAW,KAAA,GAAW,UAAU,MAAM,QAAQ,OAAO;AAErJ,OAAI,CAAC,OAAO,MAAK,MAAK,EAAE,YAAY,UAAU,CAC5C,MAAK,SAAS,WAAW,UAAU,qDAAmD,MAAM,SAAS,KAAA,GAAW,KAAA,GAAW,UAAU,MAAM,QAAQ,OAAO;;;CAKhK,kBAA0B,OAAsB,QAAsB,KAAoB;EACxF,MAAM,SAAS,KAAK,cAAc,IAAI,MAAM,QAAQ;AACpD,MAAI,CAAC,OAAQ;EAEb,MAAM,aAAa,KAAK,aAAa,QAAO,MAAK,EAAE,YAAY,MAAM,QAAQ;AAE7E,MAAI,WAAW,WAAW,KAAK,CAAC,MAAM,cAAc;AAClD,QAAK,SAAS,WAAW,aAAa,qCAAqC,MAAM,QAAQ,IAAI,MAAM,QAAQ;AAC3G;;AAGF,OAAK,MAAM,MAAM,YAAY;GAE3B,MAAM,4BAAY,IAAI,KAAqB;AAC3C,QAAK,MAAM,SAAS,GAAG,SAAS;IAC9B,MAAM,SAAS,UAAU,IAAI,MAAM,GAAG,IAAI,KAAK;AAC/C,cAAU,IAAI,MAAM,IAAI,MAAM;;AAGhC,QAAK,MAAM,CAAC,IAAI,UAAU,UACxB,KAAI,QAAQ,GAAG;AACb,SAAK,SAAS,SAAS,MAAM,iBAAiB,GAAG,YAAY,MAAM,sBAAsB,MAAM,SAAS,IAAI,KAAA,GAAW,GAAG,MAAM,QAAQ,GAAG,GAAG,SAAS,QACrJ,MAAM,uDAAuD,KAAA,EAAU;AAEzE,QAAI,IACF,MAAK,gBAAgB,IAAI,GAAG;;AAMlC,QAAK,MAAM,SAAS,GAAG,QACrB,MAAK,WAAW,OAAO,QAAQ,OAAO,QAAQ,GAAG,MAAM,QAAQ,GAAG,GAAG,SAAS,QAAQ,KAAK,GAAG;AAIhG,QAAK,uBAAuB,OAAO,QAAQ,GAAG,SAAS,GAAG,MAAM,QAAQ,GAAG,GAAG,SAAS,OAAO;;AAIhG,MAAI,MAAM,gBAAgB,WAAW,SAAS,EAC5C,MAAK,yBAAyB,OAAO,WAAW;;CAMpD,WACE,OACA,QACA,OACA,QACA,MACA,KACA,IACM;EACN,MAAM,UAAU,MAAM;AAGtB,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,QAAK,SAAS,SAAS,MAAM,0BAA0B,MAAM,SAAS,KAAA,GAAW,MAAM,KAAK;AAC5F;;AAEF,MAAI,CAAC,UAAU,QAAQ,EAAE;GACvB,MAAM,QAAQ,MAAM,YAAY,GAAG,KAAA;AACnC,QAAK,SAAS,SAAS,MAAM,sBAAsB,QAAQ,oCAAoC,MAAM,SAAS,SAAS,MAAM,MAC3H,MAAM,mBAAmB,MAAM,gCAAgC,KAAA,EAAU;AAC3E,OAAI,OAAO,OAAO;AAChB,SAAK,QAAQ,IAAI,SAAS,MAAM;AAChC,UAAM,KAAK;AACX,OAAG,QAAQ;AACX,SAAK;;;AAKT,MAAI,CAAC,MAAM,WAAW;AACpB,QAAK,SAAS,SAAS,UAAU,qBAAqB,MAAM,SAAS,SAAS,aAAa,MACzF,MAAM,kCAAkC,KAAA,EAAU;AACpD,OAAI,KAAK;AACP,UAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,OAAG,QAAQ;AACX,SAAK;;;AAGT,MAAI,CAAC,MAAM,WAAW;AACpB,QAAK,SAAS,SAAS,UAAU,qBAAqB,MAAM,SAAS,SAAS,aAAa,MACzF,MAAM,kCAAkC,KAAA,EAAU;AACpD,OAAI,KAAK;AACP,UAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,OAAG,QAAQ;AACX,SAAK;;;AAGT,MAAI,CAAC,MAAM,QAAQ;AACjB,QAAK,SAAS,SAAS,UAAU,kBAAkB,MAAM,SAAS,SAAS,UAAU,MACnF,MAAM,0BAAwB,KAAA,EAAU;AAC1C,OAAI,KAAK;AACP,UAAM,SAAS;AACf,OAAG,QAAQ;AACX,SAAK;;aAGA,CAAC;GAAC;GAAS;GAAW;GAAU,CAAC,SAAS,MAAM,OAAO,EAAE;AAChE,QAAK,SAAS,SAAS,UAAU,mBAAmB,MAAM,OAAO,yCAAyC,MAAM,SAAS,SAAS,UAAU,MAC1I,MAAM,0BAAwB,KAAA,EAAU;AAC1C,OAAI,KAAK;AACP,UAAM,SAAS;AACf,OAAG,QAAQ;AACX,SAAK;;;AAKT,MAAI,MAAM,aAAa,MAAM,KAAK,MAAM,MAAM,UAAU,CAAC,CACvD,MAAK,SAAS,SAAS,UAAU,kCAAkC,MAAM,UAAU,IAAI,MAAM,SAAS,SAAS,aAAa,KAAK;AAEnI,MAAI,MAAM,aAAa,MAAM,KAAK,MAAM,MAAM,UAAU,CAAC,CACvD,MAAK,SAAS,SAAS,UAAU,kCAAkC,MAAM,UAAU,IAAI,MAAM,SAAS,SAAS,aAAa,KAAK;EAInI,MAAM,kBAAkB,OAAO,QAAO,MAAK,CAAC,EAAE,OAAO;AACrD,OAAK,MAAM,SAAS,iBAAiB;GACnC,MAAM,QAAQ,MAAM,MAAM;AAG1B,OAAI,MAAM,cAAc,mBAAmB;QACrC,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI;AACzD,SAAI,MAAM,YAAY,cAAc,MAAM,SAAS,QAAQ,MAAM,SAAS,OAAQ;AAClF,UAAK,SAAS,SAAS,UAAU,mBAAmB,MAAM,QAAQ,aAAa,MAAM,SAAS,SAAS,MAAM,SAAS,KAAK;AAC3H;;;AAIJ,OAAI,UAAU,KAAA,KAAa,UAAU,KAAM;GAI3C,MAAM,YAAY,mBAAmB,OADhB,gBAAgB,MAAM,aAAa,MAAM,UAAU,EACd,MAAM,QAAQ;AACxE,OAAI,WAAW;AACb,SAAK,SAAS,SAAS,UAAU,kBAAkB,aAAa,MAAM,SAAS,SAAS,MAAM,SAAS,KAAK;AAC5G;;AAIF,OAAI,MAAM,gBAAgB,WAAW;IACnC,MAAM,WAAW,gBAAgB,OAAO,MAAM,QAAQ;AACtD,QAAI,SACF,MAAK,SAAS,SAAS,UAAU,UAAU,MAAM,SAAS,SAAS,MAAM,SAAS,KAAK;;AAK3F,OAAI,MAAM,gBAAgB,gBAAgB,OAAO,UAAU,SACzD,MAAK,cAAc,MAAM,SAAS,SAAS,OAAO,OAAO,KAAK;AAEhE,OAAI,MAAM,gBAAgB,iBAAiB,MAAM,QAAQ,MAAM,CAC7D,MAAK,MAAM,SAAS,MAClB,KAAI,OAAO,UAAU,SACnB,MAAK,cAAc,MAAM,SAAS,SAAS,OAAO,OAAO,KAAK;OAG9D,MAAK,SAAS,SAAS,YAAY,+CAA+C,MAAM,SAAS,SAAS,MAAM,SAAS,KAAK;AAMpI,QAAK,MAAM,gBAAgB,gBAAgB,MAAM,gBAAgB,kBAAkB,CAAC,MAAM,SAAS,WAAW,MAAM,WAAW,MAC7H,MAAK,SAAS,WAAW,YAAY,mBAAmB,MAAM,QAAQ,mDAAmD,MAAM,SAAS,KAAA,GAAW,MAAM,SAAS,UAAU,MAAM,QAAQ,OAAO;AAInM,OAAI,MAAM,gBAAgB,WAAW,OAAO,UAAU,YAAY,UAAU;QACtE,CAAC,OAAO,MAAK,MAAK,EAAE,SAAS,MAAM,CACrC,MAAK,SAAS,SAAS,SAAS,eAAe,MAAM,6BAA6B,MAAM,SAAS,SAAS,MAAM,SAAS,KAAK;;;;CAMtI,cACE,SACA,SACA,OACA,OACA,MACM;EACN,MAAM,aAAa,MAAM,SAAS,WAAW,MAAM,WAAW;AAC9D,MAAI,CAAC,WAAY;AAGjB,MAAI,CAAC,UAAU,MAAM,EAAE;AACrB,QAAK,SAAS,SAAS,YAAY,mCAAmC,MAAM,IAAI,SAAS,SAAS,MAAM,SAAS,KAAK;AACtH;;AAOF,MAAI,CAHa,KAAK,aAAa,QAAO,MAAK,EAAE,YAAY,WAAW,CACjD,MAAK,OAAM,GAAG,QAAQ,MAAK,MAAK,EAAE,OAAO,MAAM,CAAC,CAGrE,MAAK,SAAS,SAAS,YAAY,qBAAqB,MAAM,wBAAwB,WAAW,IAAI,SAAS,SAAS,MAAM,SAAS,KAAK;;CAM/I,uBACE,OACA,QACA,SACA,MACM;EACN,MAAM,eAAe,OAAO,QAAO,MAAK,EAAE,cAAc,iBAAiB,SAAS,CAAC,EAAE,OAAO;AAC5F,OAAK,MAAM,SAAS,cAAc;GAChC,MAAM,uBAAO,IAAI,KAAsB;AACvC,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,QAAQ,MAAM,MAAM;AAC1B,QAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,GAAI;IAC3D,MAAM,MAAM,OAAO,UAAU,WAAW,KAAK,UAAU,MAAM,GAAG;IAChE,MAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,QAAI,SACF,MAAK,SAAS,SAAS,UAAU,gCAAgC,MAAM,QAAQ,WAAW,MAAM,+BAA+B,SAAS,OAAO,MAAM,MAAM,MAAM,SAAS,MAAM,IAAI,MAAM,SAAS,KAAK;QAGxM,MAAK,IAAI,KAAK,MAAM,GAAG;;;;CAM/B,yBACE,OACA,YACM;AACN,MAAI,WAAW,SAAS,EAAG;EAE3B,MAAM,gBAAgB,WAAW;EACjC,MAAM,eAAe,IAAI,IAAI,cAAc,QAAQ,KAAI,MAAK,EAAE,GAAG,CAAC;AAElE,OAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;GAC1C,MAAM,YAAY,WAAW;GAC7B,MAAM,WAAW,IAAI,IAAI,UAAU,QAAQ,KAAI,MAAK,EAAE,GAAG,CAAC;AAE1D,QAAK,MAAM,MAAM,aACf,KAAI,CAAC,SAAS,IAAI,GAAG,CACnB,MAAK,SAAS,SAAS,MAAM,UAAU,GAAG,cAAc,cAAc,SAAS,uBAAuB,UAAU,SAAS,QAAQ,MAAM,SAAS,IAAI,KAAA,GAAW,GAAG,MAAM,QAAQ,GAAG,UAAU,SAAS,OAAO;AAIjN,QAAK,MAAM,MAAM,SACf,KAAI,CAAC,aAAa,IAAI,GAAG,CACvB,MAAK,SAAS,SAAS,MAAM,UAAU,GAAG,cAAc,UAAU,SAAS,uBAAuB,cAAc,SAAS,QAAQ,MAAM,SAAS,IAAI,KAAA,GAAW,GAAG,MAAM,QAAQ,GAAG,cAAc,SAAS,OAAO;;;CAQzN,MAAc,kBAAkB,OAAsB,YAA0C;AAC9F,OAAK,MAAM,MAAM,YAAY;GAE3B,MAAM,SADc,MAAM,gBAAgB,GAAG,aAAa,MAAM,UACnC,GAAG,WAAW,KAAA;AAE3C,QAAK,MAAM,SAAS,GAAG,SAAS;AAC9B,QAAI,EAAE,UAAU,UAAU,CAAC,MAAM,MAAM;AACrC,UAAK,SAAS,SAAS,QAAQ,uCAAqC,MAAM,SAAS,MAAM,IAAI,QAAQ,GAAG,MAAM,QAAQ,GAAG,GAAG,SAAS,OAAO;AAC5I;;IAGF,MAAM,UAAU;IAChB,MAAM,MAAM,MAAM,SAAS,QAAQ,SAAS;IAC5C,MAAM,aAAa,QAAQ,QAAQ,MAAM,QAAQ;IACjD,MAAM,SAAS,SACX,KAAK,KAAK,UAAU,YAAY,QAAQ,GAAG,QAAQ,OAAO,MAAM,GAChE,KAAK,KAAK,UAAU,YAAY,GAAG,QAAQ,OAAO,MAAM;AAE5D,QAAI,CAAC,MAAM,WAAW,OAAO,CAC3B,MAAK,SAAS,SAAS,QAAQ,oCAAoC,QAAQ,KAAK,IAAI,MAAM,SAAS,MAAM,IAAI,KAAA,GAAW,OAAO,QAAQ,KAAK,WAAW,KAAK,GAAG,CAAC;AAIlK,QAAI,aAAa,SAAS,MAAM,WAAW,OAAO,MAAM,YAAY,YAAa,MAAM,QAAmB,SAAS,EACjH,MAAK,SAAS,WAAW,QAAQ,yFAAuF,MAAM,SAAS,MAAM,IAAI,WAAW,GAAG,MAAM,QAAQ,GAAG,GAAG,SAAS,OAAO;;;;CAQ3M,MAAc,aAAoC;AAChD,MAAI;AACF,UAAO,MAAM,aAA2B,KAAK,KAAK,UAAU,cAAc,CAAC;UAEvE;AACJ,QAAK,SAAS,WAAW,aAAa,mCAAmC;AACzE,UAAO,EAAE;;;;;;;;;;CAab,qBAAmC;AACjC,MAAI,KAAK,QAAQ,SAAS,EAAG;AAE7B,OAAK,MAAM,MAAM,KAAK,cAAc;GAClC,MAAM,SAAS,KAAK,cAAc,IAAI,GAAG,QAAQ;AACjD,OAAI,CAAC,OAAQ;GAGb,MAAM,iBAAiB,OAAO,QAAO,MACnC,EAAE,gBAAgB,gBAAgB,EAAE,gBAAgB,cACrD;AACD,OAAI,eAAe,WAAW,EAAG;AAEjC,QAAK,MAAM,SAAS,GAAG,QACrB,MAAK,MAAM,SAAS,gBAAgB;IAClC,MAAM,QAAQ,MAAM,MAAM;AAC1B,QAAI,UAAU,KAAA,KAAa,UAAU,KAAM;AAE3C,QAAI,MAAM,gBAAgB,gBAAgB,OAAO,UAAU,UAAU;KACnE,MAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM;AACrC,SAAI,OAAO;AACT,WAAK,SAAS,WAAW,YACvB,qBAAqB,MAAM,QAAQ,MAAM,MAAM,OAAO,MAAM,IAC5D,GAAG,SAAS,MAAM,IAAI,MAAM,SAAS,GAAG,GAAG,QAAQ,GAAG,GAAG,SAAS,QAClE,mCAAmC;AACrC,YAAM,MAAM,WAAW;AACvB,SAAG,QAAQ;AACX,WAAK;;;AAIT,QAAI,MAAM,gBAAgB,iBAAiB,MAAM,QAAQ,MAAM,EAAE;KAC/D,IAAI,UAAU;KACd,MAAM,UAAW,MAAmB,KAAI,UAAS;MAC/C,MAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM;AACrC,UAAI,OAAO;AACT,iBAAU;AACV,cAAO;;AAET,aAAO;OACP;AACF,SAAI,SAAS;AACX,WAAK,SAAS,WAAW,YACvB,iCAAiC,MAAM,QAAQ,gBAAgB,KAAK,QAAQ,KAAK,gBACjF,GAAG,SAAS,MAAM,IAAI,MAAM,SAAS,GAAG,GAAG,QAAQ,GAAG,GAAG,SAAS,QAClE,0CAA0C;AAC5C,YAAM,MAAM,WAAW;AACvB,SAAG,QAAQ;AACX,WAAK;;;;;;CAUjB,gBAAwB,IAAiB,aAA2B;EAClE,IAAI,QAAQ;AACZ,OAAK,MAAM,SAAS,GAAG,QACrB,KAAI,MAAM,OAAO,aAAa;AAC5B,OAAI,OAAO;AACT,YAAQ;AACR;;GAEF,MAAM,QAAQ,YAAY;AAC1B,QAAK,QAAQ,IAAI,GAAG,YAAY,OAAO,SAAS,MAAM;AACtD,SAAM,KAAK;AACX,MAAG,QAAQ;AACX,QAAK;;;CAOX,MAAc,kBAAiC;AAC7C,OAAK,MAAM,MAAM,KAAK,aACpB,KAAI,GAAG,MACL,OAAM,cAAc,GAAG,UAAU,GAAG,QAAQ;;CAOlD,SACE,UACA,UACA,SACA,SACA,SACA,OACA,MACA,KACM;AACN,OAAK,OAAO,KAAK;GAAE;GAAU;GAAU;GAAS;GAAS;GAAO;GAAM;GAAS;GAAK,CAAC;;CAGvF,YAAoB,YAAkC;AACpD,SAAO;GACL,SAAS;IACP,OAAO,KAAK,OAAO;IACnB,QAAQ,KAAK,OAAO,QAAO,MAAK,EAAE,aAAa,QAAQ,CAAC;IACxD,UAAU,KAAK,OAAO,QAAO,MAAK,EAAE,aAAa,UAAU,CAAC;IAC5D,OAAO,KAAK;IACZ,QAAQ;IACR,SAAS,KAAK;IACf;GACD,QAAQ,KAAK;GACd;;;;;ACxsBL,SAAgB,oBAAoB,QAAmB,QAA2B,QAA4B;AAC5G,QAAO,aACL,sBACA;EACE,aAAa;GACX;GACA;GACA;GACA;GACA;GACA;GACD,CAAC,KAAK,IAAI;EACX,aAAa,EAAE,cAAc,MAAM;EACnC,aAAa;GACX,KAAK,EAAE,SAAS,CAAC,UAAU,CAAC,QAAQ,MAAM,CAAC,SAAS,6GAA6G;GACjK,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,8DAA8D;GACvG;EACF,EACD,OAAO,EAAE,KAAK,cAAc;AAC1B,MAAI;AACF,OAAI,IAOF,QAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,aAN5B,MAAM,OAAO,YAAY,OAAO,OAAO;AAEpD,YADe,IAAI,OAAO,GAAG,UAAU,GAAG,SAAS,CACrC,SAAS;MAAE,KAAK;MAAM;MAAS,CAAC;OAC7C,EAAE,SAAS,aAAa,UAAU,IAAI,YAAY,MAAM,CAAC,CAGG;IAAE,CAAC,EACjE;GAIH,MAAM,iBAAiB,OAAO,kBAAkB;AAKhD,UAAO,EACL,SAAS,CAAC;IAAE,MAAM;IAAiB,MAAM,aAH5B,MADA,IAAI,OADF,KAAK,OAAO,UAAU,eAAe,EAClB,OAAO,SAAS,CACxB,SAAS;KAAE,KAAK;KAAO;KAAS,CAAC,CAGE;IAAE,CAAC,EACjE;WAEI,KAAK;AACV,UAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAiB,MAAM,UAAU;KAAO,CAAC;IAAE,SAAS;IAAM;;GAG1F;;AAGH,SAAS,aAAa,QAA8B;CAClD,MAAM,EAAE,SAAS,WAAW;CAE5B,MAAM,QAAkB,EAAE;AAG1B,KAAI,QAAQ,WAAW,KAAK,QAAQ,aAAa,EAC/C,OAAM,KAAK,qBAAqB;UAEzB,QAAQ,WAAW,EAC1B,OAAM,KAAK,yCAAyC;KAGpD,OAAM,KAAK,qBAAqB;AAIlC,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,mBAAmB,QAAQ,SAAS;AAC/C,OAAM,KAAK,oBAAoB,QAAQ,UAAU;AACjD,OAAM,KAAK,WAAW,QAAQ,SAAS;AACvC,OAAM,KAAK,aAAa,QAAQ,WAAW;AAC3C,KAAI,QAAQ,QAAQ,EAClB,OAAM,KAAK,UAAU,QAAQ,QAAQ;AAIvC,KAAI,OAAO,SAAS,GAAG;AACrB,QAAM,KAAK,GAAG;EAEd,MAAM,0BAAU,IAAI,KAA4B;AAChD,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,MAAM,MAAM;GAClB,MAAM,MAAM,QAAQ,IAAI,IAAI,IAAI,EAAE;AAClC,OAAI,KAAK,MAAM;AACf,WAAQ,IAAI,KAAK,IAAI;;EAGvB,MAAM,iBAAyC;GAC7C,WAAW;GACX,IAAI;GACJ,QAAQ;GACR,UAAU;GACV,OAAO;GACP,MAAM;GACP;AAED,OAAK,MAAM,CAAC,UAAU,mBAAmB,SAAS;AAChD,SAAM,KAAK,SAAS,eAAe,aAAa,WAAW;AAC3D,QAAK,MAAM,SAAS,gBAAgB;IAClC,MAAM,OAAO,MAAM,aAAa,UAAU,MAAM;IAChD,MAAM,WAAW;KAAC,MAAM;KAAS,MAAM;KAAS,MAAM;KAAM,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM;IACxF,MAAM,WAAW,MAAM,OAAO,KAAK,MAAM,KAAK,KAAK;IACnD,MAAM,UAAU,MAAM,MAAM,UAAU,MAAM,IAAI,KAAK;AACrD,UAAM,KAAK,KAAK,KAAK,GAAG,WAAW,IAAI,SAAS,KAAK,KAAK,SAAS,GAAG,MAAM,UAAU,UAAU;;;;AAMtG,OAAM,KAAK,UAAU;AACrB,OAAM,KAAK,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AAE3C,QAAO,MAAM,KAAK,KAAK;;;;AC/GzB,SAAgB,aAAa,QAAiC;CAC5D,MAAM,SAAS,IAAI,UAAU;EAC3B,MAAM;EACN,SAAS;EACV,CAAC;CAEF,MAAM,SAAS,IAAI,kBAAkB,OAAO;AAG5C,oBAAmB,QAAQ,OAAO;AAClC,sBAAqB,QAAQ,OAAO;AACpC,oBAAmB,QAAQ,OAAO;AAClC,qBAAoB,QAAQ,QAAQ,OAAO;AAE3C,QAAO;;;;AChBT,MAAM,WAAW,QAAQ,IAAI,yBAAyB,QAAQ,KAAK;AACnE,MAAM,SAAS,QAAQ,IAAI,sBAAsB;AACjD,MAAM,SAAS,QAAQ,IAAI,sBAAsB;AACjD,MAAM,iBAAiB,QAAQ,IAAI,mBAAmB;AACtD,MAAM,SAAS,QAAQ,IAAI,wBAAwB;AACnD,MAAM,aAAa,QAAQ,IAAI;AAC/B,MAAM,cAAc,QAAQ,IAAI;AAEhC,MAAM,SAAS,aAAa;CAC1B;CACA;CACA;CACA;CACA;CACA,QAAQ,cAAc,cAClB;EAAE,MAAM;EAAY,OAAO;EAAa,GACxC,KAAA;CACL,CAAC;AAEF,eAAe,OAAsB;CACnC,MAAM,YAAY,IAAI,sBAAsB;AAC5C,OAAM,OAAO,QAAQ,UAAU;;AAGjC,MAAM,CAAC,OAAO,QAAQ;AACpB,SAAQ,MAAM,2CAA2C,IAAI;AAC7D,SAAQ,KAAK,EAAE;EACf"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/util/json.ts","../src/errors.ts","../src/core/model/system-fields.ts","../src/core/model/manager.ts","../src/util/id.ts","../src/core/content/markdown.ts","../src/core/content/manager.ts","../src/core/asset/manager.ts","../src/core/validation/rules.ts","../src/core/validation/validator.ts","../src/core/git/merge.ts","../src/core/git/sync.ts","../src/core/writer.ts","../src/tools/model.ts","../src/tools/content.ts","../src/tools/asset.ts","../src/core/doctor.ts","../src/tools/doctor.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["import { readFile, writeFile, rename } from 'node:fs/promises'\nimport { join, dirname } from 'node:path'\nimport { randomBytes } from 'node:crypto'\nimport { ensureDir } from './fs.js'\n\n/**\n * Read and parse a JSON file. Returns empty array if file doesn't exist.\n */\nexport async function readJsonFile<T = unknown>(filePath: string): Promise<T> {\n try {\n const raw = await readFile(filePath, 'utf-8')\n return JSON.parse(raw) as T\n }\n catch (err: unknown) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n return [] as unknown as T\n }\n throw err\n }\n}\n\n/**\n * Write JSON to file atomically (temp file + rename).\n */\nexport async function writeJsonFile(filePath: string, data: unknown): Promise<void> {\n await ensureDir(dirname(filePath))\n const tmpPath = join(dirname(filePath), `.tmp-${randomBytes(4).toString('hex')}.json`)\n const content = JSON.stringify(data, null, 2) + '\\n'\n await writeFile(tmpPath, content, 'utf-8')\n await rename(tmpPath, filePath)\n}\n","export class ContentrainError extends Error {\n code: string\n context: Record<string, unknown>\n\n constructor(message: string, code: string, context: Record<string, unknown> = {}) {\n super(message)\n this.name = 'ContentrainError'\n this.code = code\n this.context = context\n }\n}\n\nexport class ValidationError extends ContentrainError {\n fieldErrors: Array<{ fieldId: string; message: string; rule: string }>\n\n constructor(message: string, fieldErrors: Array<{ fieldId: string; message: string; rule: string }>) {\n super(message, 'VALIDATION_ERROR', { fieldErrors })\n this.name = 'ValidationError'\n this.fieldErrors = fieldErrors\n }\n}\n\nexport class ModelNotFoundError extends ContentrainError {\n constructor(modelId: string) {\n super(`Model not found: ${modelId}`, 'MODEL_NOT_FOUND', { modelId })\n this.name = 'ModelNotFoundError'\n }\n}\n\nexport class EntryNotFoundError extends ContentrainError {\n constructor(modelId: string, entryId: string) {\n super(`Entry not found: ${entryId} in model ${modelId}`, 'ENTRY_NOT_FOUND', { modelId, entryId })\n this.name = 'EntryNotFoundError'\n }\n}\n\nexport class GitSyncError extends ContentrainError {\n constructor(message: string, context: Record<string, unknown> = {}) {\n super(message, 'GIT_SYNC_ERROR', context)\n this.name = 'GitSyncError'\n }\n}\n\nexport class MergeConflictError extends GitSyncError {\n conflictedFiles: string[]\n\n constructor(conflictedFiles: string[]) {\n super(`Merge conflict in files: ${conflictedFiles.join(', ')}`, { conflictedFiles })\n this.name = 'MergeConflictError'\n this.conflictedFiles = conflictedFiles\n }\n}\n\nexport class PushRejectedError extends GitSyncError {\n attemptsExhausted: boolean\n\n constructor(attemptsExhausted: boolean) {\n super(\n attemptsExhausted\n ? 'Push rejected: all retry attempts exhausted'\n : 'Push rejected by remote',\n { attemptsExhausted },\n )\n this.name = 'PushRejectedError'\n this.attemptsExhausted = attemptsExhausted\n }\n}\n\nexport class RelationIntegrityError extends ValidationError {\n constructor(fieldId: string, referencedModel: string, referencedId: string) {\n super(\n `Relation integrity error: ${referencedId} not found in model ${referencedModel}`,\n [{ fieldId, message: `Referenced entry ${referencedId} not found in ${referencedModel}`, rule: 'relation-integrity' }],\n )\n this.name = 'RelationIntegrityError'\n }\n}\n\nexport class AssetNotFoundError extends ContentrainError {\n constructor(assetPath: string) {\n super(`Asset not found in registry: ${assetPath}`, 'ASSET_NOT_FOUND', { assetPath })\n this.name = 'AssetNotFoundError'\n }\n}\n","import type { FieldDefinition } from '../../types/index.js'\n\n/**\n * Generate the 4 required system fields for a model.\n */\nexport function createSystemFields(modelId: string): FieldDefinition[] {\n return [\n {\n name: 'createdAt',\n fieldId: 'createdAt',\n componentId: 'date',\n options: {},\n validations: { 'required-field': { value: true } },\n fieldType: 'date',\n system: true,\n defaultField: false,\n modelId,\n },\n {\n name: 'updatedAt',\n fieldId: 'updatedAt',\n componentId: 'date',\n options: {},\n validations: { 'required-field': { value: true } },\n fieldType: 'date',\n system: true,\n defaultField: false,\n modelId,\n },\n {\n name: 'ID',\n fieldId: 'ID',\n componentId: 'single-line-text',\n options: {},\n validations: { 'required-field': { value: true } },\n fieldType: 'string',\n system: true,\n defaultField: false,\n modelId,\n },\n {\n name: 'status',\n fieldId: 'status',\n componentId: 'single-line-text',\n options: {},\n validations: { 'required-field': { value: true } },\n fieldType: 'string',\n system: true,\n defaultField: false,\n modelId,\n },\n ]\n}\n\n/**\n * Generate MD/MDX default fields (slug + content).\n */\nexport function createMdDefaultFields(modelId: string): FieldDefinition[] {\n return [\n {\n name: 'slug',\n fieldId: 'slug',\n componentId: 'slug',\n options: {},\n validations: { 'required-field': { value: true }, 'unique-field': { value: true } },\n fieldType: 'string',\n system: false,\n defaultField: true,\n modelId,\n },\n {\n name: 'content',\n fieldId: 'content',\n componentId: 'md-editor',\n options: {},\n validations: { 'required-field': { value: false } },\n fieldType: 'string',\n system: false,\n defaultField: true,\n modelId,\n },\n ]\n}\n","import { join } from 'node:path'\nimport type { ModelMetadata, FieldDefinition } from '../../types/index.js'\nimport { readJsonFile, writeJsonFile } from '../../util/json.js'\nimport { ensureDir } from '../../util/fs.js'\nimport { ModelNotFoundError } from '../../errors.js'\nimport { createSystemFields, createMdDefaultFields } from './system-fields.js'\n\nexport class ModelManager {\n constructor(private basePath: string) {}\n\n private get modelsDir(): string {\n return join(this.basePath, 'models')\n }\n\n private get metadataPath(): string {\n return join(this.modelsDir, 'metadata.json')\n }\n\n // ── Read Operations ──\n\n async list(): Promise<ModelMetadata[]> {\n return readJsonFile<ModelMetadata[]>(this.metadataPath)\n }\n\n async get(modelId: string): Promise<ModelMetadata> {\n const models = await this.list()\n const model = models.find(m => m.modelId === modelId)\n if (!model) throw new ModelNotFoundError(modelId)\n return model\n }\n\n async getFields(modelId: string): Promise<FieldDefinition[]> {\n await this.get(modelId) // ensure model exists\n const fieldsPath = join(this.modelsDir, `${modelId}.json`)\n return readJsonFile<FieldDefinition[]>(fieldsPath)\n }\n\n async describe(modelId: string): Promise<{\n model: ModelMetadata\n fields: FieldDefinition[]\n customFields: FieldDefinition[]\n }> {\n const model = await this.get(modelId)\n const fields = await this.getFields(modelId)\n const customFields = fields.filter(f => !f.system && !f.defaultField)\n return { model, fields, customFields }\n }\n\n // ── Write Operations ──\n\n async create(input: {\n name: string\n description?: string\n modelId: string\n localization: boolean\n type: 'JSON' | 'MD' | 'MDX'\n path?: string\n fields?: Omit<FieldDefinition, 'modelId' | 'system' | 'defaultField'>[]\n }): Promise<ModelMetadata> {\n const models = await this.list()\n\n if (models.some(m => m.modelId === input.modelId)) {\n throw new Error(`Model already exists: ${input.modelId}`)\n }\n\n // Create metadata entry\n const metadata: ModelMetadata = {\n name: input.name,\n description: input.description,\n modelId: input.modelId,\n path: input.path,\n localization: input.localization,\n type: input.type,\n createdBy: 'user',\n isServerless: false,\n }\n\n models.push(metadata)\n await writeJsonFile(this.metadataPath, models)\n\n // Build field definitions\n const systemFields = createSystemFields(input.modelId)\n const mdFields = (input.type === 'MD' || input.type === 'MDX')\n ? createMdDefaultFields(input.modelId)\n : []\n const customFields: FieldDefinition[] = (input.fields || []).map(f => ({\n ...f,\n modelId: input.modelId,\n system: false,\n defaultField: false,\n }))\n\n const allFields = [...systemFields, ...mdFields, ...customFields]\n await ensureDir(this.modelsDir)\n await writeJsonFile(join(this.modelsDir, `${input.modelId}.json`), allFields)\n\n // Create empty content directory and file\n const contentDir = join(this.basePath, input.modelId)\n await ensureDir(contentDir)\n\n if (input.localization) {\n // We don't know which locales yet — create dir only, files created on first content write\n }\n else {\n await writeJsonFile(join(contentDir, `${input.modelId}.json`), [])\n }\n\n return metadata\n }\n\n async addField(modelId: string, field: Omit<FieldDefinition, 'modelId' | 'system' | 'defaultField'>): Promise<FieldDefinition> {\n const fields = await this.getFields(modelId)\n\n if (fields.some(f => f.fieldId === field.fieldId)) {\n throw new Error(`Field already exists: ${field.fieldId} in model ${modelId}`)\n }\n\n const newField: FieldDefinition = {\n ...field,\n modelId,\n system: false,\n defaultField: false,\n }\n\n fields.push(newField)\n await writeJsonFile(join(this.modelsDir, `${modelId}.json`), fields)\n\n return newField\n }\n\n async removeField(modelId: string, fieldId: string): Promise<void> {\n const fields = await this.getFields(modelId)\n const field = fields.find(f => f.fieldId === fieldId)\n\n if (!field) throw new Error(`Field not found: ${fieldId} in model ${modelId}`)\n if (field.system) throw new Error(`Cannot remove system field: ${fieldId}`)\n\n const filtered = fields.filter(f => f.fieldId !== fieldId)\n await writeJsonFile(join(this.modelsDir, `${modelId}.json`), filtered)\n }\n\n async delete(modelId: string): Promise<void> {\n const models = await this.list()\n const filtered = models.filter(m => m.modelId !== modelId)\n\n if (filtered.length === models.length) {\n throw new ModelNotFoundError(modelId)\n }\n\n await writeJsonFile(this.metadataPath, filtered)\n\n // Remove field definitions\n const { removeFile, removeDir } = await import('../../util/fs.js')\n await removeFile(join(this.modelsDir, `${modelId}.json`))\n\n // Remove content directory\n await removeDir(join(this.basePath, modelId))\n }\n}\n","import { randomUUID } from 'node:crypto'\n\nconst ID_PATTERN = /^[a-f0-9]{8,12}$/\n\n/**\n * Generate a 12-character lowercase hexadecimal ID.\n * Matches the Contentrain platform: crypto.randomUUID().split('-').pop()\n */\nexport function generateId(): string {\n return randomUUID().split('-').pop()!\n}\n\n/**\n * Validate that a string matches the Contentrain ID format.\n */\nexport function isValidId(id: string): boolean {\n return ID_PATTERN.test(id)\n}\n","import { join } from 'node:path'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport type { MDContentEntry, ModelMetadata } from '../../types/index.js'\nimport { ensureDir, removeFile } from '../../util/fs.js'\n\n/**\n * Build frontmatter string from entry data (excluding 'content' field).\n */\nfunction buildFrontmatter(entry: Record<string, unknown>): string {\n const lines: string[] = ['---']\n for (const [key, value] of Object.entries(entry)) {\n if (key === 'content') continue\n if (typeof value === 'string') {\n lines.push(`${key}: '${value}'`)\n }\n else if (typeof value === 'boolean' || typeof value === 'number') {\n lines.push(`${key}: ${value}`)\n }\n else if (Array.isArray(value)) {\n lines.push(`${key}: ${JSON.stringify(value)}`)\n }\n }\n lines.push('---')\n return lines.join('\\n')\n}\n\n/**\n * Get the markdown file extension based on model type.\n */\nfunction getExtension(type: 'MD' | 'MDX'): string {\n return type === 'MDX' ? '.mdx' : '.md'\n}\n\n/**\n * Resolve the markdown file path for an entry.\n */\nfunction resolveMarkdownPath(\n repoPath: string,\n model: ModelMetadata,\n entry: MDContentEntry,\n locale?: string,\n): string {\n const basePath = entry.path || model.path || ''\n const ext = getExtension(model.type as 'MD' | 'MDX')\n\n if (locale) {\n return join(repoPath, basePath, locale, `${entry.slug}${ext}`)\n }\n return join(repoPath, basePath, `${entry.slug}${ext}`)\n}\n\nexport class MarkdownSync {\n constructor(private repoPath: string) {}\n\n /**\n * Write a markdown file for an MD/MDX entry.\n */\n async write(\n model: ModelMetadata,\n entry: MDContentEntry,\n content: string,\n locale?: string,\n ): Promise<string> {\n const filePath = resolveMarkdownPath(this.repoPath, model, entry, locale)\n await ensureDir(join(filePath, '..'))\n\n const frontmatter = buildFrontmatter(entry)\n const body = content || ''\n const fileContent = `${frontmatter}\\n\\n${body}\\n`\n\n await writeFile(filePath, fileContent, 'utf-8')\n return filePath\n }\n\n /**\n * Read markdown content from a file.\n */\n async read(\n model: ModelMetadata,\n entry: MDContentEntry,\n locale?: string,\n ): Promise<string> {\n const filePath = resolveMarkdownPath(this.repoPath, model, entry, locale)\n try {\n const raw = await readFile(filePath, 'utf-8')\n // Extract content after frontmatter\n const match = raw.match(/^---\\n[\\s\\S]*?\\n---\\n\\n?([\\s\\S]*)$/)\n return match ? match[1].trim() : ''\n }\n catch {\n return ''\n }\n }\n\n /**\n * Delete a markdown file for an entry.\n */\n async delete(\n model: ModelMetadata,\n entry: MDContentEntry,\n locale?: string,\n ): Promise<void> {\n const filePath = resolveMarkdownPath(this.repoPath, model, entry, locale)\n await removeFile(filePath)\n }\n}\n","import { join } from 'node:path'\nimport type { ContentEntry, MDContentEntry, ModelMetadata } from '../../types/index.js'\nimport { readJsonFile, writeJsonFile } from '../../util/json.js'\nimport { ensureDir, listJsonFiles } from '../../util/fs.js'\nimport { generateId } from '../../util/id.js'\nimport { EntryNotFoundError } from '../../errors.js'\nimport { ModelManager } from '../model/manager.js'\nimport { MarkdownSync } from './markdown.js'\n\nexport class ContentManager {\n private modelManager: ModelManager\n private markdownSync: MarkdownSync\n\n constructor(\n private basePath: string,\n private repoPath: string,\n ) {\n this.modelManager = new ModelManager(basePath)\n this.markdownSync = new MarkdownSync(repoPath)\n }\n\n // ── Path Helpers ──\n\n private contentDir(modelId: string): string {\n return join(this.basePath, modelId)\n }\n\n private contentFilePath(modelId: string, locale?: string): string {\n const dir = this.contentDir(modelId)\n return locale\n ? join(dir, `${locale}.json`)\n : join(dir, `${modelId}.json`)\n }\n\n // ── Read Operations ──\n\n async list(modelId: string, locale?: string): Promise<ContentEntry[]> {\n const model = await this.modelManager.get(modelId)\n const filePath = this.resolveContentPath(model, locale)\n return readJsonFile<ContentEntry[]>(filePath)\n }\n\n async get(modelId: string, entryId: string, locale?: string): Promise<ContentEntry> {\n const entries = await this.list(modelId, locale)\n const entry = entries.find(e => e.ID === entryId)\n if (!entry) throw new EntryNotFoundError(modelId, entryId)\n return entry\n }\n\n async getLocales(modelId: string): Promise<string[]> {\n const model = await this.modelManager.get(modelId)\n if (!model.localization) return []\n const files = await listJsonFiles(this.contentDir(modelId))\n // Exclude modelId.json (non-localized file name)\n return files.filter(f => f !== modelId)\n }\n\n // ── Write Operations ──\n\n async create(\n modelId: string,\n data: Record<string, unknown>,\n options?: { locale?: string; id?: string; status?: 'draft' | 'publish'; content?: string },\n ): Promise<string> {\n const model = await this.modelManager.get(modelId)\n const id = options?.id || generateId()\n const now = new Date().toISOString()\n\n const entry: ContentEntry = {\n ID: id,\n createdAt: now,\n updatedAt: now,\n status: options?.status || 'draft',\n scheduled: false,\n ...data,\n }\n\n if (model.localization) {\n await this.createLocalized(model, entry, options?.locale, options?.content)\n }\n else {\n await this.appendEntry(model, entry, undefined, options?.content)\n }\n\n return id\n }\n\n async update(\n modelId: string,\n entryId: string,\n data: Record<string, unknown>,\n options?: { locale?: string; content?: string },\n ): Promise<ContentEntry> {\n const model = await this.modelManager.get(modelId)\n const locale = model.localization ? options?.locale : undefined\n const filePath = this.resolveContentPath(model, locale)\n const entries = await readJsonFile<ContentEntry[]>(filePath)\n\n const index = entries.findIndex(e => e.ID === entryId)\n if (index === -1) throw new EntryNotFoundError(modelId, entryId)\n\n // Preserve createdAt, update updatedAt\n const updated: ContentEntry = {\n ...entries[index],\n ...data,\n ID: entryId,\n createdAt: entries[index].createdAt,\n updatedAt: new Date().toISOString(),\n }\n\n entries[index] = updated\n await writeJsonFile(filePath, entries)\n\n // Sync markdown if MD/MDX\n if ((model.type === 'MD' || model.type === 'MDX') && 'slug' in updated) {\n const mdContent = options?.content ?? await this.markdownSync.read(model, updated as MDContentEntry, locale)\n await this.markdownSync.write(model, updated as MDContentEntry, mdContent, locale)\n }\n\n return updated\n }\n\n async delete(modelId: string, entryId: string): Promise<void> {\n const model = await this.modelManager.get(modelId)\n\n if (model.localization) {\n const locales = await this.getLocales(modelId)\n for (const locale of locales) {\n await this.deleteFromFile(model, entryId, locale)\n }\n }\n else {\n await this.deleteFromFile(model, entryId)\n }\n }\n\n // ── Private Helpers ──\n\n private resolveContentPath(model: ModelMetadata, locale?: string): string {\n if (model.localization && locale) {\n return this.contentFilePath(model.modelId, locale)\n }\n if (model.localization && !locale) {\n // Default: try to find any locale file\n return this.contentFilePath(model.modelId, model.modelId)\n }\n return this.contentFilePath(model.modelId)\n }\n\n private async createLocalized(\n model: ModelMetadata,\n entry: ContentEntry,\n locale?: string,\n content?: string,\n ): Promise<void> {\n if (locale) {\n // Create for a specific locale\n await this.appendEntry(model, entry, locale, content)\n }\n else {\n // Get existing locales or default\n const locales = await this.getLocales(model.modelId)\n if (locales.length === 0) {\n // No locale files yet — cannot determine locales\n throw new Error(\n `Model \"${model.modelId}\" is localized but no locale files exist. `\n + 'Specify a locale (e.g., locale: \"en\") to create the first entry.',\n )\n }\n // Create entry in ALL locale files with the same ID\n for (const loc of locales) {\n await this.appendEntry(model, entry, loc, content)\n }\n }\n }\n\n private async appendEntry(\n model: ModelMetadata,\n entry: ContentEntry,\n locale?: string,\n content?: string,\n ): Promise<void> {\n const filePath = this.resolveContentPath(model, locale)\n await ensureDir(join(filePath, '..'))\n\n const entries = await readJsonFile<ContentEntry[]>(filePath)\n\n // Check for duplicate ID\n if (entries.some(e => e.ID === entry.ID)) {\n throw new Error(`Duplicate ID: ${entry.ID} already exists in ${model.modelId}`)\n }\n\n entries.push(entry)\n await writeJsonFile(filePath, entries)\n\n // Create markdown file if MD/MDX\n if ((model.type === 'MD' || model.type === 'MDX') && 'slug' in entry) {\n await this.markdownSync.write(model, entry as MDContentEntry, content || '', locale)\n }\n }\n\n private async deleteFromFile(model: ModelMetadata, entryId: string, locale?: string): Promise<void> {\n const filePath = this.resolveContentPath(model, locale)\n const entries = await readJsonFile<ContentEntry[]>(filePath)\n\n const entry = entries.find(e => e.ID === entryId)\n if (!entry) return // Already deleted or doesn't exist in this locale\n\n const filtered = entries.filter(e => e.ID !== entryId)\n await writeJsonFile(filePath, filtered)\n\n // Delete markdown file if MD/MDX\n if ((model.type === 'MD' || model.type === 'MDX') && 'slug' in entry) {\n await this.markdownSync.delete(model, entry as MDContentEntry, locale)\n }\n }\n}\n","import { join } from 'node:path'\nimport { stat } from 'node:fs/promises'\nimport type { AssetEntry, WriterConfig } from '../../types/index.js'\nimport { readJsonFile, writeJsonFile } from '../../util/json.js'\nimport { fileExists } from '../../util/fs.js'\nimport { AssetNotFoundError } from '../../errors.js'\n\nexport class AssetManager {\n private assetsPath: string\n\n constructor(\n private basePath: string,\n private repoPath: string,\n private config: WriterConfig,\n ) {\n this.assetsPath = join(basePath, 'assets.json')\n }\n\n async list(): Promise<AssetEntry[]> {\n return readJsonFile<AssetEntry[]>(this.assetsPath)\n }\n\n async get(assetPath: string): Promise<AssetEntry | undefined> {\n const assets = await this.list()\n return assets.find(a => a.path === assetPath)\n }\n\n async register(input: {\n path: string\n mimetype: string\n alt?: string\n }): Promise<AssetEntry> {\n // Verify file physically exists\n const fullPath = join(this.repoPath, input.path)\n if (!await fileExists(fullPath)) {\n throw new Error(`Asset file does not exist at: ${input.path}`)\n }\n\n const assets = await this.list()\n\n // Check for duplicate\n if (assets.some(a => a.path === input.path)) {\n throw new Error(`Asset already registered: ${input.path}`)\n }\n\n // Get file size\n const stats = await stat(fullPath)\n const now = new Date().toISOString()\n\n const entry: AssetEntry = {\n path: input.path,\n mimetype: input.mimetype,\n size: stats.size,\n alt: input.alt || '',\n meta: {\n user: {\n name: this.config.author?.name || 'AI Agent',\n email: this.config.author?.email || 'agent@contentrain.io',\n },\n createdAt: now,\n updatedAt: now,\n },\n }\n\n assets.push(entry)\n await writeJsonFile(this.assetsPath, assets)\n\n return entry\n }\n\n async deregister(assetPath: string): Promise<void> {\n const assets = await this.list()\n const filtered = assets.filter(a => a.path !== assetPath)\n\n if (filtered.length === assets.length) {\n throw new AssetNotFoundError(assetPath)\n }\n\n await writeJsonFile(this.assetsPath, filtered)\n }\n\n async update(assetPath: string, updates: { alt?: string; mimetype?: string }): Promise<AssetEntry> {\n const assets = await this.list()\n const index = assets.findIndex(a => a.path === assetPath)\n\n if (index === -1) throw new AssetNotFoundError(assetPath)\n\n if (updates.alt !== undefined) assets[index].alt = updates.alt\n if (updates.mimetype !== undefined) assets[index].mimetype = updates.mimetype\n if (assets[index].meta) {\n assets[index].meta!.updatedAt = new Date().toISOString()\n }\n\n await writeJsonFile(this.assetsPath, assets)\n return assets[index]\n }\n}\n","import type { ComponentId, FieldType } from '../../types/index.js'\nimport { isValidId } from '../../util/id.js'\n\nconst VALID_STATUSES = ['draft', 'publish', 'changed'] as const\n\n/**\n * Map componentId to expected runtime JS type.\n */\nexport function getExpectedType(componentId: ComponentId, fieldType: FieldType): string {\n switch (componentId) {\n // String components\n case 'single-line-text':\n case 'multi-line-text':\n case 'email':\n case 'url':\n case 'slug':\n case 'color':\n case 'json':\n case 'md-editor':\n case 'rich-text-editor':\n case 'phone-number': // phone-number is string, NOT number\n return 'string'\n\n // Number components\n case 'integer':\n case 'decimal':\n case 'rating':\n case 'percent':\n return 'number'\n\n // Boolean components\n case 'checkbox':\n case 'switch':\n return 'boolean'\n\n // Date components — stored as ISO string\n case 'date':\n case 'date-time':\n return 'string'\n\n // Media — stored as path string\n case 'media':\n return 'string'\n\n // Relations\n case 'one-to-one':\n return 'string'\n case 'one-to-many':\n return 'array'\n\n default:\n return fieldType === 'array' ? 'array' : 'string'\n }\n}\n\n/**\n * Validate an ID matches Contentrain format (12-char lowercase hex).\n */\nexport function validateId(id: unknown): string | null {\n if (typeof id !== 'string') return 'ID must be a string'\n if (!isValidId(id)) return 'ID must be 8–12 character lowercase hexadecimal'\n return null\n}\n\n/**\n * Validate a status value.\n */\nexport function validateStatus(status: unknown): string | null {\n if (typeof status !== 'string') return 'status must be a string'\n if (!VALID_STATUSES.includes(status as typeof VALID_STATUSES[number])) {\n return `status must be one of: ${VALID_STATUSES.join(', ')}`\n }\n return null\n}\n\n/**\n * Validate a value matches the expected runtime type.\n */\nexport function validateFieldValue(value: unknown, expectedType: string, fieldId: string): string | null {\n if (value === null || value === undefined) return null // handled by required check\n\n switch (expectedType) {\n case 'string':\n if (typeof value !== 'string') return `${fieldId} must be a string`\n break\n case 'number':\n if (typeof value !== 'number') return `${fieldId} must be a number`\n break\n case 'boolean':\n if (typeof value !== 'boolean') return `${fieldId} must be a boolean`\n break\n case 'array':\n if (!Array.isArray(value)) return `${fieldId} must be an array`\n break\n }\n\n return null\n}\n\n/**\n * Validate integer specifically.\n */\nexport function validateInteger(value: unknown, fieldId: string): string | null {\n if (typeof value === 'number' && !Number.isInteger(value)) {\n return `${fieldId} must be an integer`\n }\n return null\n}\n","import { join } from 'node:path'\nimport type { ContentEntry, ValidationResult, ValidationFieldError } from '../../types/index.js'\nimport { readJsonFile } from '../../util/json.js'\nimport { ModelManager } from '../model/manager.js'\nimport { getExpectedType, validateFieldValue, validateInteger, validateId, validateStatus } from './rules.js'\n\nexport class Validator {\n private modelManager: ModelManager\n\n constructor(private basePath: string) {\n this.modelManager = new ModelManager(basePath)\n }\n\n /**\n * Validate content data against model field definitions.\n */\n async validate(\n modelId: string,\n data: Record<string, unknown>,\n options?: { isUpdate?: boolean },\n ): Promise<ValidationResult> {\n const errors: ValidationFieldError[] = []\n const { fields, model } = await this.modelManager.describe(modelId)\n\n const nonSystemFields = fields.filter(f => !f.system)\n\n for (const field of nonSystemFields) {\n const value = data[field.fieldId]\n\n // Required check (skip on update if field not provided)\n if (field.validations?.['required-field']?.value) {\n if (!options?.isUpdate && (value === undefined || value === null || value === '')) {\n // For MD/MDX, content field is not required in JSON\n if (field.fieldId === 'content' && (model.type === 'MD' || model.type === 'MDX')) {\n continue\n }\n errors.push({\n fieldId: field.fieldId,\n message: `${field.name} is required`,\n rule: 'required-field',\n })\n continue\n }\n }\n\n if (value === undefined || value === null) continue\n\n // Type check\n const expectedType = getExpectedType(field.componentId, field.fieldType)\n const typeError = validateFieldValue(value, expectedType, field.fieldId)\n if (typeError) {\n errors.push({ fieldId: field.fieldId, message: typeError, rule: 'type-check' })\n continue\n }\n\n // Integer check\n if (field.componentId === 'integer') {\n const intError = validateInteger(value, field.fieldId)\n if (intError) {\n errors.push({ fieldId: field.fieldId, message: intError, rule: 'integer-check' })\n }\n }\n\n // Relation integrity check\n if (field.componentId === 'one-to-one' && typeof value === 'string') {\n const refModel = field.options?.reference?.form?.reference?.value\n if (refModel) {\n const integrityError = await this.checkRelationIntegrity(refModel, value)\n if (integrityError) {\n errors.push({ fieldId: field.fieldId, message: integrityError, rule: 'relation-integrity' })\n }\n }\n }\n\n if (field.componentId === 'one-to-many' && Array.isArray(value)) {\n const refModel = field.options?.reference?.form?.reference?.value\n if (refModel) {\n for (const refId of value) {\n if (typeof refId === 'string') {\n const integrityError = await this.checkRelationIntegrity(refModel, refId)\n if (integrityError) {\n errors.push({ fieldId: field.fieldId, message: integrityError, rule: 'relation-integrity' })\n }\n }\n }\n }\n }\n\n // Media path check\n if (field.componentId === 'media' && typeof value === 'string' && value !== '') {\n const assetError = await this.checkAssetExists(value)\n if (assetError) {\n errors.push({ fieldId: field.fieldId, message: assetError, rule: 'asset-exists' })\n }\n }\n }\n\n // Unique field check\n if (!options?.isUpdate) {\n const uniqueFields = nonSystemFields.filter(f => f.validations?.['unique-field']?.value)\n for (const field of uniqueFields) {\n const value = data[field.fieldId]\n if (value === undefined || value === null) continue\n\n const uniqueError = await this.checkUniqueness(modelId, field.fieldId, value)\n if (uniqueError) {\n errors.push({ fieldId: field.fieldId, message: uniqueError, rule: 'unique-field' })\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n }\n\n /**\n * Validate ID format.\n */\n validateId(id: string): ValidationResult {\n const error = validateId(id)\n if (error) {\n return { valid: false, errors: [{ fieldId: 'ID', message: error, rule: 'id-format' }] }\n }\n return { valid: true, errors: [] }\n }\n\n /**\n * Validate status value.\n */\n validateStatus(status: string): ValidationResult {\n const error = validateStatus(status)\n if (error) {\n return { valid: false, errors: [{ fieldId: 'status', message: error, rule: 'status-value' }] }\n }\n return { valid: true, errors: [] }\n }\n\n // ── Private Helpers ──\n\n private async checkRelationIntegrity(refModelId: string, refEntryId: string): Promise<string | null> {\n try {\n await this.modelManager.get(refModelId) // verify model exists\n // Try to find the entry in non-localized or any locale file\n const contentDir = join(this.basePath, refModelId)\n const { listJsonFiles } = await import('../../util/fs.js')\n const files = await listJsonFiles(contentDir)\n\n for (const file of files) {\n const entries = await readJsonFile<ContentEntry[]>(join(contentDir, `${file}.json`))\n if (entries.some(e => e.ID === refEntryId)) return null\n }\n\n return `Referenced entry ${refEntryId} not found in model ${refModelId}`\n }\n catch {\n return `Referenced model ${refModelId} not found`\n }\n }\n\n private async checkAssetExists(assetPath: string): Promise<string | null> {\n try {\n const assets = await readJsonFile<Array<{ path: string }>>(join(this.basePath, 'assets.json'))\n if (assets.some(a => a.path === assetPath)) return null\n return `Asset not found in registry: ${assetPath}`\n }\n catch {\n return `Could not read assets.json`\n }\n }\n\n private async checkUniqueness(modelId: string, fieldId: string, value: unknown): Promise<string | null> {\n try {\n const contentDir = join(this.basePath, modelId)\n const { listJsonFiles } = await import('../../util/fs.js')\n const files = await listJsonFiles(contentDir)\n\n for (const file of files) {\n const entries = await readJsonFile<ContentEntry[]>(join(contentDir, `${file}.json`))\n if (entries.some(e => e[fieldId] === value)) {\n return `${fieldId} value \"${value}\" already exists — must be unique`\n }\n }\n return null\n }\n catch {\n return null\n }\n }\n}\n","import type { ContentEntry } from '../../types/index.js'\n\n/**\n * Three-way structural merge for JSON arrays indexed by ID.\n *\n * Strategy:\n * - New entries (in ours but not base): APPEND to result\n * - Deleted entries (in base but not in ours): REMOVE from result\n * - Modified entries (same ID, different fields): apply our field-level changes over theirs\n * - Entries only in theirs (added by another writer): KEEP\n */\nexport function structuralMerge(\n base: ContentEntry[],\n ours: ContentEntry[],\n theirs: ContentEntry[],\n): ContentEntry[] {\n const baseMap = indexById(base)\n const oursMap = indexById(ours)\n\n const result: ContentEntry[] = []\n const processedIds = new Set<string>()\n\n // 1. Process entries from theirs (remote) as the base ordering\n for (const theirEntry of theirs) {\n const id = theirEntry.ID\n processedIds.add(id)\n\n const baseEntry = baseMap.get(id)\n const ourEntry = oursMap.get(id)\n\n if (!ourEntry && baseEntry) {\n // We deleted it — skip (don't include in result)\n continue\n }\n\n if (ourEntry && baseEntry) {\n // Both have it — merge field-level changes\n result.push(mergeEntry(baseEntry, ourEntry, theirEntry))\n }\n else {\n // We don't have it (and it wasn't in base) — it's theirs only, keep it\n result.push(theirEntry)\n }\n }\n\n // 2. Add our new entries (in ours but not in base and not in theirs)\n for (const ourEntry of ours) {\n if (!processedIds.has(ourEntry.ID)) {\n result.push(ourEntry)\n }\n }\n\n return result\n}\n\n/**\n * Merge a single entry using three-way field comparison.\n * Our changes take precedence over theirs when both modified the same field.\n */\nfunction mergeEntry(\n base: ContentEntry,\n ours: ContentEntry,\n theirs: ContentEntry,\n): ContentEntry {\n const merged: ContentEntry = { ...theirs }\n\n for (const key of Object.keys(ours)) {\n const baseVal = JSON.stringify(base[key])\n const ourVal = JSON.stringify(ours[key])\n const theirVal = JSON.stringify(theirs[key])\n\n if (ourVal !== baseVal && theirVal === baseVal) {\n // We changed it, they didn't — take ours\n merged[key] = ours[key]\n }\n else if (ourVal !== baseVal && theirVal !== baseVal) {\n // Both changed — our change wins (last-writer-wins for same field)\n merged[key] = ours[key]\n }\n // If only they changed, theirs is already in merged via spread\n }\n\n return merged\n}\n\nfunction indexById(entries: ContentEntry[]): Map<string, ContentEntry> {\n const map = new Map<string, ContentEntry>()\n for (const entry of entries) {\n map.set(entry.ID, entry)\n }\n return map\n}\n","import { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { randomBytes } from 'node:crypto'\nimport { simpleGit, type SimpleGit } from 'simple-git'\nimport type { WorktreeContext, WriterConfig, ContentEntry } from '../../types/index.js'\nimport { GitSyncError, PushRejectedError } from '../../errors.js'\nimport { readJsonFile, writeJsonFile } from '../../util/json.js'\nimport { removeDir } from '../../util/fs.js'\nimport { structuralMerge } from './merge.js'\n\nexport class GitSyncManager {\n private config: Required<Pick<WriterConfig, 'remote' | 'retryAttempts' | 'commitPrefix'>> & WriterConfig\n\n constructor(config: WriterConfig) {\n this.config = {\n ...config,\n remote: config.remote || 'origin',\n retryAttempts: config.retryAttempts || 3,\n commitPrefix: config.commitPrefix || '[contentrain]',\n }\n }\n\n /**\n * Create an isolated git worktree for safe operations.\n */\n async createWorktree(): Promise<WorktreeContext> {\n const id = randomBytes(4).toString('hex')\n const worktreePath = join(tmpdir(), `contentrain-mcp-${id}`)\n const git = simpleGit(this.config.repoPath)\n\n try {\n // Fetch latest\n await git.fetch(this.config.remote, this.config.branch)\n\n // Create worktree from remote branch\n const remoteBranch = `${this.config.remote}/${this.config.branch}`\n const localBranch = `contentrain-work-${id}`\n\n await git.raw([\n 'worktree', 'add',\n '-b', localBranch,\n worktreePath,\n remoteBranch,\n ])\n\n return {\n path: worktreePath,\n branch: localBranch,\n id,\n }\n }\n catch (err) {\n throw new GitSyncError(`Failed to create worktree: ${err}`, {\n branch: this.config.branch,\n worktreePath,\n })\n }\n }\n\n /**\n * Commit all changes in the worktree.\n */\n async commit(ctx: WorktreeContext, message: string): Promise<string> {\n const git = simpleGit(ctx.path)\n const fullMessage = `${this.config.commitPrefix} ${message}`\n\n await git.add('-A')\n\n // Check if there are actual changes\n const status = await git.status()\n if (status.files.length === 0) {\n return '' // Nothing to commit\n }\n\n const authorArgs: string[] = []\n if (this.config.author) {\n authorArgs.push('--author', `${this.config.author.name} <${this.config.author.email}>`)\n }\n\n const result = await git.commit(fullMessage, undefined, Object.fromEntries(\n authorArgs.length ? [['--author', `${this.config.author!.name} <${this.config.author!.email}>`]] : [],\n ))\n\n return result.commit\n }\n\n /**\n * Push with rebase-retry on conflict.\n */\n async pushWithRetry(ctx: WorktreeContext): Promise<void> {\n const git = simpleGit(ctx.path)\n const maxAttempts = this.config.retryAttempts\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n // Push our local branch to the remote target branch\n await git.push(this.config.remote, `${ctx.branch}:${this.config.branch}`)\n return // Success\n }\n catch {\n if (attempt === maxAttempts) {\n throw new PushRejectedError(true)\n }\n\n // Fetch latest and try structural merge\n try {\n await this.resolveAndRetry(ctx)\n }\n catch (mergeErr) {\n if (attempt === maxAttempts) {\n throw new GitSyncError(`Failed to resolve conflicts after ${maxAttempts} attempts`, {\n originalError: String(mergeErr),\n })\n }\n }\n }\n }\n }\n\n /**\n * Cleanup: remove the worktree.\n */\n async cleanup(ctx: WorktreeContext): Promise<void> {\n try {\n const git = simpleGit(this.config.repoPath)\n await git.raw(['worktree', 'remove', ctx.path, '--force'])\n }\n catch {\n // Force cleanup if git worktree remove fails\n await removeDir(ctx.path)\n }\n\n // Remove the temporary branch\n try {\n const git = simpleGit(this.config.repoPath)\n await git.raw(['branch', '-D', ctx.branch])\n }\n catch {\n // Branch may already be gone\n }\n }\n\n /**\n * Run a full transaction: worktree → execute → commit → push → cleanup.\n */\n async transaction<T>(\n fn: (worktreePath: string) => Promise<T>,\n message: string,\n ): Promise<T> {\n const ctx = await this.createWorktree()\n\n try {\n const result = await fn(ctx.path)\n\n const commitHash = await this.commit(ctx, message)\n if (commitHash) {\n await this.pushWithRetry(ctx)\n }\n\n return result\n }\n finally {\n await this.cleanup(ctx)\n }\n }\n\n /**\n * Run a staging transaction: worktree → execute → commit → push to staging branch → cleanup.\n * Changes are pushed to a separate branch instead of the target branch.\n */\n async stagingTransaction<T>(\n fn: (worktreePath: string) => Promise<T>,\n message: string,\n stagingBranch: string,\n ): Promise<{ result: T; hasChanges: boolean }> {\n const ctx = await this.createWorktree()\n\n try {\n const result = await fn(ctx.path)\n\n const commitHash = await this.commit(ctx, message)\n if (commitHash) {\n const git = simpleGit(ctx.path)\n try {\n await git.push(this.config.remote, `${ctx.branch}:${stagingBranch}`)\n }\n catch (err) {\n throw new GitSyncError(`Failed to push to staging branch \"${stagingBranch}\"`, {\n branch: stagingBranch,\n originalError: String(err),\n })\n }\n }\n\n return { result, hasChanges: !!commitHash }\n }\n finally {\n await this.cleanup(ctx)\n }\n }\n\n /**\n * Merge a remote staging branch into the target branch.\n * Tries fast-forward first, falls back to merge commit.\n */\n async mergeStagingToTarget(stagingBranch: string): Promise<string> {\n const git = simpleGit(this.config.repoPath)\n const remote = this.config.remote\n\n // Verify staging branch exists on remote\n await git.fetch(remote, stagingBranch)\n try {\n await git.raw(['rev-parse', '--verify', `${remote}/${stagingBranch}`])\n }\n catch {\n throw new GitSyncError(`Staging branch \"${stagingBranch}\" not found on remote \"${remote}\"`, {\n branch: stagingBranch,\n })\n }\n\n // Fetch target branch too\n await git.fetch(remote, this.config.branch)\n\n // Create worktree from target branch, merge staging into it\n const ctx = await this.createWorktree()\n\n try {\n const wtGit = simpleGit(ctx.path)\n\n // Try fast-forward first\n try {\n await wtGit.merge([`${remote}/${stagingBranch}`, '--ff-only'])\n }\n catch {\n await wtGit.merge([\n `${remote}/${stagingBranch}`,\n '-m', `${this.config.commitPrefix} apply doctor fix from ${stagingBranch}`,\n ])\n }\n\n // Push merged result to target branch\n await this.pushWithRetry(ctx)\n\n // Delete remote staging branch\n try {\n await git.push(remote, `:${stagingBranch}`)\n }\n catch {\n // Non-fatal\n }\n\n const log = await wtGit.log({ maxCount: 1 })\n return log.latest?.hash || ''\n }\n finally {\n await this.cleanup(ctx)\n }\n }\n\n /**\n * Delete a remote staging branch without merging.\n */\n async deleteStagingBranch(stagingBranch: string): Promise<void> {\n const git = simpleGit(this.config.repoPath)\n try {\n await git.push(this.config.remote, `:${stagingBranch}`)\n }\n catch (err) {\n throw new GitSyncError(`Failed to delete staging branch \"${stagingBranch}\"`, {\n branch: stagingBranch,\n originalError: String(err),\n })\n }\n }\n\n // ── Private ──\n\n /**\n * Resolve push conflicts via structural JSON merge.\n */\n private async resolveAndRetry(ctx: WorktreeContext): Promise<void> {\n const git = simpleGit(ctx.path)\n const remoteBranch = `${this.config.remote}/${this.config.branch}`\n\n // Fetch latest remote state\n await git.fetch(this.config.remote, this.config.branch)\n\n // Find merge base\n const mergeBase = await git.raw(['merge-base', ctx.branch, remoteBranch])\n const baseCommit = mergeBase.trim()\n\n // Get list of conflicting files (JSON files in contentrain/)\n const diffOutput = await git.raw(['diff', '--name-only', `${remoteBranch}...${ctx.branch}`])\n const changedFiles = diffOutput.trim().split('\\n').filter((f: string) => f.endsWith('.json'))\n\n // For each changed JSON file, do structural merge\n for (const file of changedFiles) {\n if (!file.includes('contentrain/') && !file.startsWith('contentrain/')) continue\n\n try {\n // Read all three versions\n const basePath = join(ctx.path, file)\n const base = await this.readGitVersion<ContentEntry[]>(git, baseCommit, file)\n const ours = await readJsonFile<ContentEntry[]>(basePath)\n const theirsRaw = await git.raw(['show', `${remoteBranch}:${file}`])\n const theirs = JSON.parse(theirsRaw) as ContentEntry[]\n\n // Structural merge\n const merged = structuralMerge(base, ours, theirs)\n await writeJsonFile(basePath, merged)\n }\n catch {\n // If structural merge fails for a file, keep our version\n }\n }\n\n // Reset to remote and apply our merged state\n await git.raw(['reset', '--soft', remoteBranch])\n await git.add('-A')\n\n const status = await git.status()\n if (status.files.length > 0) {\n await git.commit(`${this.config.commitPrefix} merge: resolve concurrent changes`)\n }\n }\n\n private async readGitVersion<T>(git: SimpleGit, commit: string, filePath: string): Promise<T> {\n try {\n const raw = await git.raw(['show', `${commit}:${filePath}`])\n return JSON.parse(raw) as T\n }\n catch {\n return [] as unknown as T\n }\n }\n}\n","import { join } from 'node:path'\nimport { mkdtemp, cp } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport type { WriterConfig } from '../types/index.js'\nimport { ModelManager } from './model/manager.js'\nimport { ContentManager } from './content/manager.js'\nimport { AssetManager } from './asset/manager.js'\nimport { Validator } from './validation/validator.js'\nimport { GitSyncManager } from './git/sync.js'\nimport { removeDir } from '../util/fs.js'\n\n/**\n * Main orchestrator — coordinates all managers and git sync.\n *\n * Three modes:\n * 1. Read mode: reads from repoPath directly (no git sync)\n * 2. Transaction mode: creates a worktree, applies changes, commits + pushes\n * 3. Dry-run mode: copies contentrain dir to temp, applies changes there, then discards\n */\nexport class ContentrainWriter {\n private config: WriterConfig\n private contentrainDir: string\n\n // Direct-mode managers (operate on repoPath — reads only)\n readonly model: ModelManager\n readonly content: ContentManager\n readonly asset: AssetManager\n readonly validator: Validator\n private gitSync: GitSyncManager\n\n constructor(config: WriterConfig) {\n this.config = config\n this.contentrainDir = config.contentrainDir || 'contentrain'\n const basePath = join(config.repoPath, this.contentrainDir)\n\n this.model = new ModelManager(basePath)\n this.content = new ContentManager(basePath, config.repoPath)\n this.asset = new AssetManager(basePath, config.repoPath, config)\n this.validator = new Validator(basePath)\n this.gitSync = new GitSyncManager(config)\n }\n\n /**\n * Execute operations in a git-synced transaction.\n * Creates worktree → runs operations → validates → commits → pushes.\n * In dry-run mode, copies to a temp directory and discards after execution.\n */\n async transaction<T>(\n fn: (managers: TransactionManagers) => Promise<T>,\n options?: { message?: string },\n ): Promise<T> {\n if (this.config.dryRun) {\n return this.dryRunTransaction(fn)\n }\n\n return this.gitSync.transaction(\n async (worktreePath: string) => {\n const managers = this.createManagers(worktreePath)\n return fn(managers)\n },\n options?.message || 'content update',\n )\n }\n\n /**\n * Execute operations and push to a staging branch instead of the target branch.\n * Returns the result and whether any changes were pushed.\n */\n async stagingTransaction<T>(\n fn: (managers: TransactionManagers) => Promise<T>,\n options: { message: string; stagingBranch: string },\n ): Promise<{ result: T; hasChanges: boolean }> {\n if (this.config.dryRun) {\n const result = await this.dryRunTransaction(fn)\n return { result, hasChanges: false }\n }\n\n return this.gitSync.stagingTransaction(\n async (worktreePath: string) => {\n const managers = this.createManagers(worktreePath)\n return fn(managers)\n },\n options.message,\n options.stagingBranch,\n )\n }\n\n /**\n * Merge a staging branch into the target branch.\n */\n async applyStagingBranch(stagingBranch: string): Promise<string> {\n return this.gitSync.mergeStagingToTarget(stagingBranch)\n }\n\n /**\n * Delete a staging branch without merging.\n */\n async discardStagingBranch(stagingBranch: string): Promise<void> {\n return this.gitSync.deleteStagingBranch(stagingBranch)\n }\n\n /**\n * Dry-run: copy contentrain dir to temp, run operations there, then discard.\n * The original repository is never modified.\n */\n private async dryRunTransaction<T>(\n fn: (managers: TransactionManagers) => Promise<T>,\n ): Promise<T> {\n const tempRepoPath = await mkdtemp(join(tmpdir(), 'contentrain-dry-'))\n const tempContentrainDir = join(tempRepoPath, this.contentrainDir)\n const sourceContentrainDir = join(this.config.repoPath, this.contentrainDir)\n\n try {\n await cp(sourceContentrainDir, tempContentrainDir, { recursive: true })\n const managers = this.createManagers(tempRepoPath)\n return await fn(managers)\n }\n finally {\n await removeDir(tempRepoPath)\n }\n }\n\n /**\n * Create managers for a specific repo path (used in transactions).\n */\n private createManagers(repoPath: string): TransactionManagers {\n const basePath = join(repoPath, this.contentrainDir)\n return {\n basePath,\n repoPath,\n model: new ModelManager(basePath),\n content: new ContentManager(basePath, repoPath),\n asset: new AssetManager(basePath, repoPath, this.config),\n validator: new Validator(basePath),\n }\n }\n}\n\nexport interface TransactionManagers {\n basePath: string\n repoPath: string\n model: ModelManager\n content: ContentManager\n asset: AssetManager\n validator: Validator\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainWriter } from '../core/writer.js'\n\nexport function registerModelTools(server: McpServer, writer: ContentrainWriter): void {\n // ── List Models ──\n server.registerTool(\n 'contentrain_list_models',\n {\n description: 'List all Contentrain models with their metadata (type, localization, etc.)',\n },\n async () => {\n try {\n const models = await writer.model.list()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(models, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Describe Model ──\n server.registerTool(\n 'contentrain_describe_model',\n {\n description: 'Get detailed schema for a model including all field definitions, types, validations, and options. Call this BEFORE creating or updating content to understand required fields.',\n inputSchema: {\n modelId: z.string().describe('The model ID to describe'),\n },\n },\n async ({ modelId }) => {\n try {\n const desc = await writer.model.describe(modelId)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(desc, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Create Model ──\n server.registerTool(\n 'contentrain_create_model',\n {\n description: 'Create a new Contentrain model with field definitions. System fields (ID, createdAt, updatedAt, status) are auto-added. For MD/MDX models, slug and content fields are also auto-added.',\n inputSchema: {\n name: z.string().describe('Display name of the model'),\n modelId: z.string().regex(/^[a-z0-9-]+$/).describe('Unique model ID in kebab-case'),\n type: z.enum(['JSON', 'MD', 'MDX']).describe('Content type'),\n localization: z.boolean().describe('Whether the model supports multiple languages'),\n description: z.string().optional().describe('Model description'),\n path: z.string().optional().describe('Base path for MD/MDX files'),\n fields: z.array(z.object({\n name: z.string(),\n fieldId: z.string(),\n componentId: z.string(),\n fieldType: z.string(),\n options: z.record(z.unknown()).optional(),\n validations: z.record(z.unknown()).optional(),\n })).optional().describe('Custom field definitions (system fields are auto-added)'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n return tx.model.create({\n name: params.name,\n modelId: params.modelId,\n type: params.type,\n localization: params.localization,\n description: params.description,\n path: params.path,\n fields: params.fields as any,\n })\n }, { message: `create model: ${params.modelId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, model: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Add Field ──\n server.registerTool(\n 'contentrain_add_field',\n {\n description: 'Add a new field to an existing model.',\n inputSchema: {\n modelId: z.string().describe('Target model ID'),\n name: z.string().describe('Display name of the field'),\n fieldId: z.string().describe('Unique field identifier'),\n componentId: z.string().describe('UI component (single-line-text, integer, one-to-one, media, etc.)'),\n fieldType: z.string().describe('Data type (string, number, boolean, relation, media, date, array)'),\n options: z.record(z.unknown()).optional().describe('Field options (title-field, default-value, reference, etc.)'),\n validations: z.record(z.unknown()).optional().describe('Field validations (required-field, unique-field)'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n return tx.model.addField(params.modelId, {\n name: params.name,\n fieldId: params.fieldId,\n componentId: params.componentId as any,\n fieldType: params.fieldType as any,\n options: (params.options || {}) as any,\n validations: (params.validations || {}) as any,\n })\n }, { message: `add field ${params.fieldId} to ${params.modelId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, field: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Delete Model ──\n server.registerTool(\n 'contentrain_delete_model',\n {\n description: 'Delete a model and all its content. WARNING: This is destructive and cannot be undone.',\n annotations: { destructiveHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID to delete'),\n },\n },\n async ({ modelId }) => {\n try {\n await writer.transaction(async (tx) => {\n await tx.model.delete(modelId)\n }, { message: `delete model: ${modelId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, deleted: modelId }) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainWriter } from '../core/writer.js'\n\nexport function registerContentTools(server: McpServer, writer: ContentrainWriter): void {\n // ── List Content ──\n server.registerTool(\n 'contentrain_list_content',\n {\n description: 'List all content entries for a model. For localized models, specify a locale.',\n annotations: { readOnlyHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID to list content from'),\n locale: z.string().optional().describe('Locale code (e.g., \"en\", \"tr\") for localized models'),\n },\n },\n async ({ modelId, locale }) => {\n try {\n const entries = await writer.content.list(modelId, locale)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ total: entries.length, entries }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Get Content ──\n server.registerTool(\n 'contentrain_get_content',\n {\n description: 'Get a single content entry by ID.',\n annotations: { readOnlyHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID'),\n entryId: z.string().describe('Entry ID (12-char hex)'),\n locale: z.string().optional().describe('Locale code for localized models'),\n },\n },\n async ({ modelId, entryId, locale }) => {\n try {\n const entry = await writer.content.get(modelId, entryId, locale)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(entry, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Create Content ──\n server.registerTool(\n 'contentrain_create_content',\n {\n description: 'Create a new content entry. System fields (ID, createdAt, updatedAt, status) are auto-generated. For localized models, specify locale to create in that language. Call contentrain_describe_model first to know required fields.',\n inputSchema: {\n modelId: z.string().describe('Target model ID'),\n data: z.record(z.unknown()).describe('Content data object with field values matching model schema'),\n locale: z.string().optional().describe('Locale code (e.g., \"en\") — required for localized models'),\n status: z.enum(['draft', 'publish']).optional().describe('Entry status (default: \"draft\")'),\n content: z.string().optional().describe('Markdown body content (only for MD/MDX models)'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n const validation = await tx.validator.validate(params.modelId, params.data as Record<string, unknown>)\n if (!validation.valid) {\n throw new Error(`Validation failed:\\n${validation.errors.map(e => ` - ${e.fieldId}: ${e.message}`).join('\\n')}`)\n }\n\n return tx.content.create(params.modelId, params.data as Record<string, unknown>, {\n locale: params.locale,\n status: params.status,\n content: params.content,\n })\n }, { message: `create ${params.modelId} entry` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, id: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Update Content ──\n server.registerTool(\n 'contentrain_update_content',\n {\n description: 'Update an existing content entry. Only provide fields you want to change. updatedAt is auto-set.',\n inputSchema: {\n modelId: z.string().describe('Model ID'),\n entryId: z.string().describe('Entry ID to update (12-char hex)'),\n data: z.record(z.unknown()).describe('Fields to update'),\n locale: z.string().optional().describe('Locale code for localized models'),\n content: z.string().optional().describe('Updated markdown body (only for MD/MDX models)'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n const validation = await tx.validator.validate(\n params.modelId,\n params.data as Record<string, unknown>,\n { isUpdate: true },\n )\n if (!validation.valid) {\n throw new Error(`Validation failed:\\n${validation.errors.map(e => ` - ${e.fieldId}: ${e.message}`).join('\\n')}`)\n }\n\n return tx.content.update(params.modelId, params.entryId, params.data as Record<string, unknown>, {\n locale: params.locale,\n content: params.content,\n })\n }, { message: `update ${params.modelId}/${params.entryId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, entry: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Delete Content ──\n server.registerTool(\n 'contentrain_delete_content',\n {\n description: 'Delete a content entry by ID. For localized models, removes from all locale files. For MD/MDX, also deletes markdown files.',\n annotations: { destructiveHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID'),\n entryId: z.string().describe('Entry ID to delete (12-char hex)'),\n },\n },\n async ({ modelId, entryId }) => {\n try {\n await writer.transaction(async (tx) => {\n await tx.content.delete(modelId, entryId)\n }, { message: `delete ${modelId}/${entryId}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, deleted: entryId }) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Validate Content ──\n server.registerTool(\n 'contentrain_validate',\n {\n description: 'Validate content data against model schema without writing. Use for dry-run checks.',\n annotations: { readOnlyHint: true },\n inputSchema: {\n modelId: z.string().describe('Model ID to validate against'),\n data: z.record(z.unknown()).describe('Content data to validate'),\n },\n },\n async ({ modelId, data }) => {\n try {\n const result = await writer.validator.validate(modelId, data as Record<string, unknown>)\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainWriter } from '../core/writer.js'\n\nexport function registerAssetTools(server: McpServer, writer: ContentrainWriter): void {\n // ── List Assets ──\n server.registerTool(\n 'contentrain_list_assets',\n {\n description: 'List all registered assets in assets.json.',\n annotations: { readOnlyHint: true },\n },\n async () => {\n try {\n const assets = await writer.asset.list()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ total: assets.length, assets }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Register Asset ──\n server.registerTool(\n 'contentrain_register_asset',\n {\n description: 'Register a new asset in assets.json. The file must already exist at the specified path in the repository.',\n inputSchema: {\n path: z.string().describe('Relative path to the asset file (e.g., \"contentrain/static/hero.png\")'),\n mimetype: z.string().describe('MIME type (e.g., \"image/png\", \"image/jpeg\", \"application/pdf\")'),\n alt: z.string().optional().describe('Alt text for the asset'),\n },\n },\n async (params) => {\n try {\n const result = await writer.transaction(async (tx) => {\n return tx.asset.register({\n path: params.path,\n mimetype: params.mimetype,\n alt: params.alt,\n })\n }, { message: `register asset: ${params.path}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, asset: result }, null, 2) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n // ── Deregister Asset ──\n server.registerTool(\n 'contentrain_deregister_asset',\n {\n description: 'Remove an asset from assets.json registry. Does NOT delete the physical file.',\n annotations: { destructiveHint: true },\n inputSchema: {\n path: z.string().describe('Asset path to deregister'),\n },\n },\n async ({ path }) => {\n try {\n await writer.transaction(async (tx) => {\n await tx.asset.deregister(path)\n }, { message: `deregister asset: ${path}` })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: true, deregistered: path }) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n}\n","import { join } from 'node:path'\nimport { readFile } from 'node:fs/promises'\nimport type {\n ModelMetadata,\n FieldDefinition,\n ContentEntry,\n AssetEntry,\n MDContentEntry,\n} from '../types/index.js'\nimport { readJsonFile, writeJsonFile } from '../util/json.js'\nimport { listJsonFiles, fileExists } from '../util/fs.js'\nimport { isValidId, generateId } from '../util/id.js'\nimport { getExpectedType, validateFieldValue, validateInteger } from './validation/rules.js'\n\n// ── Types ──\n\nexport type IssueSeverity = 'error' | 'warning'\n\nexport type IssueCategory =\n | 'structure'\n | 'id'\n | 'schema'\n | 'relation'\n | 'asset'\n | 'sync'\n\nexport interface DoctorIssue {\n severity: IssueSeverity\n category: IssueCategory\n modelId?: string\n entryId?: string\n field?: string\n file?: string\n message: string\n fix?: string\n}\n\nexport interface DoctorResult {\n summary: {\n total: number\n errors: number\n warnings: number\n fixed: number\n models: number\n entries: number\n }\n issues: DoctorIssue[]\n}\n\nexport interface DoctorOptions {\n fix?: boolean\n modelId?: string\n}\n\n// Internal tracking for loaded content files\ninterface ContentFile {\n modelId: string\n fileName: string\n filePath: string\n entries: ContentEntry[]\n dirty: boolean\n}\n\n// ── Doctor Engine ──\n\nexport class Doctor {\n private issues: DoctorIssue[] = []\n private fixed = 0\n private totalEntries = 0\n private scannedEntries = 0\n\n // ID remap: oldId → newId (for relation propagation and locale consistency)\n private idRemap = new Map<string, string>()\n\n // All loaded content files (for cross-model relation propagation)\n private contentFiles: ContentFile[] = []\n\n // All field definitions by modelId (for relation field lookup)\n private fieldsByModel = new Map<string, FieldDefinition[]>()\n\n constructor(\n private basePath: string,\n private repoPath: string,\n ) {}\n\n async diagnose(options: DoctorOptions = {}): Promise<DoctorResult> {\n this.issues = []\n this.fixed = 0\n this.totalEntries = 0\n this.scannedEntries = 0\n this.idRemap = new Map()\n this.contentFiles = []\n this.fieldsByModel = new Map()\n\n const fix = options.fix ?? false\n\n // 1. Check metadata.json is valid\n const models = await this.checkMetadata()\n if (!models) {\n return this.buildResult(0)\n }\n\n // 2. Filter to specific model if requested\n const targetModels = options.modelId\n ? models.filter(m => m.modelId === options.modelId)\n : models\n\n if (options.modelId && targetModels.length === 0) {\n this.addIssue('error', 'structure', `Model \"${options.modelId}\" not found in metadata.json`)\n return this.buildResult(models.length)\n }\n\n // 3. Check structure: field files exist for all models\n await this.checkFieldFiles(models, targetModels)\n\n // 4. Load assets once for media checks\n const assets = await this.loadAssets()\n\n // 5. Load ALL content files upfront (needed for relation propagation in fix mode)\n await this.loadAllContentFiles(fix ? models : targetModels)\n\n // 6. Load ALL field definitions (needed for relation propagation)\n for (const model of models) {\n try {\n const fields = await readJsonFile<FieldDefinition[]>(join(this.basePath, 'models', `${model.modelId}.json`))\n this.fieldsByModel.set(model.modelId, fields)\n }\n catch {\n // Already reported in checkFieldFiles\n }\n }\n\n // 7. Count scanned entries (only target models)\n for (const cf of this.contentFiles) {\n if (targetModels.some(m => m.modelId === cf.modelId)) {\n this.scannedEntries += cf.entries.length\n }\n }\n\n // 8. For each target model, run all checks (collects ID remaps but doesn't write yet)\n for (const model of targetModels) {\n this.checkModelStructure(model)\n this.checkModelContent(model, assets, fix)\n // MD/MDX sync needs async file checks\n if (model.type === 'MD' || model.type === 'MDX') {\n const modelFiles = this.contentFiles.filter(f => f.modelId === model.modelId)\n await this.checkMarkdownSync(model, modelFiles)\n }\n }\n\n // 9. Check orphan content directories\n if (!options.modelId) {\n await this.checkOrphanDirectories(models)\n }\n\n // 10. Propagate ID changes to all relation fields across ALL models\n if (fix && this.idRemap.size > 0) {\n this.propagateIdChanges()\n }\n\n // 11. Write all dirty content files\n if (fix) {\n await this.writeDirtyFiles()\n }\n\n return this.buildResult(targetModels.length)\n }\n\n // ── Content Loading ──\n\n private async loadAllContentFiles(models: ModelMetadata[]): Promise<void> {\n for (const model of models) {\n const contentDir = join(this.basePath, model.modelId)\n const fileNames = await listJsonFiles(contentDir)\n\n for (const fileName of fileNames) {\n const filePath = join(contentDir, `${fileName}.json`)\n let entries: ContentEntry[]\n try {\n const raw = await readFile(filePath, 'utf-8')\n entries = JSON.parse(raw) as ContentEntry[]\n if (!Array.isArray(entries)) {\n this.addIssue('error', 'structure', 'Content file is not an array', model.modelId, undefined, undefined, `${model.modelId}/${fileName}.json`)\n continue\n }\n }\n catch {\n this.addIssue('error', 'structure', 'Invalid JSON in content file', model.modelId, undefined, undefined, `${model.modelId}/${fileName}.json`)\n continue\n }\n\n this.totalEntries += entries.length\n this.contentFiles.push({\n modelId: model.modelId,\n fileName,\n filePath,\n entries,\n dirty: false,\n })\n }\n }\n }\n\n // ── Structural Checks ──\n\n private async checkMetadata(): Promise<ModelMetadata[] | null> {\n const metadataPath = join(this.basePath, 'models', 'metadata.json')\n try {\n const raw = await readFile(metadataPath, 'utf-8')\n try {\n const models = JSON.parse(raw) as ModelMetadata[]\n if (!Array.isArray(models)) {\n this.addIssue('error', 'structure', 'metadata.json is not an array', undefined, undefined, undefined, 'models/metadata.json')\n return null\n }\n\n // Check for duplicate modelIds in metadata\n const seen = new Map<string, number>()\n for (const m of models) {\n const count = (seen.get(m.modelId) ?? 0) + 1\n seen.set(m.modelId, count)\n }\n for (const [modelId, count] of seen) {\n if (count > 1) {\n this.addIssue('error', 'structure', `Duplicate modelId \"${modelId}\" in metadata.json (${count} times)`, modelId, undefined, undefined, 'models/metadata.json')\n }\n }\n\n return models\n }\n catch {\n this.addIssue('error', 'structure', 'metadata.json contains invalid JSON', undefined, undefined, undefined, 'models/metadata.json')\n return null\n }\n }\n catch {\n this.addIssue('error', 'structure', 'metadata.json not found — is this a Contentrain project?', undefined, undefined, undefined, 'models/metadata.json')\n return null\n }\n }\n\n private async checkFieldFiles(allModels: ModelMetadata[], targetModels: ModelMetadata[]): Promise<void> {\n for (const model of targetModels) {\n const fieldPath = join(this.basePath, 'models', `${model.modelId}.json`)\n if (!await fileExists(fieldPath)) {\n this.addIssue('error', 'structure', `Field definition file missing for model \"${model.modelId}\"`, model.modelId, undefined, undefined, `models/${model.modelId}.json`)\n }\n }\n\n // Check for field files without metadata entry\n const metaIds = new Set(allModels.map(m => m.modelId))\n const fieldFiles = await listJsonFiles(join(this.basePath, 'models'))\n for (const file of fieldFiles) {\n if (file === 'metadata') continue\n if (!metaIds.has(file)) {\n this.addIssue('warning', 'structure', `Field definition file \"${file}.json\" has no matching model in metadata.json`, file, undefined, undefined, `models/${file}.json`)\n }\n }\n }\n\n private async checkOrphanDirectories(models: ModelMetadata[]): Promise<void> {\n const modelIds = new Set(models.map(m => m.modelId))\n const { readdir, stat } = await import('node:fs/promises')\n\n try {\n const entries = await readdir(this.basePath)\n for (const entry of entries) {\n if (entry === 'models' || entry === 'assets.json' || entry === 'static' || entry.startsWith('.')) continue\n const fullPath = join(this.basePath, entry)\n const s = await stat(fullPath)\n if (s.isDirectory() && !modelIds.has(entry)) {\n this.addIssue('warning', 'structure', `Content directory \"${entry}/\" has no matching model in metadata.json`, entry)\n }\n }\n }\n catch {\n // basePath doesn't exist or can't be read\n }\n }\n\n // ── Model Checks ──\n\n private checkModelStructure(model: ModelMetadata): void {\n const fields = this.fieldsByModel.get(model.modelId)\n if (!fields) return\n\n // Check system fields exist in field definition\n const requiredSystemIds = ['createdAt', 'updatedAt', 'ID', 'status']\n for (const sysId of requiredSystemIds) {\n if (!fields.some(f => f.fieldId === sysId && f.system)) {\n this.addIssue('error', 'schema', `Missing system field \"${sysId}\" in field definitions`, model.modelId, undefined, undefined, `models/${model.modelId}.json`)\n }\n }\n\n if (model.type === 'MD' || model.type === 'MDX') {\n if (!fields.some(f => f.fieldId === 'slug')) {\n this.addIssue('error', 'schema', 'MD/MDX model missing required \"slug\" field', model.modelId, undefined, undefined, `models/${model.modelId}.json`)\n }\n if (!fields.some(f => f.fieldId === 'content')) {\n this.addIssue('warning', 'schema', 'MD/MDX model missing \"content\" field definition', model.modelId, undefined, undefined, `models/${model.modelId}.json`)\n }\n }\n }\n\n private checkModelContent(model: ModelMetadata, assets: AssetEntry[], fix: boolean): void {\n const fields = this.fieldsByModel.get(model.modelId)\n if (!fields) return\n\n const modelFiles = this.contentFiles.filter(f => f.modelId === model.modelId)\n\n if (modelFiles.length === 0 && !model.localization) {\n this.addIssue('warning', 'structure', `No content files found for model \"${model.modelId}\"`, model.modelId)\n return\n }\n\n for (const cf of modelFiles) {\n // Duplicate ID detection within same file\n const fileIdSet = new Map<string, number>()\n for (const entry of cf.entries) {\n const count = (fileIdSet.get(entry.ID) ?? 0) + 1\n fileIdSet.set(entry.ID, count)\n }\n\n for (const [id, count] of fileIdSet) {\n if (count > 1) {\n this.addIssue('error', 'id', `Duplicate ID \"${id}\" appears ${count} times in same file`, model.modelId, id, undefined, `${model.modelId}/${cf.fileName}.json`,\n fix ? 'Will regenerate duplicate IDs and update relations' : undefined)\n\n if (fix) {\n this.fixDuplicateIds(cf, id)\n }\n }\n }\n\n // Check each entry\n for (const entry of cf.entries) {\n this.checkEntry(model, fields, entry, assets, `${model.modelId}/${cf.fileName}.json`, fix, cf)\n }\n\n // Check unique constraints\n this.checkUniqueConstraints(model, fields, cf.entries, `${model.modelId}/${cf.fileName}.json`)\n }\n\n // Localized model: verify ID consistency across locale files\n if (model.localization && modelFiles.length > 1) {\n this.checkLocaleIdConsistency(model, modelFiles)\n }\n }\n\n // ── Entry-Level Checks ──\n\n private checkEntry(\n model: ModelMetadata,\n fields: FieldDefinition[],\n entry: ContentEntry,\n assets: AssetEntry[],\n file: string,\n fix: boolean,\n cf: ContentFile,\n ): void {\n const entryId = entry.ID\n\n // ID format\n if (!entryId || typeof entryId !== 'string') {\n this.addIssue('error', 'id', 'Entry missing ID field', model.modelId, undefined, 'ID', file)\n return\n }\n if (!isValidId(entryId)) {\n // Reuse existing remap for locale consistency: same old ID → same new ID across all locale files\n const existingNewId = this.idRemap.get(entryId)\n const newId = fix ? (existingNewId || generateId()) : undefined\n this.addIssue('error', 'id', `Invalid ID format \"${entryId}\" — must be 12-char lowercase hex`, model.modelId, entryId, 'ID', file,\n fix ? `Regenerated to \"${newId}\" and updating all relations` : undefined)\n if (fix && newId) {\n this.idRemap.set(entryId, newId)\n entry.ID = newId\n cf.dirty = true\n if (!existingNewId) this.fixed++ // only count once per unique old ID\n }\n }\n\n // System fields presence\n if (!entry.createdAt) {\n this.addIssue('error', 'schema', 'Missing createdAt', model.modelId, entryId, 'createdAt', file,\n fix ? 'Will set to current timestamp' : undefined)\n if (fix) {\n entry.createdAt = new Date().toISOString()\n cf.dirty = true\n this.fixed++\n }\n }\n if (!entry.updatedAt) {\n this.addIssue('error', 'schema', 'Missing updatedAt', model.modelId, entryId, 'updatedAt', file,\n fix ? 'Will set to current timestamp' : undefined)\n if (fix) {\n entry.updatedAt = new Date().toISOString()\n cf.dirty = true\n this.fixed++\n }\n }\n if (!entry.status) {\n this.addIssue('error', 'schema', 'Missing status', model.modelId, entryId, 'status', file,\n fix ? 'Will set to \"draft\"' : undefined)\n if (fix) {\n entry.status = 'draft'\n cf.dirty = true\n this.fixed++\n }\n }\n else if (!['draft', 'publish', 'changed'].includes(entry.status)) {\n this.addIssue('error', 'schema', `Invalid status \"${entry.status}\" — must be draft, publish, or changed`, model.modelId, entryId, 'status', file,\n fix ? 'Will set to \"draft\"' : undefined)\n if (fix) {\n entry.status = 'draft'\n cf.dirty = true\n this.fixed++\n }\n }\n\n // Validate createdAt/updatedAt are valid ISO dates\n if (entry.createdAt && isNaN(Date.parse(entry.createdAt))) {\n this.addIssue('error', 'schema', `Invalid createdAt date format \"${entry.createdAt}\"`, model.modelId, entryId, 'createdAt', file)\n }\n if (entry.updatedAt && isNaN(Date.parse(entry.updatedAt))) {\n this.addIssue('error', 'schema', `Invalid updatedAt date format \"${entry.updatedAt}\"`, model.modelId, entryId, 'updatedAt', file)\n }\n\n // Field-level checks (non-system fields)\n const nonSystemFields = fields.filter(f => !f.system)\n for (const field of nonSystemFields) {\n const value = entry[field.fieldId]\n\n // Required check\n if (field.validations?.['required-field']?.value) {\n if (value === undefined || value === null || value === '') {\n if (field.fieldId === 'content' && (model.type === 'MD' || model.type === 'MDX')) continue\n this.addIssue('error', 'schema', `Required field \"${field.fieldId}\" is empty`, model.modelId, entryId, field.fieldId, file)\n continue\n }\n }\n\n if (value === undefined || value === null) continue\n\n // Type check\n const expectedType = getExpectedType(field.componentId, field.fieldType)\n const typeError = validateFieldValue(value, expectedType, field.fieldId)\n if (typeError) {\n this.addIssue('error', 'schema', `Type mismatch: ${typeError}`, model.modelId, entryId, field.fieldId, file)\n continue\n }\n\n // Integer check\n if (field.componentId === 'integer') {\n const intError = validateInteger(value, field.fieldId)\n if (intError) {\n this.addIssue('error', 'schema', intError, model.modelId, entryId, field.fieldId, file)\n }\n }\n\n // Relation integrity\n if (field.componentId === 'one-to-one' && typeof value === 'string') {\n this.checkRelation(model.modelId, entryId, field, value, file)\n }\n if (field.componentId === 'one-to-many' && Array.isArray(value)) {\n for (const refId of value) {\n if (typeof refId === 'string') {\n this.checkRelation(model.modelId, entryId, field, refId, file)\n }\n else {\n this.addIssue('error', 'relation', 'one-to-many array contains non-string value', model.modelId, entryId, field.fieldId, file)\n }\n }\n }\n\n // Relation field missing reference config\n if ((field.componentId === 'one-to-one' || field.componentId === 'one-to-many') && !field.options?.reference?.form?.reference?.value) {\n this.addIssue('warning', 'relation', `Relation field \"${field.fieldId}\" missing options.reference.form.reference.value`, model.modelId, undefined, field.fieldId, `models/${model.modelId}.json`)\n }\n\n // Asset check\n if (field.componentId === 'media' && typeof value === 'string' && value !== '') {\n if (!assets.some(a => a.path === value)) {\n this.addIssue('error', 'asset', `Media path \"${value}\" not found in assets.json`, model.modelId, entryId, field.fieldId, file)\n }\n }\n }\n }\n\n private checkRelation(\n modelId: string,\n entryId: string,\n field: FieldDefinition,\n refId: string,\n file: string,\n ): void {\n const refModelId = field.options?.reference?.form?.reference?.value\n if (!refModelId) return\n\n // Validate referenced ID format\n if (!isValidId(refId)) {\n this.addIssue('error', 'relation', `Relation references invalid ID \"${refId}\"`, modelId, entryId, field.fieldId, file)\n return\n }\n\n // Check referenced entry exists in loaded content\n const refFiles = this.contentFiles.filter(f => f.modelId === refModelId)\n const found = refFiles.some(rf => rf.entries.some(e => e.ID === refId))\n\n if (!found) {\n this.addIssue('error', 'relation', `Referenced entry \"${refId}\" not found in model \"${refModelId}\"`, modelId, entryId, field.fieldId, file)\n }\n }\n\n // ── Cross-Entry Checks ──\n\n private checkUniqueConstraints(\n model: ModelMetadata,\n fields: FieldDefinition[],\n entries: ContentEntry[],\n file: string,\n ): void {\n const uniqueFields = fields.filter(f => f.validations?.['unique-field']?.value && !f.system)\n for (const field of uniqueFields) {\n const seen = new Map<unknown, string>()\n for (const entry of entries) {\n const value = entry[field.fieldId]\n if (value === undefined || value === null || value === '') continue\n const key = typeof value === 'object' ? JSON.stringify(value) : value\n const existing = seen.get(key)\n if (existing) {\n this.addIssue('error', 'schema', `Unique constraint violated: \"${field.fieldId}\" value \"${value}\" duplicated between entries ${existing} and ${entry.ID}`, model.modelId, entry.ID, field.fieldId, file)\n }\n else {\n seen.set(key, entry.ID)\n }\n }\n }\n }\n\n private checkLocaleIdConsistency(\n model: ModelMetadata,\n modelFiles: ContentFile[],\n ): void {\n if (modelFiles.length < 2) return\n\n const referenceFile = modelFiles[0]\n const referenceIds = new Set(referenceFile.entries.map(e => e.ID))\n\n for (let i = 1; i < modelFiles.length; i++) {\n const otherFile = modelFiles[i]\n const otherIds = new Set(otherFile.entries.map(e => e.ID))\n\n for (const id of referenceIds) {\n if (!otherIds.has(id)) {\n this.addIssue('error', 'id', `Entry \"${id}\" exists in ${referenceFile.fileName}.json but missing in ${otherFile.fileName}.json`, model.modelId, id, undefined, `${model.modelId}/${otherFile.fileName}.json`)\n }\n }\n\n for (const id of otherIds) {\n if (!referenceIds.has(id)) {\n this.addIssue('error', 'id', `Entry \"${id}\" exists in ${otherFile.fileName}.json but missing in ${referenceFile.fileName}.json`, model.modelId, id, undefined, `${model.modelId}/${referenceFile.fileName}.json`)\n }\n }\n }\n }\n\n // ── MD/MDX Sync ──\n\n private async checkMarkdownSync(model: ModelMetadata, modelFiles: ContentFile[]): Promise<void> {\n for (const cf of modelFiles) {\n const isLocalized = model.localization && cf.fileName !== model.modelId\n const locale = isLocalized ? cf.fileName : undefined\n\n for (const entry of cf.entries) {\n if (!('slug' in entry) || !entry.slug) {\n this.addIssue('error', 'sync', 'MD/MDX entry missing \"slug\" field', model.modelId, entry.ID, 'slug', `${model.modelId}/${cf.fileName}.json`)\n continue\n }\n\n const mdEntry = entry as MDContentEntry\n const ext = model.type === 'MDX' ? '.mdx' : '.md'\n const baseMdPath = mdEntry.path || model.path || ''\n const mdPath = locale\n ? join(this.repoPath, baseMdPath, locale, `${mdEntry.slug}${ext}`)\n : join(this.repoPath, baseMdPath, `${mdEntry.slug}${ext}`)\n\n if (!await fileExists(mdPath)) {\n this.addIssue('error', 'sync', `Markdown file missing for entry \"${mdEntry.slug}\"`, model.modelId, entry.ID, undefined, mdPath.replace(this.repoPath + '/', ''))\n }\n\n // Check content body is NOT stored in JSON index\n if ('content' in entry && entry.content && typeof entry.content === 'string' && (entry.content as string).length > 0) {\n this.addIssue('warning', 'sync', 'MD/MDX JSON index should not contain \"content\" body — body belongs in markdown file', model.modelId, entry.ID, 'content', `${model.modelId}/${cf.fileName}.json`)\n }\n }\n }\n }\n\n // ── Asset Checks ──\n\n private async loadAssets(): Promise<AssetEntry[]> {\n try {\n return await readJsonFile<AssetEntry[]>(join(this.basePath, 'assets.json'))\n }\n catch {\n this.addIssue('warning', 'structure', 'assets.json not found or invalid')\n return []\n }\n }\n\n // ── Fix: ID Propagation ──\n\n /**\n * After all ID fixes are collected in idRemap, propagate changes\n * to ALL relation fields across ALL loaded content files.\n *\n * This is the critical two-way binding: when entry A's ID changes,\n * every relation field in every model that points to A must be updated.\n */\n private propagateIdChanges(): void {\n if (this.idRemap.size === 0) return\n\n for (const cf of this.contentFiles) {\n const fields = this.fieldsByModel.get(cf.modelId)\n if (!fields) continue\n\n // Find relation fields in this model\n const relationFields = fields.filter(f =>\n f.componentId === 'one-to-one' || f.componentId === 'one-to-many',\n )\n if (relationFields.length === 0) continue\n\n for (const entry of cf.entries) {\n for (const field of relationFields) {\n const value = entry[field.fieldId]\n if (value === undefined || value === null) continue\n\n if (field.componentId === 'one-to-one' && typeof value === 'string') {\n const newId = this.idRemap.get(value)\n if (newId) {\n this.addIssue('warning', 'relation',\n `Updated relation \"${field.fieldId}\": \"${value}\" → \"${newId}\"`,\n cf.modelId, entry.ID, field.fieldId, `${cf.modelId}/${cf.fileName}.json`,\n 'Propagated ID change to relation')\n entry[field.fieldId] = newId\n cf.dirty = true\n this.fixed++\n }\n }\n\n if (field.componentId === 'one-to-many' && Array.isArray(value)) {\n let changed = false\n const updated = (value as string[]).map(refId => {\n const newId = this.idRemap.get(refId)\n if (newId) {\n changed = true\n return newId\n }\n return refId\n })\n if (changed) {\n this.addIssue('warning', 'relation',\n `Updated one-to-many relation \"${field.fieldId}\": propagated ${this.idRemap.size} ID change(s)`,\n cf.modelId, entry.ID, field.fieldId, `${cf.modelId}/${cf.fileName}.json`,\n 'Propagated ID changes to relation array')\n entry[field.fieldId] = updated\n cf.dirty = true\n this.fixed++\n }\n }\n }\n }\n }\n }\n\n // ── Fix: Duplicate IDs ──\n\n private fixDuplicateIds(cf: ContentFile, duplicateId: string): void {\n let first = true\n for (const entry of cf.entries) {\n if (entry.ID === duplicateId) {\n if (first) {\n first = false\n continue\n }\n const newId = generateId()\n this.idRemap.set(`${duplicateId}:dup:${newId}`, newId) // track but don't remap the original\n entry.ID = newId\n cf.dirty = true\n this.fixed++\n }\n }\n }\n\n // ── Write Back ──\n\n private async writeDirtyFiles(): Promise<void> {\n for (const cf of this.contentFiles) {\n if (cf.dirty) {\n await writeJsonFile(cf.filePath, cf.entries)\n }\n }\n }\n\n // ── Utilities ──\n\n private addIssue(\n severity: IssueSeverity,\n category: IssueCategory,\n message: string,\n modelId?: string,\n entryId?: string,\n field?: string,\n file?: string,\n fix?: string,\n ): void {\n this.issues.push({ severity, category, modelId, entryId, field, file, message, fix })\n }\n\n private buildResult(modelCount: number): DoctorResult {\n // Issues with a `fix` descriptor were repaired — don't count them as errors\n const unfixedErrors = this.issues.filter(i => i.severity === 'error' && !i.fix).length\n return {\n summary: {\n total: this.issues.length,\n errors: unfixedErrors,\n warnings: this.issues.filter(i => i.severity === 'warning').length,\n fixed: this.fixed,\n models: modelCount,\n entries: this.scannedEntries,\n },\n issues: this.issues,\n }\n }\n}\n","import { z } from 'zod'\nimport { join } from 'node:path'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainWriter } from '../core/writer.js'\nimport type { WriterConfig } from '../types/index.js'\nimport { Doctor, type DoctorResult } from '../core/doctor.js'\n\nexport function registerDoctorTools(server: McpServer, writer: ContentrainWriter, config: WriterConfig): void {\n const targetBranch = config.branch || 'contentrain'\n const stagingPrefix = `${targetBranch}--doctor-fix/`\n\n function isStagingBranch(branch: string): boolean {\n return branch.startsWith(stagingPrefix)\n }\n\n server.registerTool(\n 'contentrain_doctor',\n {\n description: [\n 'Run a full diagnostic scan on all Contentrain models, content, and assets.',\n 'Detects: duplicate/invalid IDs, type mismatches, broken relations, missing assets,',\n 'locale ID inconsistencies, orphan directories, MD/MDX sync issues, unique constraint',\n 'violations, missing system fields, and invalid JSON files.',\n 'Use fix=true to auto-repair fixable issues — changes are pushed to a staging branch',\n 'for review. Use contentrain_doctor_apply to merge the staging branch into the target.',\n 'Use modelId to scope the scan to a single model.',\n ].join(' '),\n annotations: { readOnlyHint: false },\n inputSchema: {\n fix: z.boolean().optional().default(false).describe('Auto-fix repairable issues (duplicate IDs, missing timestamps, invalid status). Pushes to a staging branch for review.'),\n modelId: z.string().optional().describe('Scope scan to a specific model ID. Omit to scan all models.'),\n },\n },\n async ({ fix, modelId }) => {\n try {\n if (fix) {\n const stagingBranch = `${stagingPrefix}${Date.now()}`\n const { result, hasChanges } = await writer.stagingTransaction(async (tx) => {\n const doctor = new Doctor(tx.basePath, tx.repoPath)\n return doctor.diagnose({ fix: true, modelId })\n }, {\n message: `doctor fix${modelId ? ` ${modelId}` : ''}`,\n stagingBranch,\n })\n\n return {\n content: [{\n type: 'text' as const,\n text: formatResult(result, hasChanges ? stagingBranch : undefined),\n }],\n }\n }\n\n // Read-only mode: scan directly\n const contentrainDir = config.contentrainDir || 'contentrain'\n const basePath = join(config.repoPath, contentrainDir)\n const doctor = new Doctor(basePath, config.repoPath)\n const result = await doctor.diagnose({ fix: false, modelId })\n\n return {\n content: [{ type: 'text' as const, text: formatResult(result) }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n server.registerTool(\n 'contentrain_doctor_apply',\n {\n description: [\n 'Apply a staged doctor fix by merging the staging branch into the target branch.',\n 'Use the stagingBranch value returned by contentrain_doctor fix=true.',\n 'Attempts fast-forward merge first, falls back to merge commit.',\n 'Deletes the staging branch after successful merge.',\n ].join(' '),\n annotations: { readOnlyHint: false },\n inputSchema: {\n branch: z.string().describe(`The staging branch name returned by contentrain_doctor fix=true (e.g., \"${stagingPrefix}1234567890\")`),\n },\n },\n async ({ branch }) => {\n try {\n if (!isStagingBranch(branch)) {\n return {\n content: [{ type: 'text' as const, text: `Error: branch must start with \"${stagingPrefix}\" — only doctor staging branches for the \"${targetBranch}\" target can be applied.` }],\n isError: true,\n }\n }\n\n const commitHash = await writer.applyStagingBranch(branch)\n\n return {\n content: [{\n type: 'text' as const,\n text: `✅ Doctor fix applied to \"${targetBranch}\".\\nCommit: ${commitHash}\\nStaging branch \"${branch}\" has been deleted.`,\n }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n\n server.registerTool(\n 'contentrain_doctor_discard',\n {\n description: 'Discard a staged doctor fix by deleting the staging branch without merging.',\n annotations: { readOnlyHint: false },\n inputSchema: {\n branch: z.string().describe(`The staging branch name to discard (e.g., \"${stagingPrefix}1234567890\")`),\n },\n },\n async ({ branch }) => {\n try {\n if (!isStagingBranch(branch)) {\n return {\n content: [{ type: 'text' as const, text: `Error: branch must start with \"${stagingPrefix}\" — only doctor staging branches can be discarded.` }],\n isError: true,\n }\n }\n\n await writer.discardStagingBranch(branch)\n\n return {\n content: [{ type: 'text' as const, text: `Staging branch \"${branch}\" has been deleted.` }],\n }\n }\n catch (err) {\n return { content: [{ type: 'text' as const, text: `Error: ${err}` }], isError: true }\n }\n },\n )\n}\n\nfunction formatResult(result: DoctorResult, stagingBranch?: string): string {\n const { summary, issues } = result\n\n const lines: string[] = []\n\n // Header\n if (summary.errors === 0 && summary.warnings === 0 && summary.fixed === 0) {\n lines.push('✅ No issues found!')\n }\n else if (summary.errors === 0 && summary.fixed > 0) {\n lines.push('✅ All fixable issues have been repaired.')\n }\n else if (summary.errors === 0) {\n lines.push('⚠️ No errors, but some warnings found.')\n }\n else {\n lines.push('❌ Issues detected.')\n }\n\n // Summary\n lines.push('')\n lines.push(`Models scanned: ${summary.models}`)\n lines.push(`Entries scanned: ${summary.entries}`)\n lines.push(`Errors: ${summary.errors}`)\n lines.push(`Warnings: ${summary.warnings}`)\n if (summary.fixed > 0) {\n lines.push(`Fixed: ${summary.fixed}`)\n }\n\n // Staging branch info\n if (stagingBranch) {\n lines.push('')\n lines.push(`📦 Staging branch: ${stagingBranch}`)\n lines.push('→ Review the changes, then run contentrain_doctor_apply to merge into the target branch.')\n lines.push('→ Or run contentrain_doctor_discard to discard without applying.')\n }\n\n // Issues grouped by category\n if (issues.length > 0) {\n lines.push('')\n\n const grouped = new Map<string, typeof issues>()\n for (const issue of issues) {\n const key = issue.category\n const arr = grouped.get(key) ?? []\n arr.push(issue)\n grouped.set(key, arr)\n }\n\n const categoryLabels: Record<string, string> = {\n structure: '📁 Structure',\n id: '🔑 IDs',\n schema: '📋 Schema',\n relation: '🔗 Relations',\n asset: '🖼️ Assets',\n sync: '📝 MD/MDX Sync',\n }\n\n for (const [category, categoryIssues] of grouped) {\n lines.push(`\\n### ${categoryLabels[category] || category}`)\n for (const issue of categoryIssues) {\n // Fixed issues get ✅ instead of ❌/⚠️\n const icon = issue.fix ? '✅' : issue.severity === 'error' ? '❌' : '⚠️'\n const location = [issue.modelId, issue.entryId, issue.field].filter(Boolean).join(' → ')\n const filePart = issue.file ? ` (${issue.file})` : ''\n const fixPart = issue.fix ? ` [FIX: ${issue.fix}]` : ''\n lines.push(` ${icon} ${location ? `[${location}]` : ''}${filePart} ${issue.message}${fixPart}`)\n }\n }\n }\n\n // Also include raw JSON for programmatic consumption\n lines.push('\\n---\\n')\n lines.push(JSON.stringify(result, null, 2))\n\n return lines.join('\\n')\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { ContentrainWriter } from './core/writer.js'\nimport type { WriterConfig } from './types/index.js'\nimport { registerModelTools } from './tools/model.js'\nimport { registerContentTools } from './tools/content.js'\nimport { registerAssetTools } from './tools/asset.js'\nimport { registerDoctorTools } from './tools/doctor.js'\n\nexport function createServer(config: WriterConfig): McpServer {\n const server = new McpServer({\n name: 'contentrain',\n version: '0.1.1',\n })\n\n const writer = new ContentrainWriter(config)\n\n // Register all tools\n registerModelTools(server, writer)\n registerContentTools(server, writer)\n registerAssetTools(server, writer)\n registerDoctorTools(server, writer, config)\n\n return server\n}\n","#!/usr/bin/env node\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createServer } from './server.js'\n\n// Parse config from environment variables or CLI args\nconst repoPath = process.env.CONTENTRAIN_REPO_PATH || process.cwd()\nconst branch = process.env.CONTENTRAIN_BRANCH || 'contentrain'\nconst remote = process.env.CONTENTRAIN_REMOTE || 'origin'\nconst contentrainDir = process.env.CONTENTRAIN_DIR || 'contentrain'\nconst dryRun = process.env.CONTENTRAIN_DRY_RUN === 'true'\nconst authorName = process.env.CONTENTRAIN_AUTHOR_NAME\nconst authorEmail = process.env.CONTENTRAIN_AUTHOR_EMAIL\n\nconst server = createServer({\n repoPath,\n branch,\n remote,\n contentrainDir,\n dryRun,\n author: authorName && authorEmail\n ? { name: authorName, email: authorEmail }\n : undefined,\n})\n\nasync function main(): Promise<void> {\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((err) => {\n console.error('Failed to start Contentrain MCP server:', err)\n process.exit(1)\n})\n\n// Re-export for programmatic use\nexport { createServer } from './server.js'\nexport { ContentrainWriter } from './core/writer.js'\nexport { Doctor } from './core/doctor.js'\nexport type * from './types/index.js'\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@contentrain/mcp",
3
3
  "mcpName": "io.github.Contentrain/mcp",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "description": "MCP server for Contentrain CMS content management - AI agent and human friendly",
6
6
  "type": "module",
7
7
  "bin": {