@bettercms-ai/mcp 0.76.0 → 0.76.2

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.
@@ -81,7 +81,7 @@ Never let a component contain itself, directly or through a child.
81
81
 
82
82
  ## 3. Names and groups
83
83
 
84
- Name each component's closest kit entry as its `kitEntry` (`bettercms://kit/entry/{id}`) and take that entry's ui. When the match is wrong, override it; the `no-kit-match` exemption needs a note.
84
+ The import's kit entry (`kit: suggested <id> <score>`, the gate row's `hint`) is only a suggestion: nothing is recorded. For each component, read `bettercms://kit/entry/{id}`, check its `htmlSignals` against the section's own HTML and its pitfalls against the props, then confirm it or pick the right one from its category in `bettercms://kit` and name it with `update_component { kitEntry }`. Setting or changing `kitEntry` puts that entry's ui over the derived ui: where the entry specifies a tab, group, control or preview key for a prop, the entry wins; what it says nothing about stays as derived; ui you or a person set is kept. The `no-kit-match` exemption needs a note.
85
85
 
86
86
  Name a family by what it is (`Hero`, `CTA`, `Testimonials`) and each variant by how it looks
87
87
  (`Hero / Split`, `CTA / Centered`). Never put a page name in a reusable definition; which pages
@@ -43,8 +43,11 @@ defaultValue? }`.
43
43
  component's own `blockJson`. Why: rendering copies each prop's value to that target; a target
44
44
  that does not exist renders the default and the edit goes nowhere.
45
45
  - Every prop takes `helpText` and the component a `description` ("Hero with a heading,
46
- supporting text and two buttons."). Leave them out and the platform writes them; write your
47
- own by the rules in [content-modeling.md](content-modeling.md) ("Writing help text").
46
+ supporting text and two buttons."). Leave them out: the platform writes the description, and
47
+ a prop's help only for a format or unit, a place the canvas cannot show, a real constraint or
48
+ a fallback (most props get none). Write your own by the rules in
49
+ [content-modeling.md](content-modeling.md) ("Writing help text"): one short plain sentence,
50
+ under 90 characters, with no semicolon, no dash and no figure taken from the page.
48
51
  - A prop takes `showIf` (`{ match, rules: [{ field, op, value? }] }`): hidden until SIBLING props match. `op` is `eq`, `neq`, `in`, `isSet`, `notEmpty` or `empty`; the last three take no `value`, the first three need one. Props and model fields share all six, and a sub-field's `showIf` names its sibling sub-fields.
49
52
  - `object` holds ONE `{ childKey: value }` built from `config.fields` (required), like the model field `object`. Groups, tables and objects nest to 10 levels counting the prop; an 11th is refused `field_depth_exceeded`. Aim an `object` at `props` or at a key your component code reads, never at a text slot such as a heading's `props.text` (`prop_target_unrenderable`).
50
53
  - `datetime`, `slug`, `email`, `phone`, `json`, `location` (`{ lat, lng }`), `tags` (`string[]`), `rating` (0..`config.max`) and `code` (`{ language?, code }` or a string) hold what the same-named model field holds, and a placement's value is checked by that field's rules (`prop_value_invalid`). Like `object`, a `tags` or `code` prop never targets a text slot.
@@ -111,6 +114,14 @@ source? } }`.
111
114
  component with a `sectionType`. A true one-off is a `section` block whose `props.children` hold
112
115
  its blocks.
113
116
  - Components that share a `sectionType` are one family: its variants.
117
+ - **A section has a kind and a title, and only the kind is a name.** `sectionType` is the KIND
118
+ (`Hero`, `Logo cloud`, `Pricing`, `FAQ`): what the picker lists and the outline prints under each
119
+ section. It is the one thing to work out and name. The component's `name` is the layout within
120
+ that kind (`Centered`). The TITLE is the section's own heading, as written, which the outline
121
+ reads live from the section's content: nobody writes it, and a block `label` is only a person's
122
+ rename. Why: one string doing all three made two heroes collide (`Hero`, `Hero 2`) and gave a
123
+ component reused on a second page the first page's headline for a name. Never put a heading in
124
+ `sectionType` or `name`, and never write a `label` that rewords a heading or repeats the kind.
114
125
  - **A built-in section is a starting point, not a thing you place.** `list_components` shows the
115
126
  locked `builtin:*` blueprints; `create_component` with `from: "builtin:hero-centered"` copies
116
127
  one into the project as an ordinary draft component, then edit and publish it like any other.
@@ -196,13 +196,15 @@ leaving a gap; a long value wraps, and clamps only where the design intends it.
196
196
 
