@aotter/mantle 0.0.11-alpha.63 → 0.0.11-alpha.64

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.
Files changed (41) hide show
  1. package/README.md +87 -12
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +52 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/generate.d.ts +2 -0
  7. package/dist/generate.d.ts.map +1 -0
  8. package/dist/generate.js +181 -0
  9. package/dist/generate.js.map +1 -0
  10. package/dist/skills.d.ts +2 -0
  11. package/dist/skills.d.ts.map +1 -0
  12. package/dist/skills.js +80 -0
  13. package/dist/skills.js.map +1 -0
  14. package/dist/update.d.ts +2 -0
  15. package/dist/update.d.ts.map +1 -0
  16. package/dist/update.js +387 -0
  17. package/dist/update.js.map +1 -0
  18. package/docs/adr/0001-four-atom-manifest-model.md +6 -7
  19. package/docs/adr/0007-ai-as-primary-author.md +100 -138
  20. package/docs/adr/0008-structured-diagnostic-shape.md +79 -99
  21. package/docs/adr/0009-consumer-supplied-manifests.md +101 -228
  22. package/docs/adr/0012-views-as-public-rest.md +43 -15
  23. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +43 -17
  24. package/docs/adr/0018-core-starters-repository-boundary.md +155 -0
  25. package/docs/adr/README.md +8 -6
  26. package/docs/cloudflare-low-level-composition.md +94 -0
  27. package/docs/design-atoms.md +59 -57
  28. package/docs/design-references/editorial-blog-2026-05-05.md +7 -7
  29. package/docs/labels.md +1 -1
  30. package/docs/media-uploads.md +1 -1
  31. package/docs/release-process.md +156 -523
  32. package/package.json +9 -6
  33. package/skills/README.md +20 -16
  34. package/skills/develop/SKILL.md +4 -4
  35. package/skills/install/SKILL.md +16 -2
  36. package/skills/plugin/SKILL.md +1 -1
  37. package/skills/provision/SKILL.md +1 -1
  38. package/skills/theme/SKILL.md +12 -10
  39. package/skills/update/SKILL.md +31 -19
  40. package/skills/customize-design/SKILL.md +0 -215
  41. package/skills/extend/SKILL.md +0 -257
