@bettercms-ai/mcp 0.76.0 → 0.76.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/dist/index.js +20 -15
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/references/componentize.md +1 -1
- package/dist/skills/bettercms/references/components.md +13 -2
- package/dist/skills/bettercms/references/content-modeling.md +10 -7
- package/dist/skills/bettercms/references/finish-site.md +43 -16
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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` rebuilds the derived ui from that entry (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
|
|
47
|
-
|
|
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
|
|
200
|
-
|
|
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
|
|
231
|
-
only echoes its label, and `get_binding_report` and `get_site_composition` carry the
|
|
232
|
-
`help`.
|
|
233
|
-
|
|
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`)
|
|
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 }`
|
|
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` |
|
|
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` (
|
|
124
|
-
|
|
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.
|
|
182
|
-
|
|
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 `
|
|
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 |
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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