Releases: Contentrain/ai
Release list
[email protected]
Patch Changes
- Updated dependencies [c189b6f]
- @contentrain/[email protected]
- @contentrain/[email protected]
- @contentrain/[email protected]
[email protected]
Patch Changes
- Updated dependencies [dbc99fe]
- @contentrain/[email protected]
- @contentrain/[email protected]
- @contentrain/[email protected]
@contentrain/[email protected]
Patch Changes
-
c189b6f: fix(mcp): stop DEFAULT_INSTRUCTIONS advertising a universal dry_run
The MCP server instructions told every client to "preview writes with
dry_run:true, then re-run with dry_run:false" — butdry_runexists only on
contentrain_apply. On every other write tool the unknown key is stripped by
the schema, so an agent following the instructions performed a real write
while believing it had previewed. The instructions now state the actual
safety model: writes land on isolatedcr/*branches,content_save
validates before committing, destructive tools requireconfirm:true, and
dry_runis thecontentrain_applypreview. Still under the 512-character
client-UI budget; no tool behavior changes.
@contentrain/[email protected]
Minor Changes
-
dbc99fe: feat(mcp): export
contentDirPathfrom@contentrain/mcp/core/opsThe helper already existed in
core/ops/paths.ts(and backscontentFilePath
/documentFilePath), but the subpath barrel only re-exported the file-level
helpers. Exposing the directory-level resolver lets consumers (e.g. Studio)
resolve a model's content directory through the samecontent_path-aware logic
instead of maintaining a local copy.
[email protected]
Patch Changes
- Updated dependencies [173326c]
- @contentrain/[email protected]
- @contentrain/[email protected]
- @contentrain/[email protected]
- @contentrain/[email protected]
- @contentrain/[email protected]
[email protected]
Patch Changes
- Updated dependencies [a0d5bfe]
- @contentrain/[email protected]
- @contentrain/[email protected]
- @contentrain/[email protected]
@contentrain/[email protected]
Minor Changes
-
173326c: feat(mcp)!: enforce the field constraints the schema already accepted
A project reported that
items,acceptandmaxSizeare accepted on a field but
never enforced —emails: ["not-an-email"]andaccept: "image/jpeg"against a
.webpboth produced zero errors. The report was right, and the surface was larger
than the three properties it named: 4 of 27 field types had any semantic
validation, three constraints were read by nothing, and none of it blocked a write.A constraint that isn't a constraint is worse than no constraint — the author stops
looking.content_savenow validates before committing and refuses to write. It ran
plan → commit → validate → report, so an invalid value landed in git, was
auto-merged, and the caller learned about it from a string innext_stepswhile
statusstill said"committed". Validation now runs on the pending changes and
blocks on errors, returningisErrorand no commit. Warnings still pass — they are
heuristics, and a legitimate value can sit outside an approximate pattern. Only the
entries being saved are fatal: a pre-existing bad entry elsewhere in the model does
not hold up an unrelated save.Array items share the scalar rule set. They ran through a parallel type switch
that knew 10 of the 27 types and checked onlytypeof, somin/max/pattern/
optionsnever reached an item, anditemsgiven as a FieldDef with a non-object
type ({type:'array', items:{type:'string', max:50}}) matched no branch at all —
silently unvalidated, while the type emitter rendered it as real. Items now recurse
through the same validator, which also closes theintegersplit where3.7was
rejected inside an array but accepted as a scalar.17 types were pure
typeofchecks.slugnow uses theSLUG_PATTERNthe
codebase already owned — every shipped template declaresslug: { type: 'slug' },
so"Hello World!!"used to validate clean.date/datetimeare parsed (the same
checkschedule.tsalready did for meta),percentis range-checked, andcolor/
phonewarn. Mechanical rules are errors; heuristics are warnings.email/url
keep their existing warning severity.ratingis deliberately untouched — its scale
is never declared, so any range would be invented.uniqueworks on documents. It was gated on a context only the collection
validator passed, so it was a no-op exactly where every shipped template declares it.
On singletons it is now rejected at model_save: the model holds one record per
locale, so there is nothing to compare against.The dead constraints, handled honestly.
acceptis enforced by extension-sniff
and says that is what it is.defaultis coherence-checked at model_save (right
type, within its ownoptions) but not written into content.maxSizecannot be
enforced by MCP — it holds a path, never the bytes — so model_save now says so and
points at the provider, which owns the policy at ingest. The docs claimed all three
worked; they no longer do.model_save rejects what it will not enforce.
optionson a non-select,items
on a non-array,accept/maxSizeon a non-media field,min > max, and an
uncompilablepatternare now errors instead of silent no-ops. Nestedfields/
itemsschemas are validated recursively — they were typedz.unknown()and never
checked. The field schema is.strict(): a typo'd constraint (requird: true) used
to be stripped without a word.BREAKING CHANGE:
content_saverejects content it previously committed. Runcontentrain_validate
before upgrading to see what would now be blocked.model_saverejects models it previously accepted (unknown keys,min > max,
optionson a non-select,uniqueon a singleton).validateModelDefinitionreturns{ errors, warnings }instead ofstring[].- Array-item type errors carry
validateFieldValue's message ("Type mismatch:
expected string, got number") instead of "must be a string". The field path is
unchanged. - Nested object errors are qualified by their parent (
seo.title, nottitle) —
a bare name was ambiguous with a top-level field.
@contentrain/typesgainsvalidateSemanticType,validateAcceptand
isMediaType;validateFieldValuenow applies semantic andacceptrules.Studio picks all of this up automatically — its
content-validation.tsdelegates to
this validator.
@contentrain/[email protected]
Patch Changes
- Updated dependencies [173326c]
- @contentrain/[email protected]
@contentrain/[email protected]
Patch Changes
-
a0d5bfe: fix(mcp): make i18n:false delete and meta cleanup safe
Two bugs a project hit while cleaning up an
i18n: falsecollection, plus a
source-hygiene fix surfaced along the way.content_deleteno longer destroys content when handed a locale. On an
i18n: falsemodel, passing a non-defaultlocalewas destructive: the locale
mapped ontodata.jsonand the default-locale meta, so the call emptied the
shared content and deleted the wrong meta file while the locale actually named
kept its stray meta — the opposite of the request. Content is locale-agnostic
here, so a locale-scoped delete is now rejected with a clear error (both in the
plan API and the legacy path). Omitlocaleto delete the entry.contentrain_validatewithfix: truenow clears the meta layout mismatch it
warns about. The "Meta layout mismatch" warning had no remediation, sofixed
stayed0. The fix is deterministic and never decides a status: when the
default-locale meta is authoritative the redundant strays are pruned; when only
a stray exists it is migrated to the default path so the record is preserved;
several strays with no default is left for the author to resolve. Consolidation
runs before the orphan-content pass and gates that pass's draft fabrication, so
a real published record is never replaced by a fabricated draft and then deleted
on a later run.Also replaced two raw NUL bytes in the validator source (a Map-key separator)
with a\u0000escape — identical at runtime, but the source is now plain text
instead of being classified as binary by grep/diff/editors.
@contentrain/[email protected]
Major Changes
-
173326c: feat(mcp)!: enforce the field constraints the schema already accepted
A project reported that
items,acceptandmaxSizeare accepted on a field but
never enforced —emails: ["not-an-email"]andaccept: "image/jpeg"against a
.webpboth produced zero errors. The report was right, and the surface was larger
than the three properties it named: 4 of 27 field types had any semantic
validation, three constraints were read by nothing, and none of it blocked a write.A constraint that isn't a constraint is worse than no constraint — the author stops
looking.content_savenow validates before committing and refuses to write. It ran
plan → commit → validate → report, so an invalid value landed in git, was
auto-merged, and the caller learned about it from a string innext_stepswhile
statusstill said"committed". Validation now runs on the pending changes and
blocks on errors, returningisErrorand no commit. Warnings still pass — they are
heuristics, and a legitimate value can sit outside an approximate pattern. Only the
entries being saved are fatal: a pre-existing bad entry elsewhere in the model does
not hold up an unrelated save.Array items share the scalar rule set. They ran through a parallel type switch
that knew 10 of the 27 types and checked onlytypeof, somin/max/pattern/
optionsnever reached an item, anditemsgiven as a FieldDef with a non-object
type ({type:'array', items:{type:'string', max:50}}) matched no branch at all —
silently unvalidated, while the type emitter rendered it as real. Items now recurse
through the same validator, which also closes theintegersplit where3.7was
rejected inside an array but accepted as a scalar.17 types were pure
typeofchecks.slugnow uses theSLUG_PATTERNthe
codebase already owned — every shipped template declaresslug: { type: 'slug' },
so"Hello World!!"used to validate clean.date/datetimeare parsed (the same
checkschedule.tsalready did for meta),percentis range-checked, andcolor/
phonewarn. Mechanical rules are errors; heuristics are warnings.email/url
keep their existing warning severity.ratingis deliberately untouched — its scale
is never declared, so any range would be invented.uniqueworks on documents. It was gated on a context only the collection
validator passed, so it was a no-op exactly where every shipped template declares it.
On singletons it is now rejected at model_save: the model holds one record per
locale, so there is nothing to compare against.The dead constraints, handled honestly.
acceptis enforced by extension-sniff
and says that is what it is.defaultis coherence-checked at model_save (right
type, within its ownoptions) but not written into content.maxSizecannot be
enforced by MCP — it holds a path, never the bytes — so model_save now says so and
points at the provider, which owns the policy at ingest. The docs claimed all three
worked; they no longer do.model_save rejects what it will not enforce.
optionson a non-select,items
on a non-array,accept/maxSizeon a non-media field,min > max, and an
uncompilablepatternare now errors instead of silent no-ops. Nestedfields/
itemsschemas are validated recursively — they were typedz.unknown()and never
checked. The field schema is.strict(): a typo'd constraint (requird: true) used
to be stripped without a word.BREAKING CHANGE:
content_saverejects content it previously committed. Runcontentrain_validate
before upgrading to see what would now be blocked.model_saverejects models it previously accepted (unknown keys,min > max,
optionson a non-select,uniqueon a singleton).validateModelDefinitionreturns{ errors, warnings }instead ofstring[].- Array-item type errors carry
validateFieldValue's message ("Type mismatch:
expected string, got number") instead of "must be a string". The field path is
unchanged. - Nested object errors are qualified by their parent (
seo.title, nottitle) —
a bare name was ambiguous with a top-level field.
@contentrain/typesgainsvalidateSemanticType,validateAcceptand
isMediaType;validateFieldValuenow applies semantic andacceptrules.Studio picks all of this up automatically — its
content-validation.tsdelegates to
this validator.
Patch Changes
- Updated dependencies [173326c]
- @contentrain/[email protected]