@@ -1,257 +0,0 @@
1
- ---
2
- name: extend
3
- description: Add new functionality to an existing mantle project — a new Schema, View, Procedure, or Trigger; or wire a feature like a contact form, newsletter signup, comment thread, or filtered list page. Use when the user already has a mantle project and wants to grow it.
4
- metadata:
5
- source: "@aotter/mantle"
6
- sourcePath: skills/extend/SKILL.md
7
- applies_to: mantle@v0.1.0
8
- ---
9
-
10
- # Extend a mantle project
11
-
12
- The 4-atom manifest model:
13
-
14
- - **Schema** — the entity (table)
15
- - **View** — the read API
16
- - **Procedure** — the typed callable
17
- - **Trigger** — the event binding (HTTP / lifecycle / cron)
18
-
19
- Closed enums (`x-mantle-bind` values, `ctx.*` predicates, `Trigger.source.kind`, `Procedure.handler.kind`) are checked by `pnpm validate` — diagnostics return `code` + `suggestion`. If grammar is unclear, run `pnpm introspect` against the current project to see what the manifest compiler accepts, or read the shipped full grammar reference at `node_modules/@aotter/mantle/docs/design-atoms.md`.
20
-
21
- ## Match the user's request to atoms
22
-
23
- | User says | Add |
24
- | ------------------------------------------ | -------------------------------------------------------------------- |
25
- | "I want to publish blog posts" | Schema (`posts`) + per-locale child Schema (translates) + 2 templates |
26
- | "I want a contact form" | Schema (write target) + Procedure (handler.kind: builtin op:create) + Trigger (http POST /api/contact) |
27
- | "I want CAPTCHA / Slack notify on submit" | + Procedure (handler.kind: ref) + Trigger (lifecycle before_/after_create) |
28
- | "I want a /search page filtered by tag" | View with params: { tag } |
29
- | "I want a public prompt-generator / calculator / configurator page" | A consumer-side `app.get(...)` route in `src/index.ts` — see § Custom public routes |
30
- | "I want an API key / personal token / paid API / scoped MCP tool" | Procedure/View `requires.auth` plus optional `guard.procedure`; site-owned resolver/handler — see § API and MCP authorization |
31
- | "I want a /docs/<slug>/edit-history page" | Defer — v0.1 ships `simple` lifecycle only; `editorial` is v0.1.x |
32
- | "I want comments" | v0.1: anonymous-with-email pattern (Schema + write Procedure). End-user member system is v0.2. |
33
-
34
- If the user wants something not in the table, ask before guessing.
35
-
36
- ## Step-by-step (the canonical loop)
37
-
38
- ### 1. Write the manifest YAML
39
-
40
- Schemas / Views / Procedures / Triggers live under `manifests/`. One feature per file is fine; multi-doc YAML (`---` separators) for related atoms is also fine.
41
-
42
- Example for "newsletter signup":
43
-
44
- ```yaml
45
- # manifests/newsletter.yaml
46
- apiVersion: cms.mantle.aotter.net/v1
47
- kind: Schema
48
- metadata: { name: newsletter-signups }
49
- spec:
50
- title: Newsletter signups
51
- schema:
52
- type: object
53
- required: [email]
54
- properties:
55
- email: { type: string, format: email }
56
- createdAt: { type: number, x-mantle-bind: now }
57
- uniqueIndexes: [[email]]
58
- lifecycle: simple
59
- ---
60
- apiVersion: cms.mantle.aotter.net/v1
61
- kind: Procedure
62
- metadata: { name: subscribe }
63
- spec:
64
- input:
65
- type: object
66
- required: [email]
67
- properties:
68
- email: { type: string, format: email }
69
- output: { type: object }
70
- handler:
71
- kind: builtin
72
- op: create
73
- schema: newsletter-signups
74
- ---
75
- apiVersion: cms.mantle.aotter.net/v1
76
- kind: Trigger
77
- metadata: { name: subscribe-http }
78
- spec:
79
- source: { kind: http, method: POST, path: /api/subscribe }
80
- target: { procedure: subscribe }
81
- ```
82
-
83
- ### 2. Validate immediately
84
-
85
- ```bash
86
- pnpm validate
87
- ```
88
-
89
- Common diagnostics:
90
-
91
- - `DRAFT_KEY_USED` — you wrote a v0.2+ key (e.g. `Trigger.source.kind: cron`); remove it.
92
- - `LIFECYCLE_NOT_IN_V010` — you wrote `Schema.spec.lifecycle: editorial`; v0.1.0 only ships `simple`.
93
- - `VIEW_FILTER_PARAM_REF_NOT_REQUIRED` — you reference `{ $param: x }` but `x` is optional; add to `params.required`.
94
- - `VIEW_PARAMS_RESERVED_NAME` — you declared `params.page` / `.show` / `.cursor`; rename — runtime owns those.
95
- - `TRIGGER_TARGET_PROCEDURE_UNKNOWN` — typo in `target.procedure`.
96
- - `HANDLER_NOT_REGISTERED` — Procedure declares `handler.kind: ref` but no `registerHandler('<ref>', fn)` call exists in `src/`.
97
-
98
- ### 3. Wire any handler refs
99
-
100
- For `Procedure.handler.kind: ref`:
101
-
102
- ```ts
103
- // src/handlers.ts
104
- export const handlers = {
105
- subscribe: async (input) => { /* ... */ return { ok: true }; },
106
- };
107
- ```
108
-
109
- The CLI greps `src/` for the literal string `'<ref>'` — keep registration somewhere greppable (object literal or `registerHandler('subscribe', fn)` call).
110
-
111
- ### 4. Register a template if the Schema needs HTML output
112
-
113
- ```ts
114
- // src/templates/index.ts
115
- import { newsletterTemplate } from "./newsletter.js";
116
- registry.registerEntryTemplate("newsletter-signups", newsletterTemplate);
117
- ```
118
-
119
- Skip this if the Schema is internal-only (raw form submissions don't need HTML pages).
120
-
121
- ### 5. Re-emit types + OpenAPI (optional)
122
-
123
- ```bash
124
- pnpm emit-types # adds ProcInput_subscribe / ProcOutput_subscribe / Entry_newsletter_signups
125
- pnpm emit-openapi # adds POST /api/subscribe operation
126
- ```
127
-
128
- Commit both alongside the manifest changes if you keep artifacts under version control.
129
-
130
- ### 6. Try it locally
131
-
132
- ```bash
133
- pnpm fixture # only if you added new fixture data
134
- pnpm dev # restart wrangler
135
- curl -X POST http://localhost:8787/api/subscribe \
136
- -H 'content-type: application/json' \
137
- -d '{"email":"alice@example.com"}'
138
- ```
139
-
140
- Use `?preview=1` on any `/posts/` or `/pages/` URL to render an in-progress draft via the registered template instead of pre-rendered KV HTML.
141
-
142
- ### 7. Run the integration smokes
143
-
144
- ```bash
145
- pnpm view-smoke # 10 cases against /api/views/*
146
- pnpm mcp-smoke # 12 cases against /mcp
147
- ```
148
-
149
- If you added a new MCP-relevant Schema, per-collection authoring tools auto-emit:
150
- `create_draft_<segment>` / `update_draft_<segment>` for content lifecycles,
151
- or `create_record_<segment>` / `update_record_<segment>` for
152
- `lifecycle: none`. Verify the actual surface with `tools/list`.
153
-
154
- ## API and MCP authorization
155
-
156
- Read the shipped canonical guide before adding an authenticated public API:
157
- `node_modules/@aotter/mantle/docs/api-mcp-authorization.md`.
158
-
159
- Use only the closed grammar:
160
-
161
- ```yaml
162
- requires:
163
- auth:
164
- all:
165
- - ctx.auth
166
- - { "ctx.auth.scope": "orders:read" }
167
- guard:
168
- procedure: require-active-access
169
- ```
170
-
171
- - `ctx.auth` means any adapter-verified credential; it is not API-key-only.
172
- - Repeat `ctx.auth.scope` to require multiple site-owned scopes.
173
- - Use `ctx.user` as well when a user subject is required.
174
- - Put current payment, transaction, membership, ownership, or other business
175
- state in one site-owned `handler.kind: ref` guard Procedure. It receives the
176
- validated target input/params and the same `HandlerContext`.
177
- - Put API-key/PAT recognition, hashing, revocation, and normalization in the
178
- Cloudflare `credentialResolver`. Do not add Core tables, repositories, or a
179
- generic entitlement layer.
180
- - Bind the same Procedure to HTTP and MCP Triggers when both transports should
181
- expose it. Standard remote MCP uses OAuth; it does not promise to send a raw
182
- REST API key/PAT. Runtime predicates and the guard are shared after caller
183
- normalization.
184
-
185
- Expected diagnostics are `UNAUTHENTICATED`/401 for no valid credential,
186
- `AUTH_DENIED`/403 for a verified caller missing role/scope, and
187
- `ENTITLEMENT_REQUIRED`/402 when a site guard denies current access. Re-run the
188
- guide's focused integration command after changing manifests or auth wiring.
189
-
190
- ## Custom public routes (consumer-app freedom)
191
-
192
- The starter owns its `Hono` app instance. If the user wants a public surface that doesn't fit the 4-atom model — a prompt generator, calculator, configurator, starter directory browser, small interactive widget — add a route directly in `src/index.ts`:
193
-
194
- ```ts
195
- // src/index.ts
196
- import { runtimeRef } from "./bootstrap.js";
197
-
198
- app.get("/:locale/tools/prompt-generator", async (c) => {
199
- const runtime = await runtimeRef.get();
200
- const profiles = await runtime.executeView.execute({
201
- view: runtime.viewsByName.get("starter_profiles_active")!,
202
- });
203
- return c.html(renderPromptGenerator(profiles, c.req.param("locale")));
204
- });
205
- ```
206
-
207
- This is consumer-app territory, NOT an SDK feature. The SDK doesn't ship a `customRoutes.ts` declarative API or a type-safe context wrapper — the starter's `Hono` app + `runtime` access via `ref.get()` is enough.
208
-
209
- SDK mounts (`mountServerEndpoints`, `mountPublicRoutes`, `mountAuthorize`) register their routes early on the Hono `defaultHandler`; consumer `app.get(...)` calls register on the same Hono instance. MCP endpoints are NOT mounted into Hono — they live as `apiHandlers` on the top-level `createOAuthProvider({...})` instance (`/mcp/staff`, `/mcp`), so they share no route table with public Hono routes and can't collide with consumer paths. The `/:locale` param route in `mountPublicRoutes` 404s on unknown locales, so paths under `/tools/...`, `/api/foo`, `/calc`, etc. won't collide as long as your prefix isn't a declared site locale.
210
-
211
- Right tool when: the user wants ONE public page that doesn't read entries or carry workflow. Wrong tool when: you find yourself reimplementing CRUD, list pagination, or auth gating — those are atom-shaped.
212
-
213
- Don't fork core templates (post, postList, page, home, contact, notFound) just to add an unrelated public page. Add the route, leave the templates untouched.
214
-
215
- ## Diagnostic recipes
216
-
217
- | Symptom | Likely cause |
218
- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
219
- | `INPUT_VALIDATION_FAILED` on a Trigger POST | Body doesn't match `Procedure.spec.input`. Check `pnpm introspect` for the schema. |
220
- | `AUTH_DENIED` (403) on a contact-form-shaped POST | A `before_create` lifecycle Trigger threw. The diagnostic's `path` names the Procedure. |
221
- | MCP `tools/list` doesn't show your new collection | Added a Schema but didn't restart wrangler dev. The mount layer caches per-isolate. |
222
- | New View doesn't appear at `/api/views/<name>` | Same as above — restart. Or: name has uppercase / non-URL-safe chars (use kebab-case). |
223
- | `VIEW_FILTER_FIELD_NOT_IN_SCHEMA` for a real field | The Schema is referenced via `View.spec.from`; field must be in that Schema's `properties`. |
224
-
225
- ## Live-render dev mode
226
-
227
- Set `MANTLE_LOCAL_DEV=1` in `.dev.vars` (the starter ships this on by default). The worker bypasses the KV cache for `post` / `postList` / `page` routes and re-renders via the registered templates against current D1 state on every request. Edit `Header.tsx` / `Layout.tsx` / `styles.ts` / `i18n/*.json` and reload — every page reflects the change immediately, no `pnpm fixture` rebake.
228
-
229
- Production / CI: leave `MANTLE_LOCAL_DEV` unset so the publish-pipeline path is exercised.
230
-
231
- ## Stale-KV gotcha (when you change shared chrome)
232
-
233
- The starter renders **registered templates** (post / postList / page) at publish time and caches the HTML in KV. **Request-time templates** (home / contact / notFound) compose fresh on each request.
234
-
235
- If you change any module-init resolved chrome — `src/theme.default/components/Layout.tsx`, `PageShell.tsx`, `Header.tsx`, `Footer.tsx`, `styles.ts`, or `src/i18n/*.json` — or fork any of those into `src/theme/` — the new chrome shows on home / contact / notFound immediately, but post / postList / page keep serving the OLD chrome from KV until re-publish. PageShell is on the same module-init slot-resolution path as Header/Footer, so a forked PageShell triggers the same behavior even though the override lives at a different layer.
236
-
237
- Local dev fix: `pnpm fixture` rebakes everything from seed data.
238
-
239
- Production fix: iterate every published entry and call `runtime.requestPublish.execute({ id })`. A `mantle republish-all` CLI is on the v0.1.x roadmap; until then, a one-shot script that pulls `runtime.listEntries` for every collection and re-publishes each row is the right pattern.
240
-
241
- ## Don't
242
-
243
- - Don't add a Schema-level public-read flag (`Schema.spec.expose.rest` etc) — public reads always go through Views.
244
- - Don't add a non-`$param` filter sentinel (`{ $env: ... }`, `{ $cookie: ... }`, `{ $now }`) — none are in v0.1.
245
- - Don't bypass the chokepoint by writing to D1 directly — every mutation MUST go through `runtime.entries` (lifecycle hooks fire there).
246
- - Don't use `Trigger.source.kind: cron / queue` — DRAFT, parser rejects. MCP is
247
- shipped; declare `source: { kind: mcp, surface: public | staff }` and bind it
248
- to a declared Procedure.
249
- - Don't use `Procedure.spec.requires.window` / `.quota` — DRAFT.
250
- - Don't write a Procedure with `handler.kind: builtin` and `op: archive` on a `lifecycle: simple` Schema — boot rejects (archive is editorial-only).
251
- - Don't paste secrets into a manifest (`requires.auth.all` carries predicates only). Secrets go in `wrangler secret put`.
252
-
253
- ## When you're done
254
-
255
- 1. Show the user the new endpoint(s) — `curl` example for each.
256
- 2. Show the diff of `openapi.json` if you re-emitted it (number of new operationIds).
257
- 3. If the new feature has a UI dimension (template, post page, list page) — visually verify in the dev server before claiming done. UI changes can't be type-checked.