@aotter/mantle 0.1.0-alpha.9 → 0.1.2-alpha.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.
Files changed (109) hide show
  1. package/README.md +38 -38
  2. package/dist/cli/generate.d.ts +10 -1
  3. package/dist/cli/generate.d.ts.map +1 -1
  4. package/dist/cli/generate.js +49 -4
  5. package/dist/cli/generate.js.map +1 -1
  6. package/dist/cli/main.js +0 -8
  7. package/dist/cli/main.js.map +1 -1
  8. package/dist/codegen/emitMantleModule.js +8 -0
  9. package/dist/codegen/emitMantleModule.js.map +1 -1
  10. package/docs/adapter-guide.md +23 -26
  11. package/docs/adr/0008-structured-diagnostic-shape.md +7 -2
  12. package/docs/adr/0010-locale-and-translates.md +14 -16
  13. package/docs/adr/0011-adapter-port-spec.md +2 -1
  14. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +100 -0
  15. package/docs/adr/0018-core-starters-repository-boundary.md +3 -0
  16. package/docs/adr/0019-sealed-manifest-runtime-pipeline.md +38 -2
  17. package/docs/adr/0020-builtin-handler-contracts-and-matched-upsert.md +199 -0
  18. package/docs/adr/0021-retire-starter-scaffolding.md +30 -0
  19. package/docs/adr/0022-caller-observed-version-occ.md +212 -0
  20. package/docs/adr/0023-port-failure-contract.md +69 -0
  21. package/docs/adr/0024-manifest-native-schema-tables.md +260 -0
  22. package/docs/adr/README.md +19 -3
  23. package/docs/adr/adr-lite-803-request-diagnostics.md +38 -0
  24. package/docs/adr/adr-lite-808-route-readiness.md +47 -0
  25. package/docs/adr/adr-lite-809-bounded-public-content.md +71 -0
  26. package/docs/adr/adr-lite-812-native-parity.md +150 -0
  27. package/docs/adr/adr-lite-823-home-statistics.md +64 -0
  28. package/docs/adr/adr-lite-842-mcp-authoring.md +30 -0
  29. package/docs/adr/adr-lite-861-admin-webmcp.md +42 -0
  30. package/docs/adr/adr-lite-909-admin-ui-kit.md +37 -0
  31. package/docs/api-mcp-authorization.md +3 -664
  32. package/docs/auth-hosting-model.md +4 -4
  33. package/docs/cloudflare-low-level-composition.md +3 -104
  34. package/docs/deferred-lifecycle-queues.md +2 -243
  35. package/docs/design-atoms.md +16 -796
  36. package/docs/direct-authoring.md +5 -0
  37. package/docs/examples/minimal-worker/README.md +37 -0
  38. package/docs/examples/minimal-worker/manifests/site.yaml +25 -0
  39. package/docs/examples/minimal-worker/package.json +26 -0
  40. package/docs/examples/minimal-worker/smoke.mjs +23 -0
  41. package/docs/examples/minimal-worker/src/index.ts +4 -0
  42. package/docs/examples/minimal-worker/tsconfig.json +17 -0
  43. package/docs/examples/minimal-worker/wrangler.jsonc +22 -0
  44. package/docs/handbook/cloudflare/authentication.md +167 -0
  45. package/docs/handbook/cloudflare/bindings.md +236 -0
  46. package/docs/handbook/cloudflare/conventional-worker.md +162 -0
  47. package/docs/handbook/cloudflare/deferred-hooks-queues.md +171 -0
  48. package/docs/handbook/cloudflare/deploy-and-operate.md +105 -0
  49. package/docs/handbook/cloudflare/low-level-composition.md +169 -0
  50. package/docs/handbook/cloudflare/media-r2.md +216 -0
  51. package/docs/handbook/cloudflare/public-web.md +144 -0
  52. package/docs/handbook/concepts/authorization.md +116 -0
  53. package/docs/handbook/concepts/four-atoms.md +173 -0
  54. package/docs/handbook/concepts/lifecycle-and-locales.md +142 -0
  55. package/docs/handbook/concepts/mcp-and-agents.md +109 -0
  56. package/docs/handbook/concepts/procedures-and-triggers.md +221 -0
  57. package/docs/handbook/concepts/runtime-and-adapters.md +177 -0
  58. package/docs/handbook/concepts/views.md +191 -0
  59. package/docs/handbook/examples/commerce-transaction.md +813 -0
  60. package/docs/handbook/examples/guarded-api.md +427 -0
  61. package/docs/handbook/examples/intake-form.md +319 -0
  62. package/docs/handbook/examples/legal-documents.md +218 -0
  63. package/docs/handbook/examples/procurement-approvals.md +240 -0
  64. package/docs/handbook/examples/publication.md +240 -0
  65. package/docs/handbook/examples/reservation.md +220 -0
  66. package/docs/handbook/navigation.json +63 -0
  67. package/docs/handbook/reference/authorization.md +295 -0
  68. package/docs/handbook/reference/diagnostics.md +183 -0
  69. package/docs/handbook/reference/manifest.md +132 -0
  70. package/docs/handbook/reference/procedure.md +263 -0
  71. package/docs/handbook/reference/schema.md +250 -0
  72. package/docs/handbook/reference/site-config.md +233 -0
  73. package/docs/handbook/reference/surface.md +207 -0
  74. package/docs/handbook/reference/trigger.md +193 -0
  75. package/docs/handbook/reference/view.md +256 -0
  76. package/docs/handbook/start/project-and-cli.md +122 -0
  77. package/docs/handbook/start/quickstart-worker.md +193 -0
  78. package/docs/labels.md +3 -1
  79. package/docs/media-uploads.md +3 -184
  80. package/docs/migration-0.1.2.md +60 -3
  81. package/docs/performance-harness.md +127 -10
  82. package/docs/release-process.md +85 -231
  83. package/docs/schema-indexes.md +3 -180
  84. package/docs/sealed-pipeline-ownership.md +6 -5
  85. package/docs/spec-only-host-adoption.md +158 -0
  86. package/docs/transaction-patterns.md +5 -0
  87. package/package.json +21 -25
  88. package/skills/README.md +4 -4
  89. package/skills/develop/SKILL.md +23 -28
  90. package/skills/install/SKILL.md +55 -147
  91. package/skills/provision/SKILL.md +4 -3
  92. package/skills/theme/SKILL.md +6 -6
  93. package/skills/update/SKILL.md +32 -68
  94. package/dist/cli/create.d.ts +0 -2
  95. package/dist/cli/create.d.ts.map +0 -1
  96. package/dist/cli/create.js +0 -243
  97. package/dist/cli/create.js.map +0 -1
  98. package/dist/cli/update.d.ts +0 -2
  99. package/dist/cli/update.d.ts.map +0 -1
  100. package/dist/cli/update.js +0 -413
  101. package/dist/cli/update.js.map +0 -1
  102. package/dist/provision/renderProvisionBundle.d.ts +0 -70
  103. package/dist/provision/renderProvisionBundle.d.ts.map +0 -1
  104. package/dist/provision/renderProvisionBundle.js +0 -367
  105. package/dist/provision/renderProvisionBundle.js.map +0 -1
  106. package/dist/provision.d.ts +0 -2
  107. package/dist/provision.d.ts.map +0 -1
  108. package/dist/provision.js +0 -2
  109. package/dist/provision.js.map +0 -1
