@zerotal/arch 1.11.0 → 1.11.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.
- package/docs/ai.md +35 -2
- package/docs/changelog.md +160 -0
- package/docs/commands.md +31 -0
- package/docs/notifications.md +41 -0
- package/docs/support-policy.md +28 -21
- package/package.json +3 -3
package/docs/ai.md
CHANGED
|
@@ -138,9 +138,28 @@ response.stopReason; // "end_turn" | "max_tokens" | "tool_use" | …
|
|
|
138
138
|
|
|
139
139
|
Current Claude models **reject** `temperature`, `top_p`, and `top_k` with a 400 — a
|
|
140
140
|
generic sampling parameter forwarded blindly fails every request. The Anthropic
|
|
141
|
-
driver
|
|
141
|
+
driver drops `temperature` on those models and warns once.
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
**On models that accept it, it is sent.** The 4.6 and 4.5 generations take sampling
|
|
144
|
+
parameters perfectly well, and there the configured or per-request `temperature`
|
|
145
|
+
reaches the API. Ask `modelCapabilities(model)` if you want to know which you are on:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { modelCapabilities } from "@zerotal/ai";
|
|
149
|
+
|
|
150
|
+
const caps = modelCapabilities("claude-haiku-4-5");
|
|
151
|
+
// { sampling: true, effort: false, thinking: "budget" }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
That table is also what keeps the driver from sending a model something it rejects.
|
|
155
|
+
`effort` is a 400 on the 4.5 generation, and those models want an explicit thinking
|
|
156
|
+
budget rather than the adaptive form — so the driver builds a different request for
|
|
157
|
+
them rather than one request for everything. Models it does not recognise are treated
|
|
158
|
+
as current generation, because the ones that differ are a closed set that ages out
|
|
159
|
+
while new models keep arriving.
|
|
160
|
+
|
|
161
|
+
Reach for `effort` where the model has it. It trades thoroughness against cost and
|
|
162
|
+
latency:
|
|
144
163
|
|
|
145
164
|
| Effort | Use it for |
|
|
146
165
|
| -------- | -------------------------------------------------------- |
|
|
@@ -150,6 +169,18 @@ Reach for `effort` instead. It trades thoroughness against cost and latency:
|
|
|
150
169
|
| `xhigh` | Hard coding and agentic tasks |
|
|
151
170
|
| `max` | When correctness matters more than the bill |
|
|
152
171
|
|
|
172
|
+
### The thinking stream
|
|
173
|
+
|
|
174
|
+
A streamed `thinking` chunk carries the model's reasoning as it happens. The API
|
|
175
|
+
**omits that text by default** on the current generation, so the driver asks for it:
|
|
176
|
+
`drivers.anthropic.thinkingDisplay` defaults to `"summarized"`.
|
|
177
|
+
|
|
178
|
+
Set it to `"omitted"` to get the API's own default back. The thinking happens — and is
|
|
179
|
+
billed — either way; the setting only decides whether you are shown it. Before 1.11.2
|
|
180
|
+
the driver never asked, so the documented `thinking` chunk fired forever with
|
|
181
|
+
`text: ""` and no error, and a "thinking…" view built against the 4.6 models stopped
|
|
182
|
+
working when their users moved to 5 without anything saying so.
|
|
183
|
+
|
|
153
184
|
### Streaming
|
|
154
185
|
|
|
155
186
|
```ts fragment
|
|
@@ -531,6 +562,8 @@ sections above; this is the index.
|
|
|
531
562
|
| `modelPrice` | The price for a model, or `undefined` when we have none. |
|
|
532
563
|
| `estimateCost` | Estimated USD for one request's usage. Returns 0 for an unpriced model. |
|
|
533
564
|
| `modelRejectsSampling` | Whether a Claude model rejects `temperature` / `top_p` / `top_k` with a 400. |
|
|
565
|
+
| `modelCapabilities` | What a model accepts: sampling, `effort`, and which thinking shape. |
|
|
566
|
+
| `ModelCapabilities` | The three answers `modelCapabilities` returns. |
|
|
534
567
|
|
|
535
568
|
### Spend and statistics
|
|
536
569
|
|
package/docs/changelog.md
CHANGED
|
@@ -27,6 +27,166 @@ the section for every version you cross and apply its migration notes, not only
|
|
|
27
27
|
majors. [Releases and versioning](/docs/support-policy#releases-and-versioning) explains
|
|
28
28
|
when that carve-out ends.
|
|
29
29
|
|
|
30
|
+
## 1.11.2 — 2026-08-31
|
|
31
|
+
|
|
32
|
+
`@zerotal/ai` is `stable`, and the release that promotes it is the one that fixes five
|
|
33
|
+
bugs its first production users found. That ordering is the point: a `stable` promise
|
|
34
|
+
about an API nothing has pushed against is a promise nobody has tested.
|
|
35
|
+
|
|
36
|
+
Also here: two gates that were not doing their job, one of which had let two releases
|
|
37
|
+
publish over a red build.
|
|
38
|
+
|
|
39
|
+
A patch. Nothing here breaks — `@zerotal/ai`'s surface was narrowed _before_ the label,
|
|
40
|
+
while narrowing was still free.
|
|
41
|
+
|
|
42
|
+
### `@zerotal/ai` — the review, answered
|
|
43
|
+
|
|
44
|
+
The package shipped `experimental` with a stated precondition — _it graduates in the
|
|
45
|
+
release after its first real users_ — and a review date of 1.11.0 enforced by the
|
|
46
|
+
package linter rather than by a promise. Its first production users, running it against
|
|
47
|
+
Anthropic, sent a field review of the driver. So the precondition was met rather than
|
|
48
|
+
waived, and the answer is **promote**.
|
|
49
|
+
|
|
50
|
+
**Fixed, all from that review:**
|
|
51
|
+
|
|
52
|
+
- **Sonnet 5 was priced as Sonnet 4.6** — 3/15 rather than 2/10, 50% high. The same
|
|
53
|
+
table feeds `limits.perRequestUsd` and `perDayUsd`, so an app on that model was
|
|
54
|
+
refused requests comfortably inside its budget by an error that said "spend limit"
|
|
55
|
+
and sent it to its config rather than to the row. `AiSpendLimitError` now quotes the
|
|
56
|
+
rate it priced with and names `registerModelPrice()`, so a wrong table is legible
|
|
57
|
+
from the refusal and correctable without waiting for a release.
|
|
58
|
+
|
|
59
|
+
- **`effort` and `thinking` are model-aware.** Both went on every call. `effort` is a
|
|
60
|
+
400 on the 4.5 generation and those models want an explicit thinking budget rather
|
|
61
|
+
than the adaptive form — so the package listed `claude-haiku-4-5` in its pricing
|
|
62
|
+
table while the driver could not successfully call it. `modelCapabilities()` answers
|
|
63
|
+
what a model takes, and the driver builds the request that model accepts.
|
|
64
|
+
|
|
65
|
+
- **`temperature` never reached the API, on any model.** Not in the review — it turned
|
|
66
|
+
up while testing the item below. The driver warned about dropping `temperature` and
|
|
67
|
+
had no branch that set it, so the configured default and `AiRequest.temperature` were
|
|
68
|
+
both inert everywhere, including on the models that accept them. The old predicate
|
|
69
|
+
warned for almost every model, which is exactly what made the silence look deliberate
|
|
70
|
+
on the few it did not.
|
|
71
|
+
|
|
72
|
+
- **The streamed `thinking` chunk was always empty.** The API omits thinking text by
|
|
73
|
+
default on the current generation, so a documented chunk type fired forever with
|
|
74
|
+
`text: ""` and no error — and a "thinking…" view built against the 4.6 models, where
|
|
75
|
+
it defaulted on, stopped working when users moved to 5 with nothing to say so.
|
|
76
|
+
`drivers.anthropic.thinkingDisplay` defaults to `"summarized"`.
|
|
77
|
+
|
|
78
|
+
- **An app with no AI configured now boots.** `AiConfig` threw when no driver was
|
|
79
|
+
declared and threw again on an empty `apiKey`, so a deployment with no key could not
|
|
80
|
+
express itself either way. One app declared an Ollama server it did not run purely to
|
|
81
|
+
satisfy the validator, with a comment explaining that the config was lying. "AI is
|
|
82
|
+
off" is a coherent deployment and is now expressible; the first call raises
|
|
83
|
+
`AiDriverUnavailableError`, whose `transient` is already `false`.
|
|
84
|
+
|
|
85
|
+
- **`countTokens` returns `null` where a provider cannot count**, rather than `0`. Only
|
|
86
|
+
Anthropic has a counting endpoint, and `0` is also a real count for an empty prompt.
|
|
87
|
+
|
|
88
|
+
**How it was promoted**, because the order is the part that matters:
|
|
89
|
+
|
|
90
|
+
The surface was narrowed **first** — narrowing after `stable` is itself a breaking
|
|
91
|
+
change. `toSchema`, `strippedConstraints`, `resetSpend` and `resetStats` are `@internal`
|
|
92
|
+
now: still exported, so nothing breaks at runtime, but no longer promised.
|
|
93
|
+
`translateSchema` stayed public despite having no caller outside the package, for the
|
|
94
|
+
same reason `AiDriver` is public — the point of a driver contract is that someone else
|
|
95
|
+
implements it, and implementing structured output means translating a schema.
|
|
96
|
+
`AiDelivery` stayed too, being the element type of `recentGenerations()`.
|
|
97
|
+
|
|
98
|
+
Then the two modules it would have been embarrassing to freeze untested: the SSE
|
|
99
|
+
parser, which reads a remote provider's framing off the network, and prompt redaction,
|
|
100
|
+
which is the only thing between a user's prompt and a log that outlives the request.
|
|
101
|
+
Both hold up — the parser reassembles a frame whose terminator is split across chunks
|
|
102
|
+
and a UTF-8 sequence cut mid-character.
|
|
103
|
+
|
|
104
|
+
### Fixed — the gates
|
|
105
|
+
|
|
106
|
+
- **The release workflow ran three checks; the pull-request workflow ran fifteen.** So
|
|
107
|
+
every convention, surface and documentation gate guarded the cheap, reversible action
|
|
108
|
+
and not the permanent one. 1.11.0 and 1.11.1 both published over a CI that had been
|
|
109
|
+
red since the first of them, and nothing in the release objected, because nothing in
|
|
110
|
+
the release looked. `release.yml` now runs the same set.
|
|
111
|
+
|
|
112
|
+
- **One failing check hid eleven others.** When the `@zerotal/ai` review fell due, the
|
|
113
|
+
package-conventions step failed and every later step in that job was skipped —
|
|
114
|
+
reported as "skipped", which reads like "not applicable" rather than "never ran".
|
|
115
|
+
Each check is now guarded so it reports its own result.
|
|
116
|
+
|
|
117
|
+
## 1.11.1 — 2026-08-31
|
|
118
|
+
|
|
119
|
+
Two things the framework could not do, both reported by teams who had already
|
|
120
|
+
worked around them.
|
|
121
|
+
|
|
122
|
+
A patch, not a minor: nothing here breaks. Under
|
|
123
|
+
[the versioning scheme](/docs/upgrade#versioning) a minor is reserved for a
|
|
124
|
+
breaking change and a patch carries everything else, features included — so this
|
|
125
|
+
is safe to take from any 1.11.x.
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
|
|
129
|
+
- **`zt version`** — which Zerotal, which Bun, which app.
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Zerotal 1.11.1
|
|
133
|
+
Bun 1.3.14
|
|
134
|
+
App my-app 0.1.0
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
It was an unknown command, so the version had to be dug out of `package.json` or
|
|
138
|
+
`node_modules` — both of which report what is _installed_ rather than what is
|
|
139
|
+
_running_, and those differ for any process that has been up since before an
|
|
140
|
+
upgrade. It reports the running one.
|
|
141
|
+
|
|
142
|
+
`--version` and `-v` answer earlier still, ahead of the runtime check, the config
|
|
143
|
+
load and the app import, because those are the things someone is asking the version
|
|
144
|
+
_about_: a config that no longer validates and an app that will not boot are the two
|
|
145
|
+
moments the question stops being idle. A version flag that only works when
|
|
146
|
+
everything else already works answers a question nobody has.
|
|
147
|
+
|
|
148
|
+
Add `--json` for a script, and prefer `zt --version --json` over
|
|
149
|
+
`zt version --json` there — the application's boot log is written to stdout, so the
|
|
150
|
+
second form puts a log line ahead of the JSON while the first never boots at all.
|
|
151
|
+
The output carries no colour, unlike every other command's, because it gets pasted
|
|
152
|
+
into bug reports and piped into parsers more than it is read on a terminal.
|
|
153
|
+
|
|
154
|
+
- **`MailMessage.header()` and `MailPayload.headers`** — set a header the mail driver
|
|
155
|
+
does not build itself.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
new MailMessage()
|
|
159
|
+
.subject("Your weekly digest")
|
|
160
|
+
.header("List-Unsubscribe", `<https://app.test/unsubscribe/${token}>`)
|
|
161
|
+
.header("List-Unsubscribe-Post", "List-Unsubscribe=One-Click");
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`MailPayload` had `to`, `from`, `subject`, `text`, `html`, `cc`, `bcc`, `replyTo`
|
|
165
|
+
and `attachments`, and no way to add anything else — so a team that wanted
|
|
166
|
+
`List-Unsubscribe` had to patch a vendored copy of the package, and shipped a footer
|
|
167
|
+
link instead.
|
|
168
|
+
|
|
169
|
+
Those are not substitutes for one another. Gmail and Yahoo draw their native
|
|
170
|
+
unsubscribe control from the header, and a recipient who cannot find a control marks
|
|
171
|
+
the message as spam instead — a judgement that attaches to the sending domain and
|
|
172
|
+
degrades delivery of everything else it sends, including the mail people asked for.
|
|
173
|
+
Send `List-Unsubscribe-Post` alongside it: alone, the first leaves a link to follow,
|
|
174
|
+
and only the pair produces the one-click control both providers now expect.
|
|
175
|
+
|
|
176
|
+
Wired through all three drivers. SMTP writes them into the message, Resend sends
|
|
177
|
+
them as the API's `headers` object, and the log driver prints them — that last one
|
|
178
|
+
deliberately, because the reason to set a header is that a mail client does
|
|
179
|
+
something with it, and the log driver is where that gets checked before anything is
|
|
180
|
+
sent for real.
|
|
181
|
+
|
|
182
|
+
Names the drivers build themselves are refused rather than sent twice: a second
|
|
183
|
+
`Subject` is an ambiguous message, not an override, and which copy a client believes
|
|
184
|
+
is its own business. The list is exported as `RESERVED_MAIL_HEADERS`, with
|
|
185
|
+
`resolveHeaders()` beside it for anyone writing a custom transport. CR and LF in a
|
|
186
|
+
value are folded to a space — left raw they end the header and let the remainder be
|
|
187
|
+
read as further headers, which is how a `Bcc` arrives courtesy of whoever supplied a
|
|
188
|
+
tracking ID.
|
|
189
|
+
|
|
30
190
|
## 1.11.0 — 2026-08-30
|
|
31
191
|
|
|
32
192
|
Two production reports, from teams taking apps live on 1.9.0 — one shipping a
|
package/docs/commands.md
CHANGED
|
@@ -310,6 +310,7 @@ Packages register their own — see
|
|
|
310
310
|
|
|
311
311
|
| Command | Description |
|
|
312
312
|
| ---------------------- | ------------------------------------------------------ |
|
|
313
|
+
| `bun zt version` | Show the Zerotal, Bun and app versions |
|
|
313
314
|
| `bun zt route:list` | List all registered routes with methods and middleware |
|
|
314
315
|
| `bun zt route:types` | Write `types/routes.generated.ts` (`--check` in CI) |
|
|
315
316
|
| `bun zt doctor` | Check the app for silent misconfigurations |
|
|
@@ -317,6 +318,36 @@ Packages register their own — see
|
|
|
317
318
|
| `bun zt lint:packages` | Check every workspace package against convention rules |
|
|
318
319
|
| `bun zt upgrade` | Apply the codemods for a version upgrade |
|
|
319
320
|
|
|
321
|
+
#### Which version am I on?
|
|
322
|
+
|
|
323
|
+
`bun zt version` prints the framework, the runtime and the app:
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
Zerotal 1.11.0
|
|
327
|
+
Bun 1.3.14
|
|
328
|
+
App my-app 0.1.0
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
It reports the version that is **running**, which is not always the version that is
|
|
332
|
+
installed — a long-running server holds the code it booted with, so an upgrade lands
|
|
333
|
+
on disk without reaching it.
|
|
334
|
+
|
|
335
|
+
`--version` and `-v` answer the same question without booting the application:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
bun zt --version
|
|
339
|
+
bun zt --version --json # for a script
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Prefer the flag form in scripts, for two reasons. It still answers when the app does
|
|
343
|
+
not boot — a config that no longer validates is exactly when you want to know which
|
|
344
|
+
version you are on. And it is the form whose output can be piped: the application's
|
|
345
|
+
boot log is written to stdout, so `bun zt version --json` carries a log line ahead of
|
|
346
|
+
the JSON, while `bun zt --version --json` never boots and emits nothing else.
|
|
347
|
+
|
|
348
|
+
If `node_modules` contains a second Bun, the report names it. Nothing executes that
|
|
349
|
+
copy — it arrives as a peer dependency — but it is the one an install would use.
|
|
350
|
+
|
|
320
351
|
### Upgrading between versions
|
|
321
352
|
|
|
322
353
|
`bun zt upgrade --to <version>` applies the codemods a version gap calls for —
|
package/docs/notifications.md
CHANGED
|
@@ -390,6 +390,47 @@ async toMail(_n: Notifiable): Promise<MailMessage> {
|
|
|
390
390
|
}
|
|
391
391
|
```
|
|
392
392
|
|
|
393
|
+
#### Custom headers, and the unsubscribe button
|
|
394
|
+
|
|
395
|
+
`header(name, value)` sets a header the driver does not build itself. The one this
|
|
396
|
+
exists for is `List-Unsubscribe`:
|
|
397
|
+
|
|
398
|
+
```ts fragment
|
|
399
|
+
// in a Notification
|
|
400
|
+
toMail(n: Notifiable): MailMessage {
|
|
401
|
+
return new MailMessage()
|
|
402
|
+
.subject("Your weekly digest")
|
|
403
|
+
.line("Here is what happened this week.")
|
|
404
|
+
.header("List-Unsubscribe", `<https://app.test/unsubscribe/${n.unsubscribeToken}>`)
|
|
405
|
+
.header("List-Unsubscribe-Post", "List-Unsubscribe=One-Click");
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Gmail and Yahoo draw their native unsubscribe control — the one beside the sender's
|
|
410
|
+
name, not the one in your footer — from that header, and there is no other way to ask
|
|
411
|
+
for it. Send both headers, not just the first: alone, `List-Unsubscribe` leaves the
|
|
412
|
+
recipient a link to follow, and only the pair produces a control that resolves in one
|
|
413
|
+
press. The URL must accept a `POST` with no body and unsubscribe on the spot, with no
|
|
414
|
+
confirmation page.
|
|
415
|
+
|
|
416
|
+
It is worth more than the footer link it duplicates. A recipient who cannot find the
|
|
417
|
+
control marks the message as spam instead, and that judgement attaches to the sending
|
|
418
|
+
domain and affects delivery of everything else it sends — including the mail people do
|
|
419
|
+
want. The two paths out of a mailing list are not equivalent for the sender.
|
|
420
|
+
|
|
421
|
+
Header names the drivers build themselves are refused rather than sent twice, because
|
|
422
|
+
a second `Subject` is an ambiguous message rather than an override. Those names are
|
|
423
|
+
`RESERVED_MAIL_HEADERS`, and the error names the `MailMessage` method to use instead
|
|
424
|
+
where there is one. The check runs when you set the header, so the throw carries the
|
|
425
|
+
stack of the code that wrote it rather than of a queue worker three hops away — and
|
|
426
|
+
again in the driver, since a `MailPayload` can be built without ever passing through
|
|
427
|
+
`MailMessage`.
|
|
428
|
+
|
|
429
|
+
Values are folded to a single line: a CR or LF in a header value ends the header and
|
|
430
|
+
lets the rest be read as further headers, which is how a `Bcc` gets added by someone
|
|
431
|
+
who was only supposed to be supplying a tracking ID. Writing a custom transport?
|
|
432
|
+
Call `resolveHeaders()` on the payload's headers and it does both checks for you.
|
|
433
|
+
|
|
393
434
|
### database
|
|
394
435
|
|
|
395
436
|
Implement `toDatabase(notifiable)` returning a plain object. The notification is
|
package/docs/support-policy.md
CHANGED
|
@@ -111,32 +111,39 @@ has worn the label for a year is not being cautious, it is unowned. So each one
|
|
|
111
111
|
below `stable` names the release by which it is reviewed, and the review has three
|
|
112
112
|
outcomes — promote, keep with a new date and the reason, or withdraw.
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`@zerotal/ai`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
Nothing is below `stable` today. The table that lived here is empty, which is the
|
|
115
|
+
outcome the mechanism is for rather than the absence of one — it is how the review
|
|
116
|
+
looks when every date has been answered.
|
|
117
|
+
|
|
118
|
+
`@zerotal/ai` was the last entry, `experimental` and due by 1.11.0. It was promoted in
|
|
119
|
+
1.11.2, and the precondition it carried was met rather than waived: it graduates in the
|
|
120
|
+
release after its first real users, and its first production users sent a field review
|
|
121
|
+
of the driver against Anthropic. That review is why the promotion is worth anything —
|
|
122
|
+
five bugs came back with it, and a `stable` promise about an API nothing has pushed
|
|
123
|
+
against is a promise nobody has tested.
|
|
124
|
+
|
|
125
|
+
The order was deliberate. Its surface was narrowed **before** the label, because
|
|
126
|
+
narrowing after `stable` is itself a breaking change: `toSchema`, `strippedConstraints`,
|
|
127
|
+
`resetSpend` and `resetStats` are `@internal` now. `translateSchema` stayed public
|
|
128
|
+
despite having no caller outside the package, for the same reason `AiDriver` did — the
|
|
129
|
+
whole point of a driver contract is that someone else implements it, and implementing
|
|
130
|
+
structured output requires translating a schema. Then its two riskiest modules were
|
|
131
|
+
tested: the SSE parser, which reads a remote provider's framing off the network, and
|
|
132
|
+
prompt redaction, which is the only thing between a user's prompt and a log that
|
|
133
|
+
outlives the request.
|
|
129
134
|
|
|
130
135
|
`@zerotal/arch` held `beta` with the same date, was reviewed ahead of it, and is
|
|
131
136
|
`stable` — the release that carried the promotion is the one its
|
|
132
|
-
[changelog](/docs/changelog) names. Its surface was narrowed first
|
|
133
|
-
`arch:install` are `@internal
|
|
137
|
+
[changelog](/docs/changelog) names. Its surface was narrowed first too: the writers
|
|
138
|
+
behind `arch:install` are `@internal`, because they had no caller outside the package
|
|
134
139
|
and freezing them would have promised the shape of `.mcp.json` writing to nobody.
|
|
135
140
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
Neither `ai` nor `arch` is in the `zerotal` meta-package. `arch` stays out because
|
|
142
|
+
`arch:install` writes configuration and instruction files into a project, which is an
|
|
143
|
+
opinion about someone's toolchain and stays their choice to invite. `ai` stays out for
|
|
144
|
+
its own reason rather than by omission: it is the only package with an optional peer
|
|
145
|
+
on a vendor SDK, and pulling it into the meta-package would put a provider dependency
|
|
146
|
+
in front of every app that installs `zerotal`, including the ones with no AI in them.
|
|
140
147
|
|
|
141
148
|
That table used to be the whole of the commitment, which meant the version could
|
|
142
149
|
sail past it and the only consequence would be this paragraph quietly becoming
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/arch",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"maturity": "stable",
|
|
6
6
|
"private": false,
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"typecheck": "tsc --noEmit"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@zerotal/core": "1.11.
|
|
38
|
+
"@zerotal/core": "1.11.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"typescript": "^5.8.0",
|
|
42
|
-
"@zerotal/orm": "1.11.
|
|
42
|
+
"@zerotal/orm": "1.11.2"
|
|
43
43
|
},
|
|
44
44
|
"description": "The Zerotal agent surface — an MCP server that hands coding agents the framework's machine-readable truth: exact API signatures, live routes and schema, version-matched docs, and `zt doctor`.",
|
|
45
45
|
"keywords": [
|