197
197
  ### Writing help text
198
198
 
199
- The platform writes a role line on every element, so leaving `helpText` out is the default, not
200
- a gap. It says so: `helpText: { source: "auto" }` in the response, `helpSource: "auto"` on the
199
+ Most fields get no help. The platform writes a line only where one of the four reasons below
200
+ holds and leaves every other element with none, so leaving `helpText` out is the default, not a
201
+ gap. It says so: `helpText: { source: "auto" }` in the response, `helpSource: "auto"` on the
201
202
  stored field. Text you send is stored as yours (`helpSource: "agent"`) and is never rewritten;
202
203
  `editor_setup` rewrites only its own lines and never a person's.
203
204
 
204
205
  Ask one question per field: does the editor need this line to type the right value? Write your
205
- own `helpText` (on a field or a prop) only for one of four reasons:
206
+ own `helpText` (on a field or a prop) only for one of four reasons, written as one short plain
207
+ sentence under 90 characters, with no semicolon, no dash and no figure taken from the page.
206
208
 
207
209
  | Reason | Good |
208
210
  |---|---|
@@ -227,10 +229,11 @@ line, you are over-writing. A component, a collection and a Layout Section (`upd
227
229
  still take a `description` that says what the thing is ("Hero with a heading, supporting text,
228
230
  two buttons and an image"), never its name again.
229
231
 
230
- `get_next_steps` lists `help-text-missing` for a field with no help line at all, or one that
231
- only echoes its label, and `get_binding_report` and `get_site_composition` carry the coverage as
232
- `help`. Clear the finding by letting the platform's line stand or by writing one of the four
233
- kinds above, never with filler.
232
+ `get_next_steps` lists `help-text-missing` for an element the editor setup has not settled yet, or
233
+ one whose line only echoes its label, and `get_binding_report` and `get_site_composition` carry the
234
+ coverage as `help`. The platform writes a line only for the four reasons above and leaves most
235
+ elements with none; clear the finding by running `editor_setup`, or by writing one of the four
236
+ kinds yourself, never with filler.
234
237
 
235
238
  ## 6. Changing a schema safely
236
239
 
@@ -41,6 +41,12 @@ is clean for every component it placed, and `get_next_steps` lists nothing left
41
41
  Both end in the verify loop of SKILL.md, and an imported site ends only on the setup gate below.
42
42
  One converted page, or a receipt with paths still pending, is not a finished site.
43
43
 
44
+ Setup is not a conversion. An imported site whose release serves its pages is editable through the
45
+ fields the release binds on the built page, and the setup gate measures exactly that. Run the
46
+ convert codemod only for what the gate asks it for (`run-sections-conversion`, a form to wire) or
47
+ when the owner asks for the source to declare its bindings. "The codemod bound few paths" is no
48
+ gate failure and nothing to report to the owner.
49
+
44
50
  ## The setup gate: what "finished" is measured by
45
51
 
46
52
  `get_next_steps` returns `receipt.complete` and `receipt.gate`. The platform computes the gate
@@ -62,14 +68,14 @@ line), which is what a script waiting on the setup reads.
62
68
  | `svg` | every inline SVG a visitor sees (a logo, an icon, an illustration) belongs to a field; a hidden symbol sheet is not counted | `routes.rows[].svg` |
63
69
  | `number` | every element whose whole text is a number (a stat, a price, a rating) belongs to a field | `routes.rows[].number` |
64
70
  | `attribute` | every `alt`, `aria-label`, `title` and `placeholder` with words in it belongs to a field | `routes.rows[].attribute` |
65
- | `names` | no field of the page, and no component, has a name a person cannot read: a slug, a hash, a structural word (`Section 2`, `wrap`), a number beside the same name (`Icon 2`), a numbered run the import left (`Pricing text 1` … `Pricing text 12`), a section the import called by a sentence of its copy (`The Numbers After One Season items`), or a component name past four words | `blocking.rows` |
71
+ | `names` | no field of the page, and no component, has a name a person cannot read: a slug, a hash, a structural word (`Section 2`, `wrap`), or a number beside the same name (`Icon 2`) | `blocking.rows` |
66
72
  | `seo` | the page has a title of at most 70 characters and a description of at most 200, and a canonical where the build ships one for this site's origin; site-wide, every page has a share image, its own or the site's default | `blocking.rows` |
67
73
  | `structuredData` | JSON-LD the build ships on the route is on the page or the entry | `blocking.rows` |
68
74
  | `collections` | sibling routes that are one family (three under one parent, or two under `tags`, `category`, `author`) are a collection; reported on the family's listing route, built or not | `blocking.rows` |
69
75
  | `buttons` | every button field is one of the site's live Button looks, the Button family is the looks setup measured on this build (the same looks, not the same count), and no look is named by a number or a build's class | `blocking.rows`, `route: null` |
70
76
  | `components` | the authoring question is answered, and no section is left that a componentize run would place | `blocking.rows`, `route: null` |
71
77
  | `forms` | every form of the customer's has an address to notify; a form setup ignored (sign-in, add to basket) is not counted | `blocking.rows`, `route: null` |
72
- | `dock` | every component with props has at least one prop carrying V2 `ui`, and every list prop (`table`, `array`, `multi-reference`) titles its rows with `ui.preview.title` or `ui.itemLabel`; and every component with props names the `bettercms://kit` entry its dock UI was built from (`update_component { kitEntry }`, `set-kit-entry`), or records `no-kit-match` with a note on `route: "component/<slug>/kit"`; one row per component per half, Button looks not counted | `blocking.rows`, `route: null`, the component, its props and the `bettercms://kit/entry/{id}` to read in `detail` |
78
+ | `dock` | every component with props has at least one prop carrying V2 `ui`, and every list prop (`table`, `array`, `multi-reference`) titles its rows with `ui.preview.title` or `ui.itemLabel`; and every component with props names the `bettercms://kit` entry its dock UI was built from (`update_component { kitEntry }`, `set-kit-entry`; the import only suggests one, the row's `hint`: confirm or replace it after checking its htmlSignals and pitfalls, and the write rebuilds the derived ui), or records `no-kit-match` with a note on `route: "component/<slug>/kit"`; one row per component per half, Button looks not counted | `blocking.rows`, `route: null`, the component, its props and the `bettercms://kit/entry/{id}` to read in `detail` |
73
79
 
74
80
  `pages` counts routes built, created and published. `coverage` sums `editable` of `total` across
75
81
  the site, `svg`, `number` and `attribute` included, and `open` counts the rows still open per
@@ -99,14 +105,14 @@ The other rows name their fix the same way:
99
105
 
100
106
  | `fixCode` | The fix |
101
107
  |---|---|
102
- | `bind-elements` | `get_conversion_brief { complete: true }` lists what no field owns on the route; bind each (an SVG as an image or icon field, a number as its own field, attribute copy with `data-bcms-props`) |
108
+ | `bind-elements` | What no field owns on the route is `pages[].skipped` of `get_conversion_brief { complete: true }` (each node with why the import left it) and the `unaddressable` buckets of `get_binding_report`. Declare each in the site's template (`data-bcms-field`; an SVG as an image or icon field, a number as its own field, attribute copy with `data-bcms-props`) and push. **Do not run `@bettercms-ai/convert` for this row.** The brief's `paths` are fields the CMS already has, and on a built site the release has already bound them on the page: the codemod creates no field, and run over them it refuses copy that lives in Markdown, pictures from a content collection and values a helper formats, by the thousand |
103
109
  | `rename-fields` | `get_page`, read the section's source, then name every listed field of the page in ONE `update_content_model` call (`pageId`, `fieldLabel` and `fieldHelpText` keyed by field path, `ifMatch` from `get_page`'s `backingModel`), or `update_field` for a single one; `update_component` for a component (`name`, and `propLabel` / `propHelpText` for its props in one call). See "Names" below |
104
110
  | `fill-seo` | `update_page` with `metaTitle` and `metaDescription` (`update_content_entry` with `meta` for an entry; `generate_seo_meta` drafts one from text you give it); a missing canonical is setup's to copy from the built head, so that row carries `editor_setup { force: true }`; the site-wide row wants the site's default share image, `update_seo` with `seoDefaults.ogImage` |
105
111
  | `import-structured-data` | `editor_setup { force: true }` imports the build's JSON-LD; `update_seo` when it has to be written by hand |
106
112
  | `model-collection` | `create_content_model` with the family's `urlPattern` (`/tags/:slug`), then its entries: one item template for the family ([content-modeling.md](content-modeling.md)) |
107
113
  | `derive-buttons` | `editor_setup { force: true }` mints a Button for each look the build draws, retires one it no longer draws and maps each button to its look; rename a look with `update_component` ([components.md](components.md)) |
108
114
  | `componentize` | `set_authoring_preference` when nobody answered, then `get_componentize_plan` and `componentize_sections` ([componentize.md](componentize.md)) |
109
- | `set-form-recipient` | ask the user where the form's submissions go, then `update_form` with `notifyEmails`; `update_form { ignored: true }` when it is not one of their forms |
115
+ | `set-form-recipient` | `update_form` with `notifyEmails` set to the address the owner gave when setup started (it is in your first prompt); `update_form { ignored: true }` when it is not one of their forms. Only a form the built site posts to BetterCMS is listed, and the release publishes it once it is wired. Never ask for an address at the end of a run |
110
116
  | `set-dock-ui` | the last step of the repair order below: `get_component`, then `update_component` with `propUi` and `propTab` for the props the row names ([editor-chrome.md](editor-chrome.md)) |
111
117
 
112
118
  A row nothing measured reads `unmeasured: true` with `run-setup`, and `gate.unmeasured` lists the
@@ -120,9 +126,8 @@ it and an open row of these means run setup again: `run-setup`, `derive-buttons`
120
126
  the map), `import-structured-data`, `fill-seo` from the built head and the page's own copy,