@@ -0,0 +1,256 @@
1
+ ---
2
+ description: View field reference — declarative and SQL forms, filter AST, params, pagination, REST and MCP surfaces, and every diagnostic they raise.
3
+ ---
4
+ # View
5
+
6
+ A View is a named read-only query over Schemas. It is the only atom that needs no [Trigger](./trigger.md): declaring `surface` mounts it. This page is the field-level contract; the concepts are in [Views](../concepts/views.md) and [The four atoms](../concepts/four-atoms.md). Envelope rules are in [Manifest envelope and conventions](./manifest.md), and every diagnostic code named here is catalogued in [Diagnostics](./diagnostics.md).
7
+
8
+ ## Fields
9
+
10
+ | Field | Type | Required | Default | Rules |
11
+ |---|---|---|---|---|
12
+ | `title` | LocalizedText | no | Title-Cased `metadata.name` | Admin report label. Non-empty string or locale map. |
13
+ | `uiSchema` | object | no | — | Only on `surface: staff`; only the key `list`. Violations are `VIEW_UI_INVALID`. |
14
+ | `from` | string | exactly one of `from` / `sql` | — | Name of a declared Schema (`VIEW_FROM_UNKNOWN_SCHEMA`). The declarative form. |
15
+ | `sql` | string | exactly one of `from` / `sql` | — | One SQLite `SELECT`. See [`sql`](#sql). |
16
+ | `surface` | `public` \| `staff` | yes | — | Decides where the View mounts. See [Surfaces](#surfaces). |
17
+ | `cache` | `{ sharedMaxAge }` | no | — | Anonymous REST shared-cache hint. `sharedMaxAge` is an integer from 1 to 86400. Only an unguarded, declarative public View over a publishing Schema may declare it. |
18
+ | `requires` | AuthorizationRequirements | no | — | `auth.all` predicates plus one optional `guard.procedure`. See [Authorization](./authorization.md). |
19
+ | `filter` | FilterAst | no | — | `from` form only. See [Filter AST](#filter-ast). |
20
+ | `fields` | `string[]` | no | every column | `from` form only. Projection. Not shape-validated by the parser. |
21
+ | `orderBy` | `{ field, direction? }[]` | no | `[]` | `from` form only. `direction` defaults to `asc`. |
22
+ | `limit` | number | no | 50 at runtime | `from` and `sql`. Not shape-validated by the parser; clamped at request time. |
23
+ | `params` | JSON Schema | no | — | `type: object` with `properties`. Reserved: `page`, `show`, `cursor`. |
24
+
25
+ `from` counts as present when it is a non-empty string; `sql` when it is non-empty after trimming. Declaring both, or neither, is `INVALID_MANIFEST_ENVELOPE` at `/spec` with the message *View.spec requires exactly one of from or sql*. Combining `sql` with `filter`, `fields` or `orderBy` is rejected the same way at `/spec/<key>`.
26
+
27
+ ## Declarative example
28
+
29
+ ```yaml
30
+ apiVersion: cms.mantle.aotter.net/v1
31
+ kind: View
32
+ metadata:
33
+ name: my-support-requests
34
+ spec:
35
+ title: { en: My support requests, "zh-TW": 我的客服請求 }
36
+ surface: public
37
+ from: support-requests
38
+ requires:
39
+ auth:
40
+ all: [ctx.user]
41
+ fields: [id, ticketNumber, subject, requestStatus, submittedAt]
42
+ filter:
43
+ and:
44
+ - eq: { field: submittedBy, value: { "$ctx.user": "id" } }
45
+ - eq: { field: requestStatus, value: { $param: requestStatus } }
46
+ orderBy:
47
+ - { field: submittedAt, direction: desc }
48
+ limit: 100
49
+ params:
50
+ type: object
51
+ required: [requestStatus]
52
+ properties:
53
+ requestStatus: { type: string, enum: [open, waiting, closed] }
54
+ ```
55
+
56
+ The Schema this reads must index `submittedBy` as the leftmost field of some tuple, otherwise the identity filter is rejected. See [Schema indexes](./schema.md#indexes).
57
+
58
+ For caller-independent published data, a View may opt its anonymous REST response into the deployment cache:
59
+
60
+ ```yaml
61
+ spec:
62
+ surface: public
63
+ from: published-notes
64
+ cache: { sharedMaxAge: 3600 }
65
+ ```
66
+
67
+ This emits `Cache-Control: public, max-age=0, s-maxage=3600` only when the request has no cookie or authorization header and the Cloudflare Worker has a valid `cacheScope`. MCP, WebMCP, staff, guarded, SQL and operational-schema reads remain uncached. Invalid combinations fail with `VIEW_CACHE_INVALID`.
68
+
69
+ ## SQL example
70
+
71
+ ```yaml
72
+ apiVersion: cms.mantle.aotter.net/v1
73
+ kind: View
74
+ metadata:
75
+ name: order-lines-by-status
76
+ spec:
77
+ title: Order lines by status
78
+ surface: staff
79
+ sql: |
80
+ SELECT o._mantle_id AS orderId,
81
+ o.orderNumber AS orderNumber,
82
+ json_extract(line.value, '$.sku') AS sku,
83
+ json_extract(line.value, '$.quantity') AS quantity
84
+ FROM orders AS o
85
+ JOIN json_each(o.lines) AS line
86
+ WHERE o.orderStatus = :orderStatus
87
+ ORDER BY o.orderNumber ASC
88
+ params:
89
+ type: object
90
+ required: [orderStatus]
91
+ properties:
92
+ orderStatus: { type: string, enum: [paid, shipped, cancelled] }
93
+ limit: 200
94
+ uiSchema:
95
+ list:
96
+ columns: [orderNumber, sku, quantity]
97
+ searchFields: [orderNumber, sku]
98
+ filterFields: [sku]
99
+ ```
100
+
101
+ ## Filter AST
102
+
103
+ `filter` is a tree. Every node is an object with **exactly one** key: a comparison operator, or `and` / `or`.
104
+
105
+ | Key | Shape | Rules |
106
+ |---|---|---|
107
+ | `eq`, `gt`, `gte`, `lt`, `lte` | `{ field, value }` | Only those two keys. `field` is a non-empty string. `value` must be present; `null` is a legal value. |
108
+ | `and`, `or` | array of nodes | Non-empty. Nests to any depth. |
109
+
110
+ Any other key, a node with zero or several keys, an array node, or an empty `and` / `or` is `INVALID_MANIFEST_ENVELOPE` at the node's pointer.
111
+
112
+ ### Value forms
113
+
114
+ | Form | Written as | Rules |
115
+ |---|---|---|
116
+ | Literal | `value: published` | Any JSON scalar, including `null`. Compared as written. |
117
+ | Param reference | `value: { $param: locale }` | The name must be declared under `params.properties` (`VIEW_FILTER_PARAM_REF_UNKNOWN`, also raised when no `params` is declared at all or the name is empty) and listed in `params.required` (`VIEW_FILTER_PARAM_REF_NOT_REQUIRED`). |
118
+ | Caller identity | `value: { "$ctx.user": "id" }` | The sentinel is closed: exactly one key, the literal string `id`, and only under `eq`. Anything else is `VIEW_FILTER_CTX_USER_REF_INVALID`. |
119
+
120
+ The identity sentinel carries two further graph-level obligations, checked once `from` resolves:
121
+
122
+ | Rule | Diagnostic |
123
+ |---|---|
124
+ | The View declares `ctx.user` in `requires.auth.all`. | `VIEW_FILTER_CTX_USER_REF_REQUIRES_AUTH` at `/spec/requires/auth/all` |
125
+ | The compared field is the leftmost field of some `uniqueIndexes` or `indexes` tuple on the source Schema. | `VIEW_FILTER_CTX_USER_REF_REQUIRES_INDEX` |
126
+
127
+ Provider claims and platform identities are deliberately out of reach; `id` is the only bindable identity value.
128
+
129
+ ### Graph-level field checks
130
+
131
+ For a `from` View, the valid field names are the top-level keys of the Schema's `properties` plus the [reserved entry columns](./schema.md#reserved-entry-columns). Unknown names are rejected per site:
132
+
133
+ | Location | Diagnostic |
134
+ |---|---|
135
+ | `filter.<op>.field` | `VIEW_FILTER_FIELD_NOT_IN_SCHEMA` |
136
+ | `fields[i]`, `orderBy[i].field` | `VIEW_FIELD_NOT_IN_SCHEMA` |
137
+ | `uiSchema.list.<key>[i]` | `VIEW_UI_INVALID` |
138
+ | `from` itself | `VIEW_FROM_UNKNOWN_SCHEMA` (no further field checks run) |
139
+
140
+ None of these run for a `sql` View — its output columns are whatever the `SELECT` produces.
141
+
142
+ ## `sql`
143
+
144
+ One statement, read-only, compiled and bound by the runtime.
145
+
146
+ | Rule | Effect |
147
+ |---|---|
148
+ | The trimmed text matches `/^select\b/i` and contains no `;`. | Otherwise `INVALID_MANIFEST_ENVELOPE` at `/spec/sql`: `View.spec.sql must be one SELECT statement without a semicolon`. |
149
+ | Every `:name` occurrence is declared in `params.properties`. | `VIEW_FILTER_PARAM_REF_UNKNOWN` |
150
+ | Every `:name` occurrence is listed in `params.required`. | `VIEW_FILTER_PARAM_REF_NOT_REQUIRED` |
151
+ | `filter`, `fields` and `orderBy` are absent. | `INVALID_MANIFEST_ENVELOPE` at `/spec/<key>` |
152
+ | Tables are Schema names. | Each Schema is exposed as a logical table reconciled at boot. Names containing `-` must be double-quoted: `FROM "post-translations"`. |
153
+
154
+ Bound params are passed as positional values; caller input is never interpolated into the statement. SQLite JSON functions are available, so `json_each` and `json_extract` can unnest and project array or object members of `data` — the SQL example above does both.
155
+
156
+ > **Warning**
157
+ > `sql` Views are native SQLite. Static validation never executes the statement, so a syntax or column error surfaces only when the View runs. On a storage adapter that does not support the native dialect the View fails at prepare time with `VIEW_DIALECT_UNSUPPORTED`, naming the dialects that adapter does support.
158
+
159
+ ## `params`
160
+
161
+ `params` declares the caller-supplied query shape and is walked by the [JSON Schema subset](./schema.md#json-schema-subset) validator.
162
+
163
+ | Rule | Diagnostic |
164
+ |---|---|
165
+ | A non-array object. | `VIEW_PARAMS_INVALID_SHAPE` at `/spec/params` |
166
+ | `type: "object"`. | `VIEW_PARAMS_INVALID_SHAPE` at `/spec/params/type` |
167
+ | `properties` is declared and is an object. | `VIEW_PARAMS_INVALID_SHAPE` at `/spec/params/properties` |
168
+ | No property named `page`, `show` or `cursor`. | `VIEW_PARAMS_RESERVED_NAME` |
169
+
170
+ The runtime owns those three names for pagination, which is why they cannot be redeclared. Rename the domain param (`pageSize`, `showArchived`).
171
+
172
+ ## `orderBy`, `fields` and `limit`
173
+
174
+ `orderBy` is an array of objects accepting only `field` and `direction`. A non-array value, a non-object entry, a missing or empty `field`, or a `direction` other than `asc` / `desc` is `VIEW_ORDERBY_INVALID` at the offending pointer; an unrecognized key inside an entry is `INVALID_MANIFEST_ENVELOPE`.
175
+
176
+ `fields` and `limit` receive **no shape validation in the parser**. A `fields` value that is not an array of strings, or a `limit` that is not a number, is not reported as a diagnostic — it fails later, at graph validation or at request time. Declare them as documented.
177
+
178
+ `limit` is a per-View cap, and the runtime clamps around it on every call:
179
+
180
+ | Input | Result |
181
+ |---|---|
182
+ | `limit` missing, non-numeric, non-finite or `<= 0` | Cap is 50. |
183
+ | `limit` valid | Cap is `min(floor(limit), 500)`. 500 is the hard ceiling for any single round-trip. |
184
+ | `?show=` missing or not a positive finite number | Page size is the cap. |
185
+ | `?show=` valid | Page size is `min(floor(show), cap)`. |
186
+ | `?page=` missing or below 1 | Page 1. |
187
+
188
+ ## `uiSchema.list`
189
+
190
+ Admin presentation for `surface: staff` Views. Declaring `uiSchema` on a public View is `VIEW_UI_INVALID`, as is any root key other than `list` or any key inside `list` other than the three below.
191
+
192
+ | Key | Meaning |
193
+ |---|---|
194
+ | `columns` | Ordered columns for the Admin report table and the default CSV column set. |
195
+ | `searchFields` | Output fields the Admin substring search box covers. |
196
+ | `filterFields` | Output fields offered as exact-match filters (`?filter.<field>=`). |
197
+
198
+ Each is an array of non-empty strings with no duplicates within the key. The characters `"`, `\` and NUL are rejected in a field name. The names are **View output field names** — SQL aliases for a `sql` View; for a `from` View they are additionally checked against the Schema's properties plus reserved columns.
199
+
200
+ Admin applies search and filters before pagination, rejecting a search term or filter value longer than 200 characters and any `filter.<field>` key that is not a declared `filterFields` entry. `GET /admin/api/views/<name>/export` streams the same query as CSV covering every matching row, not only the visible page; its columns come from `uiSchema.list.columns`, falling back to `spec.fields` and then to the union of keys in the returned rows.
201
+
202
+ ## Surfaces
203
+
204
+ | `surface` | REST | MCP tool | Admin |
205
+ |---|---|---|---|
206
+ | `public` | `GET /api/views/<name>`, plus a catalog at `GET /api/views` | `query_view_<segment>` on `/mcp` | Also mounted at `GET /admin/api/views/<name>` and `/export` behind the staff gate |
207
+ | `staff` | `GET /admin/api/views/<name>` and `/admin/api/views/<name>/export` — not mounted publicly | `query_view_<segment>` on `/mcp/staff` | Report sidebar |
208
+
209
+ `<segment>` is `metadata.name` lower-cased with `-` replaced by `_`. Two Views that mangle to the same segment collide with `MCP_TOOL_NAME_COLLISION`. Admin also serves the manifest listing `GET /admin/api/views-manifest`. Surface choice is visibility, not authorization: `requires` still gates every call on both transports. See [Surfaces](./surface.md) and [MCP and agents](../concepts/mcp-and-agents.md).
210
+
211
+ The MCP `inputSchema` is `params.properties` plus `page` and `show` as optional numbers, carrying `params.required` through unchanged; the tool is annotated `readOnlyHint: true`.
212
+
213
+ ## REST contract
214
+
215
+ Pagination uses the two reserved knobs, `?page=` (1-indexed) and `?show=`. The response envelope is:
216
+
217
+ ```json
218
+ { "ok": true, "data": { "rows": [], "page": 1, "show": 20, "hasMore": true } }
219
+ ```
220
+
221
+ `hasMore` is the lazy form — `rows.length === show`. There is no COUNT query and no `LIMIT n+1` probe, so a final page that exactly fills `show` reports `hasMore: true` and the next page comes back empty.
222
+
223
+ A failure returns `{ ok: false, diagnostic }` with the diagnostic's mapped status. Static `requires.auth` runs before parameter validation, so an unauthorized caller never learns the parameter shape; a `guard` Procedure runs after validation and authorizes the whole query rather than filtering rows.
224
+
225
+ ### Param coercion
226
+
227
+ Query strings arrive as text. The runtime coerces each declared param by its `type` before validating against `params`; MCP callers send typed JSON and skip this step.
228
+
229
+ | Declared `type` | Coercion | Rejected when |
230
+ |---|---|---|
231
+ | `string`, or `type` omitted | Used as-is. | — |
232
+ | `integer` | `parseInt(raw, 10)` | The round-trip does not equal the trimmed input, so `"1.5"` and `"1abc"` fail. |
233
+ | `number` | `Number(raw)` | The result is not finite. |
234
+ | `boolean` | `"true"` / `"false"` | Any other text. |
235
+ | `enum` (with any of the above) | Coerced by `type` first, then checked for membership. | The coerced value is not in `enum`. |
236
+ | anything else | — | Unsupported on the REST surface. |
237
+
238
+ A missing required param or a failed coercion is `INPUT_VALIDATION_FAILED` (400). Unknown query keys are ignored.
239
+
240
+ ## Source
241
+
242
+ - [`packages/mantle-spec/src/domain/model/ManifestGrammar.ts`](../../../packages/mantle-spec/src/domain/model/ManifestGrammar.ts)
243
+ - [`packages/mantle-spec/src/domain/service/ManifestParser.ts`](../../../packages/mantle-spec/src/domain/service/ManifestParser.ts)
244
+ - [`packages/mantle-spec/src/domain/service/ManifestGraphValidator.ts`](../../../packages/mantle-spec/src/domain/service/ManifestGraphValidator.ts)
245
+ - [`packages/mantle-spec/src/domain/service/SchemaAdminUiChecker.ts`](../../../packages/mantle-spec/src/domain/service/SchemaAdminUiChecker.ts)
246
+ - [`packages/mantle-spec/src/domain/service/McpToolNaming.ts`](../../../packages/mantle-spec/src/domain/service/McpToolNaming.ts)
247
+ - [`packages/mantle-runtime/src/usecase/view/ExecuteViewUseCase.ts`](../../../packages/mantle-runtime/src/usecase/view/ExecuteViewUseCase.ts)
248
+ - [`packages/mantle-runtime/src/domain/service/ViewParamCoercer.ts`](../../../packages/mantle-runtime/src/domain/service/ViewParamCoercer.ts)
249
+ - [`packages/mantle-runtime/src/domain/service/Pagination.ts`](../../../packages/mantle-runtime/src/domain/service/Pagination.ts)
250
+ - [`packages/mantle-runtime/src/domain/service/CallableCapabilityProjector.ts`](../../../packages/mantle-runtime/src/domain/service/CallableCapabilityProjector.ts)
251
+ - [`packages/mantle-runtime/src/infrastructure/http/createMantleRequestHandler.ts`](../../../packages/mantle-runtime/src/infrastructure/http/createMantleRequestHandler.ts)
252
+ - [`packages/mantle-runtime/src/infrastructure/storage/SqliteViewCompiler.ts`](../../../packages/mantle-runtime/src/infrastructure/storage/SqliteViewCompiler.ts)
253
+ - [`packages/mantle-runtime/src/infrastructure/storage/SqliteMantleStorageAdapter.ts`](../../../packages/mantle-runtime/src/infrastructure/storage/SqliteMantleStorageAdapter.ts)
254
+ - [`packages/mantle-runtime/src/usecase/boot/ValidateBootUseCase.ts`](../../../packages/mantle-runtime/src/usecase/boot/ValidateBootUseCase.ts)
255
+ - [`packages/mantle-admin/src/mountMantleAdmin.ts`](../../../packages/mantle-admin/src/mountMantleAdmin.ts)
256
+ - [`packages/adapters/cloudflare/src/mount/mountRuntimeEndpoints.ts`](../../../packages/adapters/cloudflare/src/mount/mountRuntimeEndpoints.ts)
@@ -0,0 +1,122 @@
1
+ ---
2
+ description: The files you own in a Mantle project, every mantle and mantle-harness command with its flags, the generated module, the daily check loop, and upgrade rules.
3
+ ---
4
+ # Project layout and the CLI loop
5
+
6
+ This page describes a directly authored Mantle project: which files are yours, what the installed CLI does to them, and the loop you run before every commit. It is for engineers and coding agents working in an existing project.
7
+
8
+ ## You own the project
9
+
10
+ Core supplies a manifest compiler and a runtime, not a project generator. Since [ADR-0021](../../../docs/adr/0021-retire-starter-scaffolding.md) there is no scaffolder: you write `package.json`, the manifests, the Worker entry, handlers, TypeScript and provider configuration. `mantle generate` compiles what exists; it never initializes a missing project or invents a default Schema, frontend or home route.
11
+
12
+ ```txt
13
+ my-service/
14
+ ├── package.json exact-pinned @aotter/mantle* + peers, scripts
15
+ ├── tsconfig.json includes src/ and .mantle/generated/
16
+ ├── wrangler.jsonc Worker name, flags, vars, bindings
17
+ ├── .dev.vars local secrets for wrangler dev; never committed
18
+ ├── manifests/
19
+ │ └── site.yaml immediate .yaml/.yml files; multi-document with ---
20
+ ├── src/
21
+ │ ├── index.ts createMantleWorker({ plan, extend })
22
+ │ └── mantle/handlers/ handler refs (a convention; any path under src/)
23
+ ├── public/ ASSETS: your frontend; public/_mantle/admin/ when Admin UI is installed
24
+ ├── .mantle/generated/
25
+ │ └── mantle.ts written by mantle generate
26
+ ├── .agents/skills/mantle-*/ written by mantle skills
27
+ └── .claude/skills/mantle-*/ same bytes, for Claude compatibility
28
+ ```
29
+
30
+ The minimal Worker reference keeps `.mantle/`, `.agents/`, `.claude/`, `.wrangler/`, `.dev.vars*` and `.env*` out of git and regenerates the first three in its `check` script. Committing the generated files is also workable, because `generate --check` and `skills --check` detect drift either way. Commit the lockfile in a real application and install with `--frozen-lockfile` afterwards.
31
+
32
+ `mantle validate` greps `./src` for the names of `handler.kind: ref` Procedures; a handler that is not found anywhere under `src/` produces a `HANDLER_NOT_REGISTERED` warning, so keep handlers under that root or pass `--source`.
33
+
34
+ ## The CLI
35
+
36
+ The umbrella package installs two binaries, `mantle` and `mantle-harness`. Run them through the package manager, for example `pnpm exec mantle generate`. Defaults shown are the pinned ones.
37
+
38
+ | Command | Flags | Does |
39
+ |---|---|---|
40
+ | `mantle generate` | `--manifests <dir>` (default `./manifests`), `-o, --output <dir>` (default `.mantle/generated`), `--namespace <name>` (default `Mantle`), `--check` | Validate, link and compile manifests; write `mantle.ts`; sync Admin assets when installed. `--check` exits 1 without writing when output is stale. |
41
+ | `mantle validate` | `--manifests <dir>`, `--source <dir>` (default `./src`), `--no-source`, `--phase preview\|deploy` (default `preview`), `--format json\|text`, `--json` | Static manifest and handler-source validation. Exit 0 with warnings allowed, 1 on any error, 2 on a CLI invocation problem. |
42
+ | `mantle skills` | `--check` | Copy every skill marked `projection: project` into `.agents/skills/mantle-<name>/` and `.claude/skills/mantle-<name>/`. `--check` exits 1 when a projection is stale. |
43
+ | `mantle emit-openapi` | `--manifests <dir>`, `--title <str>` (default `mantle`), `--version <str>` (default `0.1.0`), `--session-cookie-name <str>`, `-o, --output <file>` | Emit OpenAPI 3.1 for HTTP Triggers and `GET /api/views/<name>` routes to stdout or a file. MCP is out of scope. |
44
+ | `mantle-harness indexes` | `--manifests <dir>`, `--rows <n>` (default 2000), `--require <view>` (repeatable), `--require-public`, `--format text\|json` | Execute compiled Views in crowded SQLite and inspect query plans; exit 1 when a required View lacks its access path. |
45
+ | `mantle-harness http` | `--base-url <url>`, `--route <name=path-or-url>` (repeatable, required), `--rounds <n>` (default 20), `--warmup <n>` (default 2), `--format text\|json` | Sample a running Worker and report p50/p95 timings plus query-count and `rows_read` metric headers. |
46
+
47
+ Advanced manifest primitives live in the `@aotter/mantle-spec` package's own `mantle-spec` binary: `mantle-spec introspect [--manifests <dir>]` dumps the parsed manifest tree as JSON, and `mantle-spec emit-types [--manifests <dir>] [--namespace <name>] [-o <file>]` emits standalone `.d.ts` declarations. Add `@aotter/mantle-spec` as a direct dependency to run them. The full flag reference is in [HTTP, MCP, CLI and packages](../reference/surface.md).
48
+
49
+ ### What `generate` does and does not do
50
+
51
+ `generate` reads the manifest directory, runs the same validation as `validate` (without the handler-source grep), links the set, and emits one `.mantle/generated/mantle.ts`. When `@aotter/mantle-admin-ui` is installed it also syncs the Admin SPA into `public/_mantle/admin/`, excluding the package's `server.*` exports; Core-only installs skip that copy. Any error diagnostic stops the run with exit 1.
52
+
53
+ It does not project skills, update packages, change styling, provision providers, or deploy. It does not create manifests: a missing or empty `manifests/` directory is an error, not a prompt.
54
+
55
+ ## The generated module
56
+
57
+ `.mantle/generated/mantle.ts` exports:
58
+
59
+ | Export | Purpose |
60
+ |---|---|
61
+ | `plan` | The sealed RuntimePlan with its fingerprint. The conventional Worker imports only this. |
62
+ | `MantleHandlers<Env>` | The handler map type: one typed function per `handler.kind: ref` Procedure, receiving `(input, ctx)`. |
63
+ | `createMantle({ storage, handlers, ports })` | Prepares storage eagerly once and returns the typed binding. No caching or retry. |
64
+ | `bindMantle(runtime)` | The same typed binding over a runtime whose lifecycle the host already owns. |
65
+
66
+ The binding exposes `mantle.views.<lowerCamelName>()`, `mantle.procedures.<name>(input, ctx)`, `mantle.entries.<collection>.createDraft({ data, authorId })` and the underlying `mantle.runtime`. Generated property names are deterministic lower-camel identifiers; calls keep the authored wire names internally. Details are in [HTTP, MCP, CLI and packages](../reference/surface.md).
67
+
68
+ ## The daily loop
69
+
70
+ ```sh
71
+ pnpm install --frozen-lockfile
72
+ pnpm exec mantle validate
73
+ pnpm exec mantle generate
74
+ pnpm exec mantle generate --check
75
+ pnpm exec tsc --noEmit
76
+ pnpm test # only when the project declares a test script
77
+ pnpm exec mantle-harness indexes --require-public --format text
78
+ pnpm exec wrangler dev --local
79
+ ```
80
+
81
+ Run the harness after any change to a Schema index, View filter or ordering, or public route; declare the smallest ordered index the measured path needs and respect SQLite's leftmost-prefix rule. Before a deploy, run `mantle validate --phase deploy`. Probe at least one declared route on the local origin; a `200` from a public View does not prove Admin or MCP login works.
82
+
83
+ ## Connecting an agent
84
+
85
+ `mantle skills` projects the skills the installed package marks `projection: project`. At this version those are `develop`, `plugin`, `theme` and `update`; `install`, `media-gc` and `provision` stay opt-in because they create projects, delete remote objects or handle production secrets. Both tool layouts receive identical bytes. Generation never rewrites these files.
86
+
87
+ Install the version-matched plugin bundle in the agent host, using the exact version from `package.json`:
88
+
89
+ ```sh
90
+ # Claude Code — two separate prompts
91
+ /plugin marketplace add aotter/mantle@v<installed-version>
92
+ /plugin install mantle@mantle
93
+
94
+ # Codex
95
+ codex plugin marketplace add aotter/mantle --ref v<installed-version>
96
+ codex plugin add mantle@mantle
97
+ ```
98
+
99
+ The projected `develop` skill tells the agent to read `package.json` for the installed version, the manifests and adapter config, and the docs under `node_modules/@aotter/mantle/docs/` before editing. To connect an MCP client to the running Worker, see [MCP and agents](../concepts/mcp-and-agents.md).
100
+
101
+ ## Upgrade rules
102
+
103
+ - Pin every `@aotter/mantle*` package to one exact version and move them together. Check that release's peer ranges when you move.
104
+ - Read the migration notes of the release you are moving to. Docs for a floating branch do not describe your installed version; use a tag that matches `package.json`.
105
+ - The 0.1.2 line removes `mantle create`, the bundle `mantle update` command and `@aotter/mantle/provision`, with no aliases. `generate`, `validate`, `emit-openapi` and `skills` remain. `0.1.0-alpha.17` stays immutable; staying on it requires no migration.
106
+ - When upgrading an existing application: pin the new exact version and refresh the lockfile; remove scripts that invoked the retired scaffolder; keep the Worker, D1, KV identity, origins, auth mode, secrets and legacy `.mantle` metadata; then run `generate`, `generate --check`, `skills`, `skills --check`, `validate`, typecheck and tests before deploying.
107
+
108
+ ## Source
109
+ - [`docs/direct-authoring.md`](../../../docs/direct-authoring.md)
110
+ - [`docs/adr/0021-retire-starter-scaffolding.md`](../../../docs/adr/0021-retire-starter-scaffolding.md)
111
+ - [`docs/migration-0.1.2.md`](../../../docs/migration-0.1.2.md)
112
+ - [`docs/examples/minimal-worker/.gitignore`](../../../docs/examples/minimal-worker/.gitignore)
113
+ - [`packages/mantle/README.md`](../../../packages/mantle/README.md)
114
+ - [`packages/mantle/src/cli/main.ts`](../../../packages/mantle/src/cli/main.ts)
115
+ - [`packages/mantle/src/cli/generate.ts`](../../../packages/mantle/src/cli/generate.ts)
116
+ - [`packages/mantle/src/cli/skills.ts`](../../../packages/mantle/src/cli/skills.ts)
117
+ - [`packages/mantle/src/cli/harness.ts`](../../../packages/mantle/src/cli/harness.ts)
118
+ - [`packages/mantle/src/codegen/emitMantleModule.ts`](../../../packages/mantle/src/codegen/emitMantleModule.ts)
119
+ - [`packages/mantle-spec/src/infrastructure/cli/ValidateCommand.ts`](../../../packages/mantle-spec/src/infrastructure/cli/ValidateCommand.ts)
120
+ - [`packages/mantle-spec/src/infrastructure/cli/EmitOpenapiCommand.ts`](../../../packages/mantle-spec/src/infrastructure/cli/EmitOpenapiCommand.ts)
121
+ - [`packages/mantle-spec/src/infrastructure/cli/MantleCli.ts`](../../../packages/mantle-spec/src/infrastructure/cli/MantleCli.ts)
122
+ - [`skills/develop/SKILL.md`](../../../skills/develop/SKILL.md)
@@ -0,0 +1,193 @@
1
+ ---
2
+ description: "Author a minimal Cloudflare Worker from scratch: one Schema, one public View, generate, validate, run locally and probe it with curl."
3
+ ---
4
+ # Quickstart: a minimal Worker
5
+
6
+ This page reproduces Core's minimal Worker reference as a from-scratch walkthrough. It is for engineers who want a running Mantle service on their machine in a few minutes, with no visitor frontend and no Cloudflare account.
7
+
8
+ > **Version scope**
9
+ > The install below uses the published `0.1.0-alpha.17` packages. That release supports this minimal Worker flow, but still includes `mantle create` and `mantle update` and does not contain this handbook. The rest of this handbook describes a later development snapshot; see [Versions](../reference/surface.md#versions) before applying its CLI and migration guidance to a registry installation.
10
+
11
+ ## Prerequisites
12
+
13
+ - Node.js 22 or newer.
14
+ - pnpm 9 or newer. The reference is tested with pnpm; see the npm note at the end of this page.
15
+ - `wrangler` is installed as a project devDependency below. No Cloudflare account, D1 database or secret is needed for the local loop.
16
+
17
+ ## 1. `package.json`
18
+
19
+ Pin every `@aotter/mantle*` package to the same exact release and add the peers the Cloudflare adapter needs.
20
+
21
+ ```json
22
+ {
23
+ "name": "mantle-minimal-consumer",
24
+ "private": true,
25
+ "type": "module",
26
+ "scripts": {
27
+ "generate": "mantle generate",
28
+ "validate": "mantle validate",
29
+ "typecheck": "tsc --noEmit",
30
+ "dev": "wrangler dev --local",
31
+ "check": "mantle generate && mantle generate --check && mantle validate && mantle skills && mantle skills --check && tsc --noEmit"
32
+ },
33
+ "dependencies": {
34
+ "@aotter/mantle": "0.1.0-alpha.17",
35
+ "@aotter/mantle-cloudflare": "0.1.0-alpha.17",
36
+ "better-auth": "1.7.2",
37
+ "hono": "^4.13.3",
38
+ "zod": "^4.5.4",
39
+ "aws4fetch": "^1.0.20"
40
+ },
41
+ "devDependencies": {
42
+ "@cloudflare/workers-types": "^5.20260907.1",
43
+ "typescript": "^6.0.3",
44
+ "wrangler": "^4.125.0"
45
+ },
46
+ "packageManager": "pnpm@9.15.0"
47
+ }
48
+ ```
49
+
50
+ The reference's own `check` script ends with `&& node smoke.mjs`, a test that starts the Worker and asserts the three probes in step 6. This walkthrough runs those probes by hand instead.
51
+
52
+ Add a `tsconfig.json` that includes the generated module:
53
+
54
+ ```json
55
+ {
56
+ "compilerOptions": {
57
+ "target": "ES2022",
58
+ "module": "ESNext",
59
+ "moduleResolution": "bundler",
60
+ "strict": true,
61
+ "noEmit": true,
62
+ "skipLibCheck": true,
63
+ "types": ["@cloudflare/workers-types"]
64
+ },
65
+ "include": ["src/**/*.ts", ".mantle/generated/**/*.ts"]
66
+ }
67
+ ```
68
+
69
+ ## 2. `manifests/site.yaml`
70
+
71
+ One publishing Schema and one public View. `mantle generate` never invents a Schema; this notes model is example business data.
72
+
73
+ ```yaml
74
+ apiVersion: cms.mantle.aotter.net/v1
75
+ kind: Schema
76
+ metadata:
77
+ name: notes
78
+ spec:
79
+ title: Notes
80
+ schema:
81
+ type: object
82
+ required: [title]
83
+ properties:
84
+ title: { type: string }
85
+ lifecycle: publishing
86
+ ---
87
+ apiVersion: cms.mantle.aotter.net/v1
88
+ kind: View
89
+ metadata:
90
+ name: published-notes
91
+ spec:
92
+ surface: public
93
+ from: notes
94
+ fields: [id, title]
95
+ filter:
96
+ eq: { field: status, value: published }
97
+ limit: 20
98
+ ```
99
+
100
+ ## 3. `src/index.ts`
101
+
102
+ The conventional Worker entry hands the sealed plan to the Cloudflare adapter.
103
+
104
+ ```ts
105
+ import { createMantleWorker } from "@aotter/mantle/cloudflare";
106
+ import { plan } from "../.mantle/generated/mantle.js";
107
+
108
+ export default createMantleWorker({ plan });
109
+ ```
110
+
111
+ `createMantleWorker` owns the D1 and assets bindings, Auth, Admin, View REST, HTTP Triggers, OAuth, MCP and cache policy. Application handlers and extra routes go through its `extend` option; see [the conventional Worker](../cloudflare/conventional-worker.md).
112
+
113
+ ## 4. `wrangler.jsonc`
114
+
115
+ ```jsonc
116
+ {
117
+ "$schema": "node_modules/wrangler/config-schema.json",
118
+ "name": "mantle-reference",
119
+ "main": "src/index.ts",
120
+ "compatibility_date": "2026-09-08",
121
+ "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
122
+ "observability": { "enabled": true },
123
+ "vars": { "MANTLE_AUTH_MODE": "self-managed" },
124
+ "d1_databases": [
125
+ { "binding": "DB", "database_name": "mantle-reference-local" }
126
+ ]
127
+ }
128
+ ```
129
+
130
+ Both compatibility flags are required by the adapter. `MANTLE_AUTH_MODE` must be explicit; `self-managed` without GitHub credentials is a deliberate partial configuration for this local reference, so Auth-owned routes fail closed while public routes work. Local `wrangler dev` creates the D1 database on demand; configure a real `database_id` and the full auth matrix before any remote deploy ([Authentication](../cloudflare/authentication.md)).
131
+
132
+ ## 5. Install, generate, validate, run
133
+
134
+ ```sh
135
+ pnpm install
136
+ pnpm exec mantle generate
137
+ pnpm exec mantle validate
138
+ pnpm exec wrangler dev --local
139
+ ```
140
+
141
+ `mantle validate` prints `OK no issues (root: manifests, phase: preview)`. Wrangler prints the local origin, normally `http://localhost:8787`; use whatever it prints in the next step.
142
+
143
+ ## 6. Probe the Worker
144
+
145
+ ```sh
146
+ curl -s http://localhost:8787/api/views/published-notes
147
+ ```
148
+
149
+ ```json
150
+ { "ok": true, "data": { "rows": [], "page": 1, "show": 20, "hasMore": false } }
151
+ ```
152
+
153
+ The View is served with no data because the local D1 is fresh. `show` follows the View's `limit` when the request carries no `?show=`; `?page=` and `?show=` are the reserved pagination params ([Reads: Views, REST and MCP](../concepts/views.md)).
154
+
155
+ ```sh
156
+ curl -i http://localhost:8787/
157
+ ```
158
+
159
+ `GET /` returns `404`. No visitor frontend is installed or rendered; `mantle-web` is optional composition and never owns an implicit home route. Add your own routes or templates when the product needs them ([Public web, SEO and cache](../cloudflare/public-web.md)).
160
+
161
+ ```sh
162
+ curl -i http://localhost:8787/mcp/staff
163
+ ```
164
+
165
+ `GET /mcp/staff` returns `503` with the error code `setup_incomplete` until `MANTLE_AUTH_MODE` is backed by a complete configuration. This is the expected fail-closed state; a working public endpoint is not evidence of a working Admin or MCP login.
166
+
167
+ ## What `mantle generate` wrote
168
+
169
+ - `.mantle/generated/mantle.ts` — one module with the sealed `plan`, generated types (`MantleHandlers<Env>`), `createMantle` and `bindMantle`. The Worker entry above imports only `plan`.
170
+ - `public/_mantle/admin/` — the Admin SPA, synced only when `@aotter/mantle-admin-ui` is installed. This project did not install it, so nothing is written there and `/admin` has no assets.
171
+
172
+ `generate` fails on missing or invalid manifests and never creates a project, a default Schema or a home route. `mantle generate --check` reports stale output without writing. The reference keeps `.mantle/`, `.agents/` and `.claude/` out of git and regenerates them in `check`; see [Project layout and the CLI loop](./project-and-cli.md).
173
+
174
+ > **npm and `ERESOLVE`**
175
+ > With npm 11.16.0 a cold Cloudflare install can fail with `ERESOLVE`: Better Auth/Drizzle selects optional `@libsql/client@0.18.0` while this release declares the tested `^0.17.4` peer. If that exact conflict occurs, merge `{ "overrides": { "@libsql/client": "0.17.4" } }` into `package.json` and rerun `npm install`. Do not use `--force` or `--legacy-peer-deps`. Commit the lockfile and use `npm ci` afterwards. Recheck the peer range when upgrading; the workaround is specific to these versions.
176
+
177
+ ## Next steps
178
+
179
+ - [Project layout and the CLI loop](./project-and-cli.md) — the files you own, every CLI flag, the daily check loop.
180
+ - [The four atoms](../concepts/four-atoms.md) — add a Procedure and a Trigger to accept writes.
181
+ - [Authentication](../cloudflare/authentication.md) — complete `MANTLE_AUTH_MODE` so Admin and `/mcp/staff` open.
182
+ - [Public web, SEO and cache](../cloudflare/public-web.md) — give the service a rendered public surface.
183
+
184
+ ## Source
185
+ - [`docs/examples/minimal-worker/README.md`](../../../docs/examples/minimal-worker/README.md)
186
+ - [`docs/examples/minimal-worker/package.json`](../../../docs/examples/minimal-worker/package.json)
187
+ - [`docs/examples/minimal-worker/tsconfig.json`](../../../docs/examples/minimal-worker/tsconfig.json)
188
+ - [`docs/examples/minimal-worker/manifests/site.yaml`](../../../docs/examples/minimal-worker/manifests/site.yaml)
189
+ - [`docs/examples/minimal-worker/src/index.ts`](../../../docs/examples/minimal-worker/src/index.ts)
190
+ - [`docs/examples/minimal-worker/wrangler.jsonc`](../../../docs/examples/minimal-worker/wrangler.jsonc)
191
+ - [`docs/examples/minimal-worker/smoke.mjs`](../../../docs/examples/minimal-worker/smoke.mjs)
192
+ - [`docs/direct-authoring.md`](../../../docs/direct-authoring.md)
193
+ - [`packages/mantle-runtime/src/domain/service/Pagination.ts`](../../../packages/mantle-runtime/src/domain/service/Pagination.ts)
package/docs/labels.md CHANGED
@@ -29,11 +29,12 @@ For package README or package-local docs changes, prefer the package area label
29
29
  | `area:runtime` | `packages/mantle-runtime` behavior, ports, use cases, dispatcher, and MCP runtime. |
30
30
  | `area:spec` | `packages/mantle-spec`, manifest parsing, validation, diagnostics, CLI, spec types. |
31
31
  | `area:cf` | `packages/adapters/cloudflare`, Workers adapter, D1/KV/ASSETS wiring, Cloudflare deploy behavior. |
32
- | `area:starters` | External `aotter/mantle-starters` integration, release fanout, and the local moved-starter stub. |
32
+ | `area:starters` | Legacy `aotter/mantle-starters` source and retirement migration. |
33
33
  | `area:skills` | `skills/*` agent briefs and install/extend/provision workflows. |
34
34
  | `area:admin-ui` | `packages/mantle-admin-ui` React admin SPA. |
35
35
  | `area:docs` | Repo-wide human docs, governance docs, ADR text, release docs, root README content, and cross-cutting documentation work. |
36
36
  | `area:adapter` | Adapter boundary work spanning Cloudflare or future adapters. |
37
+ | `area:ci` | GitHub Actions, dependency automation, and repository checks. |
37
38
 
38
39
  ## Release and review gates
39
40
 
@@ -60,6 +61,7 @@ gh label create "area:skills" --description "Agent Skills and install/extend/pro
60
61
  gh label create "area:admin-ui" --description "React admin UI" --color "1d76db"
61
62
  gh label create "area:docs" --description "Documentation and governance" --color "1d76db"
62
63
  gh label create "area:adapter" --description "Adapter boundary and future adapter work" --color "1d76db"
64
+ gh label create "area:ci" --description "CI, dependency automation, and repository checks" --color "1d76db"
63
65
  gh label create "breaking-change" --description "Semver-relevant breaking change" --color "b60205"
64
66
  gh label create "skip-release-notes" --description "Release bookkeeping only; omit from generated GitHub notes" --color "ededed"
65
67
  gh label create "needs-adr" --description "Requires an ADR or ADR-lite decision before merge" --color "d93f0b"