@bettercms-ai/mcp 0.41.1 → 0.42.0

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 CHANGED
@@ -3107,7 +3107,7 @@ function buildToolDefs(deps) {
3107
3107
  def(
3108
3108
  "submit_conversion_receipt",
3109
3109
  "Record what the conversion codemod could and could not do",
3110
- "Hand BetterCMS the codemod's own account of a conversion run, so the coverage meter can say WHY a path is not declared instead of only that it is not. Submit the receipt `npx @bettercms-ai/convert` wrote (`--receipt out.json`) for the SAME `briefDigest` get_conversion_brief { complete: true } returned: `{ briefDigest, receipt }`, where the receipt carries `paths: { declared, rewritten, alreadyDeclared, pending[{ route, scope, path, kind, file, reason, message }] }`. `paths.declared` must equal rewritten + alreadyDeclared + pending.length, and each pending `reason` is one of the converter's own (IN_EXPRESSION, AMBIGUOUS_LITERAL, REPEATER_FIXED_LENGTH, PARSE_ERROR, \u2026) \u2014 a path with no receipt row simply reads `not-declared`. It is a RECORD, not a release: it changes nothing about the site, and the meter picks it up on the next get_binding_report after the next deploy. A 404 `unknown-brief` means that digest was never issued here, so convert against a brief this project actually returned. \u{1F534} SUBMIT THE BINDING RECEIPT, NOT THE `--forms` ONE. A run of `npx @bettercms-ai/convert --forms` writes a receipt whose `paths` are all zero and whose account is in a `forms` block (`{ wired, alreadyWired, pending[{ id, name, reason }], notes }`); it passes this endpoint's arithmetic and would overwrite the real coverage with zeros. Write it to its own file (`--receipt forms-receipt.json`), read `forms.pending` and publish every form `forms.notes` names in the Forms tab, and submit the binding run's receipt here. Requires artifact:write, the same authority as set_binding_mode; on a workspace-wide connection pass `projectId`.",
3110
+ "Hand BetterCMS the codemod's own account of a conversion run, so the coverage meter can say WHY a path is not declared instead of only that it is not. Submit the receipt `npx @bettercms-ai/convert` wrote (`--receipt out.json`) for the SAME `briefDigest` get_conversion_brief { complete: true } returned: `{ briefDigest, receipt }`, where the receipt carries `paths: { declared, rewritten, alreadyDeclared, pending[{ route, scope, path, kind, file, reason, message, fix }] }`. `paths.declared` must equal rewritten + alreadyDeclared + pending.length, and each pending `reason` is one of the converter's own (IN_EXPRESSION, AMBIGUOUS_LITERAL, REPEATER_FIXED_LENGTH, PARSE_ERROR, \u2026) \u2014 a path with no receipt row simply reads `not-declared`. \u{1F534} A RECEIPT WITH PENDING PATHS IS A PROGRESS REPORT, NOT A FINISH LINE. The response answers `complete` and `pendingTotal`, and echoes the first 40 pending rows WITH their `fix` \u2014 `{ action, file, line, col?, snippet, why? }`, where `action` is one of `wrap-span` (wrap the literal in a `<span data-bcms-field=\u2026>`), `declare-attr` (add `data-bcms-field=\u2026` to the element at file:line), `bind-expression` (replace the expression with the framework's bcmsField helper), `declare-richtext` (bind the container with the richtext helper), `bind-data` (the literal comes from the data file at file:line \u2014 bind that field) or `manual` (with a one-sentence `why`). Apply every fix in the source, rerun the codemod, resubmit. `complete: true` is the only receipt that ends a conversion \u2014 do not report a site converted on anything less. It is a RECORD, not a release: it changes nothing about the site, and the meter picks it up on the next get_binding_report after the next deploy. A 404 `unknown-brief` means that digest was never issued here, so convert against a brief this project actually returned. \u{1F534} SUBMIT THE BINDING RECEIPT, NOT THE `--forms` ONE. A run of `npx @bettercms-ai/convert --forms` writes a receipt whose `paths` are all zero and whose account is in a `forms` block (`{ wired, alreadyWired, pending[{ id, name, reason }], notes }`); it passes this endpoint's arithmetic and would overwrite the real coverage with zeros. Write it to its own file (`--receipt forms-receipt.json`), read `forms.pending` and publish every form `forms.notes` names in the Forms tab, and submit the binding run's receipt here. Requires artifact:write, the same authority as set_binding_mode; on a workspace-wide connection pass `projectId`.",
3111
3111
  z.object({
3112
3112
  briefDigest: z.string().min(1).describe("The `briefDigest` get_conversion_brief { complete: true } returned. Must match the receipt's own."),
3113
3113
  receipt: z.record(z.string(), z.unknown()).describe("The receipt `npx @bettercms-ai/convert --receipt out.json` wrote, verbatim.")
@@ -4803,8 +4803,14 @@ an empty string on the live site.
4803
4803
 
4804
4804
  ## 13. Convert an imported repo into a CMS-backed, editable site
4805
4805
 
4806
- \xA711 says a deploy does not make a site editable. This is the recipe that does, and it ends in a
4807
- receipt you can read: \`get_binding_report\` says \`mode: "declared"\` with zero unmatched paths.
4806
+ \xA711 says a deploy does not make a site editable. This is the recipe that does.
4807
+
4808
+ \u{1F534} **A CONVERSION ENDS ONLY WHEN NOTHING IS PENDING.** The codemod's first pass never
4809
+ declares every path \u2014 measured on a real site, one pass declared 282 of 767 \u2014 and every path it
4810
+ leaves in \`paths.pending\` carries a \`fix\` naming the file, the line and the one edit. Apply
4811
+ them, rerun, resubmit, and repeat until \`submit_conversion_receipt\` answers \`complete: true\`.
4812
+ That boolean is the finish line; a receipt with pending paths is a progress report. The
4813
+ \`convert_site\` MCP prompt walks exactly this loop.
4808
4814
 
4809
4815
  **Scope, before you start.**
4810
4816
 
@@ -4878,21 +4884,30 @@ hand. THE ORDER, and every step of it matters:
4878
4884
  could not match are in \`forms.pending\` with a reason (\`FORM_NOT_IN_SOURCE\`,
4879
4885
  \`FIELD_UNMATCHED\`, \`FORM_AMBIGUOUS\`, \u2026) \u2014 it refuses rather than guessing, because a form
4880
4886
  wired to the wrong id delivers the customer's leads into another form's inbox.
4881
- 3. REVIEW THE PENDING LIST. Every path the codemod could not do is in \`paths.pending\` with a
4882
- reason (\`IN_EXPRESSION\`, \`AMBIGUOUS_LITERAL\`, \`REPEATER_FIXED_LENGTH\`, \`PARSE_ERROR\`, \u2026).
4883
- Do those by hand, or decide they are genuinely not convertible \u2014 do not skip past them.
4884
- 4. \`submit_conversion_receipt { briefDigest, receipt }\` with the receipt file, verbatim. This is
4885
- what lets the coverage meter say WHY a path is undeclared instead of only that it is; without
4886
- it every one of them reads \`not-declared\`, which looks like a broken site rather than work
4887
- with a reason. It records and releases nothing.
4887
+ 3. APPLY EVERY PENDING FIX, THEN RERUN. Each entry of \`paths.pending\` carries its reason
4888
+ (\`IN_EXPRESSION\`, \`AMBIGUOUS_LITERAL\`, \`REPEATER_FIXED_LENGTH\`, \`PARSE_ERROR\`, \u2026) and,
4889
+ where the codemod could work one out, a \`fix\`: \`{ action, file, line, col?, snippet, why? }\`.
4890
+ Go to \`file\`:\`line\` and do what \`action\` says \u2014 \`wrap-span\` (wrap the literal in
4891
+ \`<span data-bcms-field="\u2026">\`), \`declare-attr\` (add \`data-bcms-field="\u2026"\` to that element),
4892
+ \`bind-expression\` (replace the expression with the framework's bcmsField helper),
4893
+ \`declare-richtext\` (bind the container with the richtext helper), \`bind-data\` (bind the field
4894
+ in the data file or collection entry it names) or \`manual\` (do what its \`why\` says). A row
4895
+ with no \`fix\` is still yours. Then RERUN step 2 and repeat until \`paths.pending\` is empty \u2014
4896
+ the codemod is idempotent, so a second run over converted source rewrites nothing.
4897
+ 4. \`submit_conversion_receipt { briefDigest, receipt }\` with the receipt file, verbatim. It
4898
+ answers \`complete\`, \`pendingTotal\` and the first 40 pending rows with their \`fix\`. \`complete: true\` \u2014 nothing
4899
+ pending \u2014 is the ONLY receipt that ends a conversion; on anything less go back to step 3. It
4900
+ is also what lets the coverage meter say WHY a path is undeclared instead of only that it is.
4901
+ It records and releases nothing.
4888
4902
  5. Push, or \`deploy_project\`. A PLAIN HTML repo \u2014 no \`package.json\` \u2014 deploys by PUSH ONLY:
4889
4903
  \`deploy_project\` refuses such an archive by design, so pushing is the whole lane. Either way,
4890
4904
  poll \`get_deploy_status\` until \`slot\` is \`current\` (or \`awaitingPromote\` is false) before you
4891
4905
  read the report in step 6 \u2014 a report read against the previous release is the previous
4892
4906
  release's verdict.
4893
4907
  6. \`get_binding_report\`. Alongside \`unmatched\` it now carries \`coverage\` \u2014 \`declared\` (what the
4894
- brief listed), \`bound\` (what the build declares) and \`pending\` with the reasons. Fix what it
4895
- names and release again until \`coverage.pending\` is empty.
4908
+ brief listed), \`bound\` (what the build declares) and \`pending\` with the reasons. It must read
4909
+ \`unmatched\` 0 and \`unaddressable\` under 2% of each route's visible text (\xA711). Fix what it
4910
+ names IN THE SOURCE and release again until \`coverage.pending\` is empty.
4896
4911
  7. \`set_binding_mode { declaredBindings: true }\` and release ONE MORE TIME \u2014 the mode applies to
4897
4912
  the next release, not to the one already out.
4898
4913
 
@@ -5121,6 +5136,31 @@ disagrees with the array the moment anyone drags a row in the builder.
5121
5136
  // src/prompts.ts
5122
5137
  var STRUCTURE_RULE = `### Page structure (non-negotiable)
5123
5138
  ${SECTION_DOCTRINE}`;
5139
+ var CONVERT_SITE_TEXT = (projectId) => `Convert my site so every visible element is editable in BetterCMS${projectId ? `, project ${projectId}` : ""}.
5140
+ Do not stop at the plan or at a partial receipt \u2014 finish the whole site.
5141
+
5142
+ 1. \`list_projects\`; pass \`projectId\`${projectId ? ` (${projectId})` : ""} on every project tool from here on. If the project
5143
+ is not listed the grant is on another workspace \u2014 stop and say so.
5144
+ 2. \`get_conversion_plan\`. On a 404 \`no-approved-plan\`, call
5145
+ \`get_conversion_brief { complete: true }\` and follow \`cursor\` to the LAST page, keeping every
5146
+ page \u2014 a codemod fed the capped brief converts a truncated site. Write them to \`brief.json\`.
5147
+ 3. \`pull_project_source\` (or clone the \`github\` remote it names) and work from the SOURCE, never
5148
+ from the deployed HTML.
5149
+ 4. \`npx @bettercms-ai/convert --brief brief.json --root . --receipt receipt.json\`; read \`git diff\`.
5150
+ 5. For EVERY entry of \`paths.pending\`, go to its \`fix.file\`:\`fix.line\` and do what \`fix.action\`
5151
+ says: \`wrap-span\` wrap the literal in \`<span data-bcms-field="\u2026">\`; \`declare-attr\` add
5152
+ \`data-bcms-field="\u2026"\` to that element; \`bind-expression\` replace the expression with the
5153
+ framework's bcmsField helper; \`declare-richtext\` bind the container with the richtext helper;
5154
+ \`bind-data\` bind the field in the data file it names; \`manual\` do what its \`why\` says. A row
5155
+ with no \`fix\` is still yours. Then rerun step 4 until \`paths.pending\` is EMPTY.
5156
+ 6. \`submit_conversion_receipt { briefDigest, receipt }\`. It must answer \`complete: true\`; if it
5157
+ does not, go back to step 5 \u2014 that is the only receipt that ends a conversion.
5158
+ 7. Deploy (push, or \`deploy_project\`) and poll \`get_deploy_status\` until the release is live.
5159
+ 8. \`get_binding_report\` must read \`unmatched\` 0 and \`unaddressable\` under 2% of each route's
5160
+ visible text. Fix what it names in the SOURCE, deploy, re-read (playbook section 11).
5161
+ 9. \`set_binding_mode { declaredBindings: true }\`, deploy AGAIN and poll \u2014 the mode applies to the
5162
+ NEXT release \u2014 then confirm \`get_binding_report\` reads mode "declared", unmatched 0, bound
5163
+ above 0, \`coverage.pending\` empty. That report is your final answer to me.`;
5124
5164
  var SCHEMA_PROPOSAL_FLOW = `### Whole-project design (confirm-first) \u2192 \`create_component\` / \`create_page\` / \`create_content_model\`
5125
5165
  Design the WHOLE project from its brief or its code, and **confirm the shape with the user
5126
5166
  BEFORE creating anything**. Never silently guess.
@@ -5500,6 +5540,19 @@ in the dashboard \u2014 those render as an empty string until they are published
5500
5540
  ]
5501
5541
  })
5502
5542
  );
5543
+ server.registerPrompt(
5544
+ "convert_site",
5545
+ {
5546
+ title: "Convert a site so nothing is left pending (guided)",
5547
+ description: "Run the conversion end to end: pull the source, run @bettercms-ai/convert, apply every fix it lists as pending, rerun until nothing is pending, and finish only when submit_conversion_receipt returns complete and get_binding_report reads unmatched 0.",
5548
+ argsSchema: {
5549
+ projectId: z2.string().optional().describe("the project to convert; omit if this connection is scoped to one")
5550
+ }
5551
+ },
5552
+ ({ projectId }) => ({
5553
+ messages: [{ role: "user", content: { type: "text", text: CONVERT_SITE_TEXT(projectId) } }]
5554
+ })
5555
+ );
5503
5556
  server.registerPrompt(
5504
5557
  "build_site",
5505
5558
  {