121
127
  `rename-fields` for a field still carrying a derived name, `componentize` once the authoring
122
128
  answer is `components`, and `enable-in-place-structure` on a project it creates. What is left
123
- takes judgement and is yours: `bind-elements`, `model-collection`, `set-form-recipient` (ask the
124
- user), `run-sections-conversion`, a name a person gave, a name only the site's code can give (a
125
- numbered run, a section called by its copy), a title over its limit, a look named by a number, and
129
+ takes judgement and is yours: `bind-elements`, `model-collection`, `set-form-recipient` (the
130
+ address given at the start), `run-sections-conversion`, a name a person gave, a title over its limit, a look named by a number, and
126
131
  the authoring answer itself (the user's). `rebuild` is the user's.
127
132
 
128
133
  Beside the gate, `data` lists what else is unfinished, each item by its `ruleId`: content
@@ -178,17 +183,30 @@ of the collection, as it is a page's own field on a page.
178
183
 
179
184
  The import names a field by its section and its role there (`Hero headline`, `FAQ questions`).
180
185
  Where the markup says no more it numbers them (`Pricing text 4`, `Hero image 2`), and a section
181
- whose classes name nothing is called by its heading's words. The `names` row lists both until
182
- each is renamed, and nothing excuses it: an editor reads these names all day.
186
+ whose classes name nothing is called by its heading's words. No build can say that a picture is
187
+ the dashboard screenshot; the site's code can. So these never hold the gate open: they are
188
+ `receipt.gate.naming`, and `get_next_steps` lists the step `names-from-code` until each is
189
+ renamed. Do it in the same job, after the gate's own rows: an editor reads these names all day.
183
190
 
184
191
  Name from the site's code, the way a developer writes a schema. Open the section's source file
185
192
  and use what it calls things (`plan.cents`, `billingFrequency`, `StatsBand.astro`):
186
193
 
187
194
  | What | The name | Like | Never |
188
195
  |---|---|---|---|
189
- | A section (a component's `name`) | what it IS, one to four words, sentence case | `Announcement banner`, `Logo cloud`, `Feature tabs`, `Testimonial + stats`, `Call to action` | its heading's copy, `Section 4`, a file path |
196
+ | A section's KIND (a component's `sectionType`) | what it IS, one to three words, sentence case. The picker lists kinds and a page's outline shows it under each section | `Announcement banner`, `Hero`, `Logo cloud`, `Feature tabs`, `Testimonial`, `Pricing`, `FAQ`, `Call to action` | its heading's copy or first word (`Simple`, `Trusted`), `Section 4`, a file path |
197
+ | A layout of that kind (a component's `name`) | how THIS layout differs from the kind's others; the kind alone when it has one layout | `Centered`, `Split with media`, `Pricing` | its heading's copy, the page it came from with a number (`Hero 2`) |
198
+ | One placed section's title | nothing you write. The outline shows the section's own heading, as written and read live, with its kind underneath | `Equip Your Projects with ScrewFast` over `Hero` | a `label` you invent, a shortened or reworded heading, the kind said again |
190
199
  | A field or prop | what it HOLDS there, one to three words | `Dismiss key`, `Starter plan price`, `Dashboard screenshot`, `Billing frequency` | its own copy, a role with a number (`Text 4`), the section's name alone |
191
- | Help text | one sentence saying what it is for or where it shows, only where the name leaves a question | `Change this to show the banner again to visitors who dismissed an earlier one.` | the label or the value said again |
200
+ | Help text | most fields get none. One plain sentence under 90 characters, only for a format or unit, a place the canvas cannot show, a real constraint or a fallback. No semicolons, no dashes, no values from the page | `Leave empty to use the page title.` | the label or the value said again, writing advice, a figure copied from another page |
201
+
202
+ A section says two things: what it IS (its kind) and what it SAYS (its title). Only the kind is
203
+ yours to judge. The import sets it from the band's shape or the site's own word for it; where it
204
+ could read neither, the kind is the key's word, often the heading's first, and you correct it with
205
+ `update_component { sectionType }`, spelled exactly like the kind's other layouts. The title is
206
+ never named by anyone: the outline prints the section's heading as the page holds it, so two
207
+ heroes are `Hero` twice in the picker and two different titles on the page. Do not write a block
208
+ `label` to title a section, and do not shorten, re-case or reword a heading into one. A `label`
209
+ is only ever a person's own rename.
192
210
 
193
211
  A numbered run that is one thing repeated (plans, features, logos) is a list, not twelve names:
194
212
  fold it into one list field or one `table` prop whose rows are the cards
@@ -244,10 +262,19 @@ holds it. Edit it as a field of any one entry and publish that entry: the value
244
262
  field and every item page is repainted. Until then it is that entry's draft, shown on no page.
245
263
  A translated entry keeps its own words for it. Never write the same value into each entry.
246
264
 
247
- Git belongs to the owner. Verification releases go out from the working tree, so the job never
248
- needs a commit to prove itself. After the final report, ask whether they want the changes
249
- committed and pushed, committed only, or left uncommitted, and mention that an unpushed
250
- conversion is lost on the next rebuild from their repository.
265
+ Git is answered before you start, never asked at the end. `bettercms init` asks the owner once
266
+ whether BetterCMS may push, and the first prompt carries the answer:
267
+
268
+ - **Yes:** after every change to the repository, commit it, push, wait until `get_deploy_status`
269
+ shows the build of your commit live, run `editor_setup`, then read `get_next_steps`. The gate
270
+ measures the build of their repository, so a change you did not push can never pass.
271
+ - **No, or the prompt says nothing about it:** do not push. Verification releases go out from the
272
+ working tree, so the job never needs a commit to prove itself; say once, in the final report,
273
+ which routes wait on their push and that an unpushed conversion is lost on the next rebuild
274
+ from their repository.
275
+
276
+ The same goes for the authoring answer and where forms send submissions: when the first prompt
277
+ states one, it is settled. Do not ask again, and do not end on a question the prompt answered.
251
278
 
252
279
  ## The gap ledger: `.bettercms/ledger.json`
253
280
 
@@ -282,7 +309,7 @@ and add the new result. Update the file as each row moves, so a reset loses at m
282
309
  | 0 | Editor setup: `editor_setup { check: true }` reads `ready` for the live build (run `editor_setup` when it does not) | [getting-started.md](getting-started.md) |
283
310
  | 1 | Routing and data shape: page / collection / entry classification and folders; keep public URLs | [content-modeling.md](content-modeling.md) |
284
311
  | 2 | Sections and reuse: every band a section; dedupe compatible patterns into variants and overrides; nav and footer as globals (`get_componentize_plan`, `componentize_sections` on a components project). `bettercms init` has already wrapped each page's sections in `<BcmsSections>` and committed it: fix only the pages it refused ([componentize.md](componentize.md) §5) | [components.md](components.md), [componentize.md](componentize.md) |
285
- | 3 | Editor contract: field types, labels, help text on every element (`get_next_steps` shows no `help-text-missing`), validation, Portable Text accept lists, nested groups | [portable-text.md](portable-text.md) |
312
+ | 3 | Editor contract: field types, labels, help text settled on every element — a line only where the editor needs one, most get none (`get_next_steps` shows no `help-text-missing`), validation, Portable Text accept lists, nested groups | [portable-text.md](portable-text.md) |
286
313
  | 4 | Design: media, logo roles, fonts and brand tokens, without changing visual intent | [media-and-brand.md](media-and-brand.md) |
287
314
  | 5 | Bindings: the site reads BetterCMS data and each intended leaf or attribute is bound; build, preview and runtime agree | [bindings.md](bindings.md), [frameworks.md](frameworks.md) |
288
315
  | 6 | Behaviour: forms, links, menus, accordions, modals, consent, custom code | [forms-and-interactions.md](forms-and-interactions.md) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/mcp",
3
- "version": "0.76.0",
3
+ "version": "0.76.2",
4
4
  "type": "module",
5
5
  "description": "BetterCMS MCP server — schema, content, forms + components authoring tools over the Management API, authed via OAuth device-code.",
6
6
  "bin": {