Skip to content

Releases: Contentrain/ai

[email protected]

Choose a tag to compare

@github-actions github-actions released this 17 Jul 18:46
102d1c9

Patch Changes

[email protected]

Choose a tag to compare

@github-actions github-actions released this 17 Jul 12:38
ec1cb6e

Patch Changes

@contentrain/[email protected]

Choose a tag to compare

@github-actions github-actions released this 17 Jul 18:46
102d1c9

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" — but dry_run exists 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 isolated cr/* branches, content_save
    validates before committing, destructive tools require confirm:true, and
    dry_run is the contentrain_apply preview. Still under the 512-character
    client-UI budget; no tool behavior changes.

@contentrain/[email protected]

Choose a tag to compare

@github-actions github-actions released this 17 Jul 12:38
ec1cb6e

Minor Changes

  • dbc99fe: feat(mcp): export contentDirPath from @contentrain/mcp/core/ops

    The helper already existed in core/ops/paths.ts (and backs contentFilePath
    / 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 same content_path-aware logic
    instead of maintaining a local copy.

[email protected]

Choose a tag to compare

@github-actions github-actions released this 16 Jul 10:05
da9d5c6

Patch Changes

[email protected]

Choose a tag to compare

@github-actions github-actions released this 16 Jul 21:22
1a10faf

Patch Changes

@contentrain/[email protected]

Choose a tag to compare

@github-actions github-actions released this 16 Jul 10:05
da9d5c6

Minor Changes

  • 173326c: feat(mcp)!: enforce the field constraints the schema already accepted

    A project reported that items, accept and maxSize are accepted on a field but
    never enforced — emails: ["not-an-email"] and accept: "image/jpeg" against a
    .webp both 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_save now 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 in next_steps while
    status still said "committed". Validation now runs on the pending changes and
    blocks on errors, returning isError and 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 only typeof, so min/max/pattern/
    options never reached an item, and items given 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 the integer split where 3.7 was
    rejected inside an array but accepted as a scalar.

    17 types were pure typeof checks. slug now uses the SLUG_PATTERN the
    codebase already owned — every shipped template declares slug: { type: 'slug' },
    so "Hello World!!" used to validate clean. date/datetime are parsed (the same
    check schedule.ts already did for meta), percent is range-checked, and color/
    phone warn. Mechanical rules are errors; heuristics are warnings. email/url
    keep their existing warning severity. rating is deliberately untouched — its scale
    is never declared, so any range would be invented.

    unique works 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. accept is enforced by extension-sniff
    and says that is what it is. default is coherence-checked at model_save (right
    type, within its own options) but not written into content. maxSize cannot 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. options on a non-select, items
    on a non-array, accept/maxSize on a non-media field, min > max, and an
    uncompilable pattern are now errors instead of silent no-ops. Nested fields/
    items schemas are validated recursively — they were typed z.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_save rejects content it previously committed. Run contentrain_validate
      before upgrading to see what would now be blocked.
    • model_save rejects models it previously accepted (unknown keys, min > max,
      options on a non-select, unique on a singleton).
    • validateModelDefinition returns { errors, warnings } instead of string[].
    • 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, not title) —
      a bare name was ambiguous with a top-level field.

    @contentrain/types gains validateSemanticType, validateAccept and
    isMediaType; validateFieldValue now applies semantic and accept rules.

    Studio picks all of this up automatically — its content-validation.ts delegates to
    this validator.

@contentrain/[email protected]

Choose a tag to compare

@github-actions github-actions released this 16 Jul 10:05
da9d5c6

Patch Changes

@contentrain/[email protected]

Choose a tag to compare

@github-actions github-actions released this 16 Jul 21:22
1a10faf

Patch Changes

  • a0d5bfe: fix(mcp): make i18n:false delete and meta cleanup safe

    Two bugs a project hit while cleaning up an i18n: false collection, plus a
    source-hygiene fix surfaced along the way.

    content_delete no longer destroys content when handed a locale. On an
    i18n: false model, passing a non-default locale was destructive: the locale
    mapped onto data.json and 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). Omit locale to delete the entry.

    contentrain_validate with fix: true now clears the meta layout mismatch it
    warns about.
    The "Meta layout mismatch" warning had no remediation, so fixed
    stayed 0. 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 \u0000 escape — identical at runtime, but the source is now plain text
    instead of being classified as binary by grep/diff/editors.

@contentrain/[email protected]

Choose a tag to compare

@github-actions github-actions released this 16 Jul 10:05
da9d5c6

Major Changes

  • 173326c: feat(mcp)!: enforce the field constraints the schema already accepted

    A project reported that items, accept and maxSize are accepted on a field but
    never enforced — emails: ["not-an-email"] and accept: "image/jpeg" against a
    .webp both 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_save now 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 in next_steps while
    status still said "committed". Validation now runs on the pending changes and
    blocks on errors, returning isError and 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 only typeof, so min/max/pattern/
    options never reached an item, and items given 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 the integer split where 3.7 was
    rejected inside an array but accepted as a scalar.

    17 types were pure typeof checks. slug now uses the SLUG_PATTERN the
    codebase already owned — every shipped template declares slug: { type: 'slug' },
    so "Hello World!!" used to validate clean. date/datetime are parsed (the same
    check schedule.ts already did for meta), percent is range-checked, and color/
    phone warn. Mechanical rules are errors; heuristics are warnings. email/url
    keep their existing warning severity. rating is deliberately untouched — its scale
    is never declared, so any range would be invented.

    unique works 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. accept is enforced by extension-sniff
    and says that is what it is. default is coherence-checked at model_save (right
    type, within its own options) but not written into content. maxSize cannot 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. options on a non-select, items
    on a non-array, accept/maxSize on a non-media field, min > max, and an
    uncompilable pattern are now errors instead of silent no-ops. Nested fields/
    items schemas are validated recursively — they were typed z.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_save rejects content it previously committed. Run contentrain_validate
      before upgrading to see what would now be blocked.
    • model_save rejects models it previously accepted (unknown keys, min > max,
      options on a non-select, unique on a singleton).
    • validateModelDefinition returns { errors, warnings } instead of string[].
    • 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, not title) —
      a bare name was ambiguous with a top-level field.

    @contentrain/types gains validateSemanticType, validateAccept and
    isMediaType; validateFieldValue now applies semantic and accept rules.

    Studio picks all of this up automatically — its content-validation.ts delegates to
    this validator.

Patch Changes