@pikku/skills 0.12.10 → 0.12.12

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 (61) hide show
  1. package/CHANGELOG.md +819 -0
  2. package/dist/skills.gen.js +1 -1
  3. package/package.json +4 -4
  4. package/skills/pikku-addon/SKILL.md +17 -11
  5. package/skills/pikku-addon/references/addon-package-manifest.md +2 -2
  6. package/skills/pikku-agent/SKILL.md +4 -5
  7. package/skills/pikku-audit/SKILL.md +28 -13
  8. package/skills/pikku-aws/SKILL.md +2 -2
  9. package/skills/pikku-better-auth/SKILL.md +97 -17
  10. package/skills/pikku-build-app/SKILL.md +621 -0
  11. package/skills/pikku-build-app/references/multi-app.md +117 -0
  12. package/skills/pikku-build-app/references/ship.md +98 -0
  13. package/skills/pikku-build-app/references/theming.md +70 -0
  14. package/skills/pikku-build-platform/SKILL.md +239 -0
  15. package/skills/pikku-build-quick/SKILL.md +238 -0
  16. package/skills/pikku-cli/SKILL.md +7 -7
  17. package/skills/pikku-cli/references/complete-example.md +1 -1
  18. package/skills/pikku-concepts/SKILL.md +5 -2
  19. package/skills/pikku-concepts/references/concept-mapping.md +1 -1
  20. package/skills/pikku-config/SKILL.md +5 -3
  21. package/skills/pikku-deploy-azure/SKILL.md +5 -4
  22. package/skills/pikku-emails/SKILL.md +28 -7
  23. package/skills/pikku-fabric/SKILL.md +27 -3
  24. package/skills/pikku-fabric-debug/SKILL.md +1 -1
  25. package/skills/pikku-feature/SKILL.md +5 -4
  26. package/skills/pikku-http/SKILL.md +4 -4
  27. package/skills/pikku-http/references/http-options.md +13 -13
  28. package/skills/pikku-i18n/SKILL.md +2 -1
  29. package/skills/pikku-info/SKILL.md +1 -1
  30. package/skills/pikku-knowledge/SKILL.md +13 -13
  31. package/skills/pikku-mcp/SKILL.md +4 -4
  32. package/skills/pikku-middleware/SKILL.md +5 -5
  33. package/skills/pikku-middleware/references/middleware-patterns.md +14 -8
  34. package/skills/pikku-n8n-import/SKILL.md +12 -12
  35. package/skills/pikku-n8n-import/SPEC.md +3 -0
  36. package/skills/pikku-n8n-import/references/code-translation.md +26 -22
  37. package/skills/pikku-n8n-import/references/loops-and-control.md +7 -7
  38. package/skills/pikku-paraglide/SKILL.md +11 -6
  39. package/skills/pikku-permissions/SKILL.md +19 -15
  40. package/skills/pikku-product-second-opinion/README.md +3 -3
  41. package/skills/pikku-product-second-opinion/example/sample-report.md +15 -12
  42. package/skills/pikku-product-second-opinion/references/report-template.md +10 -7
  43. package/skills/pikku-queue/SKILL.md +1 -1
  44. package/skills/pikku-react/SKILL.md +53 -13
  45. package/skills/pikku-realtime/SKILL.md +52 -21
  46. package/skills/pikku-rpc/SKILL.md +4 -2
  47. package/skills/pikku-rtl/SKILL.md +1 -1
  48. package/skills/pikku-scenario/SKILL.md +131 -20
  49. package/skills/pikku-schedule/SKILL.md +6 -1
  50. package/skills/pikku-schema-ajv/SKILL.md +2 -2
  51. package/skills/pikku-schema-cfworker/SKILL.md +1 -1
  52. package/skills/pikku-security/SKILL.md +9 -5
  53. package/skills/pikku-services/SKILL.md +27 -18
  54. package/skills/pikku-services/references/audit-wire-service.md +14 -8
  55. package/skills/pikku-software-archaeology/scripts/validate.mjs +146 -69
  56. package/skills/pikku-tag-middleware/SKILL.md +1 -0
  57. package/skills/pikku-template-clone/SKILL.md +2 -1
  58. package/skills/pikku-trigger/SKILL.md +3 -3
  59. package/skills/pikku-websocket/SKILL.md +4 -3
  60. package/skills/pikku-workflow/SKILL.md +2 -2
  61. package/skills/pikku-workflow/references/workflow-reference.md +24 -10
package/CHANGELOG.md ADDED
@@ -0,0 +1,819 @@
1
+ # @pikku/skills
2
+
3
+ ## 0.12.12
4
+
5
+ ### Patch Changes
6
+
7
+ - 8d6a6bc: fix(cli): a scaffold flag says a surface exists, not who may call it
8
+
9
+ `scaffold.<feature>` accepted an `auth` field, and the generated wrapper emitted
10
+ it onto the wired function. That put authentication in two places at once: the
11
+ target function already declares whether it needs a session, its wiring, its
12
+ scopes and its addon gate already refine it, and `runPikkuFunc` already enforces
13
+ all of that on every call. The scaffold flag only stacked a coarser gate in
14
+ front of the one that actually decides, and — being a config field — it could
15
+ disagree with the function it was gating.
16
+
17
+ `PikkuScaffoldFeature` is now `boolean | { path?: string }`. It answers two
18
+ things and no more: whether the surface is generated, and where the file is
19
+ written. A feature that was `{ "auth": false }` becomes plain `true`, and
20
+ `pikku enable` loses its `--noAuth` flag along with the dimension it set.
21
+
22
+ The six generators that took the flag no longer take one. The four that generate
23
+ a dispatcher — public RPC, public agent, workflow routes and the events channel
24
+ — now emit a fixed `auth: false`. That is the wrapper declining to gate, not the
25
+ scaffold declaring the surface public: `rpcCaller` forwards to whichever
26
+ function the caller named, and that function's own `auth`, permissions, scopes
27
+ and addon gate are what decide. Emitting nothing would not be neutral, since a
28
+ wiring without `auth` requires a session and would reject the call before the
29
+ gate that decides ever ran. The two that generate scoped admin functions — user
30
+ admin and virtual users — emit no `auth`, because they are `pikkuFunc` with
31
+ their own `scopes`: session-required by construction, and the deciding function
32
+ rather than a wrapper in front of one.
33
+
34
+ The legacy `'auth'` / `'no-auth'` string values are gone with it. A bare string
35
+ is still refused rather than read as a `path`: under `boolean | object` no
36
+ string is valid, so guessing one would turn a typo into a generated file nobody
37
+ asked for.
38
+
39
+ - 31ad85f: fix(emails): escape substituted values in the generated email renderer
40
+
41
+ `renderEmailTemplate` spliced values into HTML unescaped and looped substitution
42
+ until it reached a fixed point, so a value containing `"` broke out of the
43
+ attribute it landed in, a value containing markup was injected verbatim, and a
44
+ value containing `{{...}}` was re-expanded as a template on the next pass. An
45
+ ordinary CSS font stack from `theme.json` was enough to corrupt the document.
46
+
47
+ Rendering is now layered by trust. Partials are inlined first; `theme.*` and
48
+ `t.*` are expanded next as template-author input; caller `data` is substituted in
49
+ a single pass that is never rescanned. Values are HTML-escaped in `.html` output
50
+ and left raw in `.subject.txt` / `.text.txt`. `{{content}}` and partials stay
51
+ raw, and `{{{value}}}` is a new opt-in raw form. The console's email preview uses
52
+ the same renderer, so previews match what is sent.
53
+
54
+ ## 0.12.11
55
+
56
+ ### Patch Changes
57
+
58
+ - 7722ceb: Split the addon leaf so an application cannot shadow a linked addon's own
59
+
60
+ An addon authored its services through `#pikku/addon`, and so did an
61
+ application installing one. Node keeps those apart — `#pikku/*` is a
62
+ package-private subpath import, resolved against the addon's own
63
+ `package.json` — but tsconfig `paths` are global to a tsx process, and every
64
+ runtime template maps `#pikku/*` onto a sibling package. A linked addon's
65
+ `#pikku/addon` was resolved against the _application's_ leaf, which holds the
66
+ install half and none of the authoring exports, and every template failed to
67
+ boot with `does not provide an export named 'pikkuAddonServices'`.
68
+
69
+ The authoring half now sits at `#pikku/addon/setup`. An application generates a
70
+ flat `.pikku/<leaf>`, so there is nothing there for that specifier to match and
71
+ the resolver falls back to Node, which reads the addon's own imports. Addons
72
+ declaring themselves import `pikkuAddonConfig`, `pikkuAddonServices` and
73
+ `pikkuAddonWireServices` from `#pikku/addon/setup`; `wireAddon` and
74
+ `wireRemoteAddon` stay at `#pikku/addon`.
75
+
76
+ `wireAddon` and `wireRemoteAddon` also move off `@pikku/core/rpc` onto
77
+ `@pikku/core/addon`. Being reached over rpc is how an addon is called rather
78
+ than what it is, and it put the whole addon surface behind the rpc subpath for
79
+ consumers that only wanted to install one.
80
+
81
+ - 6eef0a0: Bump every dependency to its latest compatible minor/patch across the monorepo.
82
+ - 3b1164a: feat(react,mantine): ship the dev actor switcher instead of making every app copy it
83
+
84
+ The dev-only "Sign in as …" control — one click signs in as a declared scenario
85
+ persona, no password — was hand-copied into every app that needed it, because
86
+ `pikku fabric validate` requires any frontend with a login screen to have one.
87
+ The `devActors()` / `signInAsActor()` pair was byte-identical everywhere it
88
+ landed, including the `import.meta.env.DEV` gate that keeps the shared secret out
89
+ of production bundles. That is not a thing each app should be re-deriving from a
90
+ copy-paste.
91
+
92
+ Split along the dependency line:
93
+
94
+ - `@pikku/react` gains `useDevActors()`, `signInAsActor()` and `parseDevActors()`.
95
+ UI-free, so it stays inside the package's react-only dependency budget.
96
+ - `@pikku/mantine/dev` gains `<DevActorSwitcher />`, built on that hook. It is a
97
+ new entry point rather than part of `/core`, because `/core`'s contract is
98
+ "drop-in alias for `@mantine/core`" and exporting a component Mantine has no
99
+ counterpart for would break it.
100
+
101
+ The component takes `onSignedIn` rather than depending on a router, and the
102
+ actors/secret are passed in rather than read from env — how env is spelled is a
103
+ bundler fact (`import.meta.env.VITE_*` vs `process.env.NEXT_PUBLIC_*`), and a
104
+ package that guesses gets it wrong for half its consumers.
105
+
106
+ The skills document it in the four places an agent would look: `pikku-better-auth`
107
+ for the `actor` plugin's endpoint (which had only `/dev/quick-login` before, and
108
+ so sent agents to the wrong control), `pikku-scenario` for the actor list being
109
+ the same one a human signs in through, `pikku-react` for the hook, and
110
+ `pikku-fabric` for the validate rule that requires it.
111
+
112
+ `fabric validate` now also accepts a `useDevActors()` call site as evidence the
113
+ control is wired, so apps that want their own UI on the shared logic pass. The
114
+ hand-rolled shape still passes too — nothing existing breaks. Its fix text no
115
+ longer tells you to hand-write the helper, which would have become wrong advice
116
+ the day this shipped.
117
+
118
+ - 266e3bc: `#pikku` is a namespace, not a module: one subpath per wiring
119
+
120
+ The bare `#pikku` specifier resolved to `.pikku/pikku-types.gen.ts`, a hub that
121
+ re-exported all twelve wiring leaves with `export *` — undoing the split the
122
+ leaves exist for, each of which still says so in its own generated header
123
+ ("HTTP-specific type definitions for tree-shaking optimization"). Reaching that
124
+ hub put 33 distinct `@pikku/core` subpaths into the module graph, and neither
125
+ consumer could drop them again: bundlers keep `export *` chains because the app
126
+ declares no `sideEffects`, and Node and tsx do not tree-shake at all, so an app
127
+ with no queues still executed `@pikku/core/queue` at boot.
128
+
129
+ The hub is gone. An app now imports the leaf the name belongs to —
130
+ `#pikku/function`, `#pikku/http`, `#pikku/workflow` — and a project's `imports`
131
+ map declares two patterns, because both resolvers pick the more specific one:
132
+
133
+ ```json
134
+ "#pikku/*.js": "./.pikku/*.ts",
135
+ "#pikku/*": "./.pikku/*/index.ts"
136
+ ```
137
+
138
+ A source tree names the `.ts` on both. Webpack, esbuild and Bun all rewrite a
139
+ `.js` specifier to the `.ts` beside it for a relative import but not for an
140
+ imports-map target, so a `.js` target there resolves to a file that does not
141
+ exist. The two places that keep `.js` are the ones where it is the real file: a
142
+ published addon, whose map points into `dist`, and a project that imports a
143
+ declaration-only generated file such as `pikku-rpc-wirings-map.gen.d.ts`, where
144
+ naming the `.js` lets the type resolver's own mapping reach the `.d.ts`.
145
+
146
+ `pikku` generates the leaf indexes and removes the hub, and `pikku validate`
147
+ reports a barrel import as an error. The split also turns the addon boundary
148
+ from advice into a rule: an addon never generates the wiring leaves, so
149
+ `#pikku/http` fails at the specifier rather than yielding "no exported member"
150
+ from a hub that quietly dropped the re-export.
151
+
152
+ - 9fce0f1: Give a persona step its actor instead of making it unwrap one
153
+
154
+ `requireActor(scenarioStep)` was the first line of every step that acts as
155
+ somebody, and it existed because the actor lived on the `scenarioStep` wire as
156
+ an optional property. A property of a wire member is either optional for every
157
+ binding or required for all of them, so the only expressible answer was
158
+ "optional", and each step paid for it with a guard.
159
+
160
+ The actor is now its own wire member, `wire.actor`, injected by the runner. Wire
161
+ members can be required per binding, so a step declares whether it runs as
162
+ somebody and the type follows:
163
+
164
+ ```typescript
165
+ export const buysAnApple = pikkuScenarioStep<
166
+ { qty: number },
167
+ { orderId: string }
168
+ >({
169
+ name: 'buysAnApple',
170
+ actor: true,
171
+ default: async (_services, { qty }, { actor }) =>
172
+ actor.invoke('placeOrder', { qty }),
173
+ })
174
+ ```
175
+
176
+ A `browser` binding implies it — a window is opened as somebody, so every
177
+ binding of a step that has one gets the actor too. A step that declares neither
178
+ has no `actor` on its wire at all, rather than an optional one: a pure assertion
179
+ over what an earlier step returned has nobody to be, and `attemptsSignIn`
180
+ deliberately posts credentials instead of reusing an actor's established
181
+ session. That distinction is why the requirement is declared per step rather
182
+ than inferred from the step being a persona step — "persona step ⇒ has an actor"
183
+ is false, and a guard built on it rejects the 61 steps in the e2e suite that
184
+ correctly run without one.
185
+
186
+ Dispatching a step that declared an actor without `{ actor: actors.x }` now
187
+ fails before the body runs, with `ScenarioActorRequired` naming the step.
188
+ `ScenarioBrowserActorRequired` is replaced by it, and `requireActor` is gone
189
+ from `@pikku/core/scenario` and the generated `#pikku/scenario` barrel.
190
+
191
+ - 9fce0f1: Say what a scenario step is actually given, and stop the skill teaching an RPC call that throws
192
+
193
+ The `pikku-scenario` skill's two `default` witnesses destructured `rpc` from
194
+ services and called `rpc.invoke`. That is exactly what the scenario runner
195
+ refuses: steps run in the CLI process, and `guardRpc` answers every member with
196
+ _"Scenario tried to run 'getOrder' as an internal step. Every workflow.do in a
197
+ scenario must carry { actor: actors.x }"_. Both examples now go through
198
+ `actor.invoke` off the step's wire, which is the path the surrounding prose
199
+ already described.
200
+
201
+ Adds a **What a step is given** section, because nothing said it. The services
202
+ object is built by hand in `scenario.ts` and holds `logger`, `workflowService`,
203
+ `workflowRunService` and — only when the project declares agents — `agentRunner`.
204
+ There is no `kysely`, no `variables`, no `secrets` and none of the project's own
205
+ services, so a step that destructures one gets `undefined` and fails on first
206
+ use, which reads like a broken container and is not. The section names the three
207
+ ways in (`invoke`, `invokeRaw`, a plain `fetch` at `env.apiUrl`), the two
208
+ consequences that shape how steps get written, and the condition on
209
+ `agentRunner` — `createDevAgentRunner` needs a base URL _and_ a key together, so
210
+ a project with only `OPENAI_API_KEY` set gets `undefined` and every conversing
211
+ scenario fails before the persona says anything.
212
+
213
+ Adds **Declaring personas in TypeScript**, covering the one-call rule and the
214
+ trap underneath it: `definePersonas` is read from source and never evaluated, so
215
+ every value must be statically knowable — but only `name` is validated. A
216
+ computed `personality` is dropped in silence and the persona runs with a blank
217
+ temperament. `stringProperty` accepts `ts.isStringLiteralLike`, so a
218
+ no-substitution template literal is read and is the way to write a long
219
+ personality across several lines; a `+` concatenation is not. Also records that
220
+ `actorInstructions` builds the conversing persona's prompt from `name`,
221
+ `jobTitle`, `personality` and the scenario's `task` only — `disposition`,
222
+ `goals` and `roles` are stored and shown but never reach it.
223
+
224
+ Finally, the three `import ... from '#pikku/workflow/pikku-workflow-types.gen.js'`
225
+ lines now point at `#pikku/scenarios/pikku-scenario-types.gen.js`, which is where
226
+ the scenario surface moved when the barrel was split.
227
+
228
+ Also corrects three import specifiers the skill still taught from before the
229
+ `#pikku` leaves landed: `#pikku/scenarios/pikku-scenario-types.gen.js` and
230
+ `@pikku/core/workflow` both become `#pikku/scenario`, which is the one door the
231
+ leaf exists to be and the specifier every step file in the e2e suite already
232
+ uses.
233
+
234
+ - 727671b: `wireAddon` and `wireRemoteAddon` move from `#pikku/function` to `#pikku/addon`.
235
+
236
+ Installing an addon and authoring one are the same concept from opposite ends,
237
+ so they are one import: an application's `#pikku/addon` carries the two install
238
+ functions, an addon package's carries `pikkuAddonConfig`, `pikkuAddonServices`,
239
+ `pikkuAddonWireServices` and `AddonBaseServices`.
240
+
241
+ Two generation fixes came with it:
242
+
243
+ - `CredentialsMap` is generated as a type alias rather than an interface. An
244
+ interface has no implicit index signature, so it was never assignable to the
245
+ `Record<string, unknown>` that `GetCredential` is constrained by, and every
246
+ generated project reported two errors on its own function types.
247
+ - An unresolved `SingletonServices` type is now `PKU724` instead of a services
248
+ map with no entries in it. Written out, the empty map made every service
249
+ optional and the real failure resurfaced as unrelated "possibly undefined"
250
+ errors in files nobody had touched.
251
+
252
+ ## 0.12.10
253
+
254
+ ### Patch Changes
255
+
256
+ - 7406bfe: Rename the agent runtime from `AI*` to `Agent*` (#596)
257
+
258
+ `AI` described the model provider, not the thing being named. Every symbol that
259
+ belongs to the agent runtime now says `Agent`; the symbols that genuinely wrap a
260
+ model provider — `AIEmbeddingService`, `AIProviderOptions`, `AIEmbedParams`,
261
+ `AITranscriptionParams`, `AIGenerateImageParams` and their siblings, and the
262
+ `@pikku/ai-vercel` / `@pikku/ai-deepinfra` / `@pikku/ai-voice` packages — keep
263
+ their names.
264
+
265
+ **Wiring**
266
+ - `pikkuAIAgent` → `pikkuAgent`, `pikkuAIScorer` → `pikkuAgentScorer`,
267
+ `pikkuAIJudge` → `pikkuAgentJudge`
268
+ - `CoreAIAgent` → `CoreAgent`, `AIAgentInput` → `AgentInput`, `AIAgentStep` →
269
+ `AgentStep`, `AIMessage` → `AgentMessage`, and the rest of the agent types
270
+ - `AIAgentRunnerService` → `AgentRunnerService`, `AIStorageService` →
271
+ `AgentStorageService`, `AIRunStateService` → `AgentRunStateService`
272
+
273
+ **Entry points**
274
+
275
+ `@pikku/core/agent` → `@pikku/core/agent`, `@pikku/core/agent-scorer` →
276
+ `@pikku/core/agent-scorer`.
277
+
278
+ **Queues**
279
+
280
+ The scorer queues are now `agent-score-fast` and `agent-score-slow`. Drain the
281
+ old `ai-score-fast` / `ai-score-slow` queues before deploying — jobs still
282
+ sitting on them when the new workers start will never be picked up.
283
+
284
+ **Scaffolds**
285
+
286
+ The agent scaffold pikku wrote for your project — `<scaffold>/agent/agent.gen.ts`
287
+ and its schemas file — imports `@pikku/core/ai-agent`, which no longer exists. A
288
+ scaffold is normally written once and then left alone, so `pikku all` would find
289
+ it present and leave the broken import in place. It now deletes an agent scaffold
290
+ importing either removed entry point and regenerates it in the same run. Anything
291
+ you added to that file goes with it, so move local edits out first.
292
+
293
+ **Database**
294
+
295
+ The agent tables are renamed: `ai_threads`, `ai_message`, `ai_tool_call`,
296
+ `ai_working_memory`, `ai_run` and `ai_run_score` become `agent_threads`,
297
+ `agent_message`, `agent_tool_call`, `agent_working_memory`, `agent_run` and
298
+ `agent_run_score`, along with their indexes and the `ai_working_memory_pk`
299
+ constraint. The same rename applies to the MongoDB collections.
300
+
301
+ `ensurePikkuSchema` creates tables it cannot find, so an existing database will
302
+ get empty `agent_*` tables and leave the old data stranded in `ai_*`. Rename
303
+ them before the first boot on the new version:
304
+
305
+ ```sql
306
+ ALTER TABLE ai_threads RENAME TO agent_threads;
307
+ ALTER TABLE ai_message RENAME TO agent_message;
308
+ ALTER TABLE ai_tool_call RENAME TO agent_tool_call;
309
+ ALTER TABLE ai_working_memory RENAME TO agent_working_memory;
310
+ ALTER TABLE ai_run RENAME TO agent_run;
311
+ ALTER TABLE ai_run_score RENAME TO agent_run_score;
312
+ ```
313
+
314
+ - e7e5319: Add `pikku semver`, which derives a release's semver from a diff against a deployed surface and writes `.pikku/changes.gen.json`
315
+
316
+ A function or client-facing wiring that disappeared is major, an addition is minor, and a surface that did not move is patch. Where the generated JSON Schemas are available the verdict goes below the id level: a removed field or a newly required input field is breaking, an added optional one is not — direction-aware, so an output field going optional counts even though the same change on an input does not. `versions.pikku.json` is consumed, so a `@v2` bump does not read as a removal while v1 is still published.
317
+
318
+ The baseline is `--against <path|url>`: another `.pikku` directory, a snapshot file, or a snapshot published by `pikku semver --emit`. `--fail-on <level>` turns the verdict into a CI gate.
319
+
320
+ - 411f89a: Add `pikku update`: report which `@pikku/*` dependencies can move forward, and which peers those versions need.
321
+
322
+ Reporting only by default. `--update` writes the new ranges into every covered package.json — the project root plus every workspace it declares — and then runs an install with the package manager the project names (`--no-install` to skip). `--update-peers` additionally writes the ranges unsatisfied peers require; it is separate because a peer bump can cross a major of a third-party package.
323
+
324
+ Peers are read off the version the run lands on rather than the one installed, so an update that needs a companion bump says so before it is applied. Ranges that cannot be substituted into (`workspace:*`, `file:`, unions, x-ranges) are reported and left alone, and a package the registry could not answer for is reported as unresolved rather than current.
325
+
326
+ ## 0.12.9
327
+
328
+ ### Patch Changes
329
+
330
+ - b5fa1e5: Enumerate addon secret and credential grants in the deployment manifest.
331
+
332
+ `wireAddon`'s `secretGrants` / `credentialGrants` widen an addon's scope the same
333
+ way `globalSecrets` does, only narrower — but the manifest reported the exemption
334
+ and not the grant, so a deployment could not see the secrets an app had lent an
335
+ addon. `grantedSecretAddons` and `grantedCredentialAddons` now list them by name,
336
+ including override keys, since scoping is checked before an override renames.
337
+
338
+ The `pikku-addon` skill documents the whole grant family and the scoping rule
339
+ behind it, rather than the override fields alone.
340
+
341
+ ## 0.12.8
342
+
343
+ ### Patch Changes
344
+
345
+ - 0ab1a88: feat(knowledge): draw a note's scenario and its decision, and say the vocabulary exists
346
+
347
+ The console already drew ```mermaid fences as diagrams and `> [!NOTE]` blocks as
348
+ callouts, and nothing told the librarian either existed — the skill that governs
349
+ what goes in a note never mentioned them, so notes were written as prose and
350
+ tables into a renderer that would happily have drawn the graph. The gap was the
351
+ guidance, not the format.
352
+
353
+ Two blocks join them. A slice's ```gherkin scenario is drawn rather than
354
+ highlighted: the keywords line up in a column so the shape of the scenario is
355
+ readable before a word of it is, and each quoted persona becomes a chip — which
356
+ also makes a first-person scenario, the form the format rejects, visibly a block
357
+ with no personas in it.
358
+
359
+ A new ```decision fence states what a decision note owes: `chosen`, `rules-out`,
360
+ `because`. The middle one is the half that gets dropped, so `pikku knowledge
361
+ validate` now warns when a fence says what was chosen and never says what it
362
+ closes off. The fence is optional and a decision argued in prose is still a
363
+ decision — validate checks the fences that exist rather than asking every note
364
+ to be reformatted.
365
+
366
+ `Markdown` is exported from `@pikku/console` so the fabric console can render the
367
+ same notes through the same vocabulary instead of a second `<ReactMarkdown>`.
368
+
369
+ - 8978fbd: feat(workflow): let an approval gate declare who may answer it
370
+
371
+ `workflow.approval()` gains `approvers` (`'any' | 'owner' | 'not-initiator'`)
372
+ and `approverScope`, so a gate can require four-eyes sign-off, restrict itself
373
+ to the run's initiator, or require the decider to hold a named scope.
374
+
375
+ Both are enforced when the workflow replays the gate — the same place, and for
376
+ the same reason, the decision payload is validated: the policy is a value on
377
+ the workflow, and a decision can be recorded before the run has ever reached
378
+ the gate. A decision that fails the policy is discarded and the gate stays
379
+ closed. Where the run has already published its policy, the check also runs at
380
+ submission time so the caller gets a 403 rather than silence.
381
+
382
+ An answer is now recorded where it can be answered for later. The settled
383
+ decision carries `decidedBy` and `decidedAt` in its `ApprovalOutcome`, so who
384
+ signed reaches `workflowStep.result` and `workflowStepHistory` rather than
385
+ living only in mutable run state. Every answer — accepted, refused at the door,
386
+ or cleared on replay — is also written to the audit sink as
387
+ `workflow.approval.decided`, which outlives the run: `deleteRun` cascades to
388
+ steps and history, and a refused attempt never reaches a step at all. Projects
389
+ with no audit service wired are unaffected.
390
+
391
+ **This loosens the default.** `approveStep` previously refused anyone but the
392
+ run's initiator, unconditionally. A gate that declares no `approvers` now
393
+ accepts a decision from anyone the approve entrypoint admits — restore the old
394
+ behaviour per-gate with `approvers: 'owner'`, or gate the approve route with
395
+ `auth`/`permissions`. Ownership still governs _reads_ of a run unchanged.
396
+
397
+ ## 0.12.7
398
+
399
+ ### Patch Changes
400
+
401
+ - e110c55: Add `scenario.expectScore` — grade a finished agent run with a declared scorer and assert on it.
402
+
403
+ An agent's answer cannot be matched against a fixed string, so a scenario grades
404
+ it instead. `expectScore(step, runId, scorer, { atLeast, atMost, reference })`
405
+ runs one declared scorer against the run the scenario just triggered and fails
406
+ with the reason the judge gave. The default bound is `atLeast: 0.5`, so an
407
+ unqualified assertion still fails a run graded zero.
408
+
409
+ Grading goes over the new `pikkuScenarioGradeRun` instrumentation RPC, which the
410
+ dev server registers alongside the coverage and stub RPCs — so it exists only in
411
+ processes that should have it, and never in a deployed bundle. It grades from
412
+ the snapshot the runtime already took when the run finished, which is what makes
413
+ a scenario's grade the same measurement production's sampler makes rather than
414
+ an approximation of it: a run's prompt, answer and tool calls are spread across
415
+ a thread's messages, where the boundary of one run is not recoverable.
416
+
417
+ Two things differ deliberately from live scoring. The sample rate is ignored — a
418
+ scorer grading 1% of traffic still grades every scenario run — and the grade is
419
+ returned rather than recorded, so a test's score never lands among the
420
+ production figures. `reference` supplies the answer key a `requiresReference`
421
+ judge grades against, which is the only way such a judge is reachable at all.
422
+
423
+ - 2f15aad: `pikku workspace validate` is now `pikku validate`, and it checks addon packaging
424
+
425
+ The command no longer needs to be told what kind of project it is looking at.
426
+ Each check declares the condition under which it means anything and runs
427
+ wherever that condition holds, so a repo that is an app, a pile of publishable
428
+ addons, or both gets exactly the checks that apply — and a run that found
429
+ nothing to check says so instead of printing a tick.
430
+
431
+ The new checks are for addons, and both state the same property at a different
432
+ level: every relative import in a shipped generated file, and every `exports` or
433
+ `imports` target, must resolve to a file the package actually publishes.
434
+
435
+ That property was false in every published `@pikku/addon-*`. They shipped
436
+ `dist/.pikku` without the `types/application-types.d.ts` those files import —
437
+ 14 typecheck errors inside `node_modules` for any app depending on one — and
438
+ they published a second, dead copy of `.pikku` at the root whose imports reached
439
+ for a `src/` and `types/` the tarball did not contain, behind the very subpath
440
+ consumers import their bootstrap through.
441
+
442
+ Addons now point every entry point at the built copy under `dist`; the addon's
443
+ own build resolves `#pikku` through tsconfig `paths`, so nothing has to reach
444
+ into the source tree. `pikku new-addon` scaffolds that shape, and the addon
445
+ skill teaches it.
446
+
447
+ ## 0.12.6
448
+
449
+ ### Patch Changes
450
+
451
+ - 2ff07e0: Remove `pikku db seed`. Seeding is now a step of `pikku db reset`, which grew `--no-seed`.
452
+
453
+ `seed` read like something you might point at any environment. It never was. It exists
454
+ for one job: put enough test data into a **dev** database that the app isn't empty on
455
+ first run. Production and staging are provisioned, not seeded — accounts and their role
456
+ grants come from `pikku persona sync` or a migration, and always have.
457
+
458
+ A standalone seed command is also what made seed files unpleasant to write. Because it
459
+ could be run against a database in any state, every seed had to defend itself with
460
+ `INSERT OR IGNORE`, `ON CONFLICT DO NOTHING`, `IF NOT EXISTS`. Folding it into reset
461
+ removes that: `pikku db reset` wipes, migrates, then seeds, so the seed only ever meets
462
+ an empty database and **plain `INSERT`s are correct**. The guarantee is structural now
463
+ rather than a documented convention.
464
+
465
+ ```bash
466
+ pikku db reset # wipe + migrate + test data
467
+ pikku db reset --no-seed # wipe + migrate, empty — for empty-state and onboarding work
468
+ ```
469
+
470
+ Seeding also inherits reset's guards for free: it refuses `NODE_ENV=production`, and
471
+ refuses a database resolved outside the runtime directory.
472
+
473
+ The seed file keeps a name that says what it is:
474
+ - `db/postgres-seed.sql` → `db/postgres-dev-seed.sql`
475
+ - `db/sqlite-seed.sql` → `db/sqlite-dev-seed.sql`
476
+
477
+ **Migrating:** rename the file, and drop the idempotency guards from it if you like.
478
+ `pikku db seed` no longer exists — use `pikku db reset`. A project that keeps the old
479
+ filename gets no error: reset reports the database is empty, which is the one failure
480
+ mode worth knowing about up front. The Fabric validator's `seed-sql-missing` finding is
481
+ now `dev-seed-sql-missing` and looks for the new name.
482
+
483
+ - 1e74b01: Remove `pikku db seed`. Seeding is now a step of `pikku db reset`, which grew `--no-seed`.
484
+
485
+ `seed` read like something you might point at any environment. It never was. It exists
486
+ for one job: put enough test data into a **dev** database that the app isn't empty on
487
+ first run. Production and staging are provisioned, not seeded — accounts and their role
488
+ grants come from `pikku persona sync` or a migration, and always have.
489
+
490
+ A standalone seed command is also what made seed files unpleasant to write. Because it
491
+ could be run against a database in any state, every seed had to defend itself with
492
+ `INSERT OR IGNORE`, `ON CONFLICT DO NOTHING`, `IF NOT EXISTS`. Folding it into reset
493
+ removes that: `pikku db reset` wipes, migrates, then seeds, so the seed only ever meets
494
+ an empty database and **plain `INSERT`s are correct**. The guarantee is structural now
495
+ rather than a documented convention.
496
+
497
+ ```bash
498
+ pikku db reset # wipe + migrate + test data
499
+ pikku db reset --no-seed # wipe + migrate, empty — for empty-state and onboarding work
500
+ ```
501
+
502
+ Seeding also inherits reset's guards for free: it refuses `NODE_ENV=production`, and
503
+ refuses a database resolved outside the runtime directory.
504
+
505
+ The seed file keeps a name that says what it is:
506
+ - `db/postgres-seed.sql` → `db/postgres-dev-seed.sql`
507
+ - `db/sqlite-seed.sql` → `db/sqlite-dev-seed.sql`
508
+
509
+ **Migrating:** rename the file, and drop the idempotency guards from it if you like.
510
+ `pikku db seed` no longer exists — use `pikku db reset`. A project that keeps the old
511
+ filename gets no error: reset reports the database is empty, which is the one failure
512
+ mode worth knowing about up front. The Fabric validator's `seed-sql-missing` finding is
513
+ now `dev-seed-sql-missing` and looks for the new name.
514
+
515
+ - 95f6144: Audit the twelve core skills against the shipped APIs and correct the drift.
516
+ - pikku-ai-agent: `instructions` does not exist — the prompt is `role`/`personality`/`goal` (required); tools are `ref()` handles; import from `#pikku/agent/pikku-agent-types.gen.js`; invoke via `rpc.agent.*` rather than `runAIAgent(name, input, { singletonServices })`
517
+ - pikku-scenario: step bodies live under `default`/`browser`/`cli` bindings, not a `func`; `scaffold.scenarios` is a boolean, not the rejected `"auth"` string
518
+ - pikku-addon: there is no `addon()` helper — `ref()` covers local and addon functions
519
+ - pikku-realtime: SSE is `PikkuRealtime.subscribeToTopic`; `publish`'s channelId argument excludes rather than targets
520
+ - pikku-cli: factories come from `#pikku`; documents options parsing, permissions/middleware/auth and the generated websocket backend
521
+ - pikku-services, pikku-config, pikku-middleware, pikku-rpc, pikku-workflow, pikku-queue, pikku-cron, pikku-websocket: corrected option names, wire objects, scopes/secrets coverage and cross-skill routing
522
+
523
+ - facd61f: Audit the remaining skills against the shipped APIs and correct the drift.
524
+ - pikku-mcp: there is no `wireMCPTool` — a tool _is_ the function (`mcp: true` or `pikkuMCPToolFunc`), while `uri`/`title`/`name` belong on `wireMCPResource`/`wireMCPPrompt` rather than on the `pikkuMCP*Func` factories; resources return `{ uri, text }` only; `PikkuMCPServer` takes `(config, logger)`
525
+ - pikku-http: `channel` is on the wire, not services; `sse` is `get`-only and `query` is `post`-only; `docs` was never a `wireHTTP` option; factories come from `#pikku`
526
+ - pikku-security: documents `authBearer`'s static-token mode, `authCookie`'s merged defaults and re-issue rule, and that every strategy is a no-op without an HTTP request or with a session already set
527
+ - pikku-better-auth: the `admin:users:*` scope tree gained create/ban/remove/sessions/password, and `syncProjectedAdminRole` projects them onto `user.role` for better-auth's own `admin()` endpoints; documents dev quick login
528
+ - pikku-react / pikku-react-query / pikku-workflows-client: `createPikku` options are flat `CorePikkuFetchOptions` with `authHeaders` and the `setAuthorizationJWT`/`setAPIKey`/`setHeader` setters (no request interceptor); `useWorkflowStatus` never stops polling on its own
529
+ - pikku-trigger: a source function runs once at startup with singleton services only; documents `InMemoryTriggerService` startup and the skipped-metadata warning
530
+ - pikku-schedule: the singleton is `schedulerService` and `start()` is what registers the cron jobs; documents `scheduleRPC` and the one-off task API
531
+ - pikku-ws: there is no `PikkuWSServer` — `pikkuWebsocketHandler({ server, wss, logger })` over a `noServer: true` `WebSocketServer` is the real API
532
+ - pikku-info: there are only four subcommands, and `--silent` works despite the spurious "Unknown option" warning
533
+ - pikku-versioning: `override` is not required — a matching `V<n>` export suffix is stripped automatically — and the live function must be bumped explicitly; `versions init` writes an empty manifest, so `versions update` has to follow it
534
+ - pikku-audit: documents `audit: { durability }`, the `Safe<>` guard on `auditLog.write`, `createInvocationAudit`'s logger argument, and `createAuditedKysely`'s options
535
+ - pikku-kysely: six packages, not four — `@pikku/kysely-node-sqlite` / `-bun-sqlite` build the instance functions query, while `createSQLiteKysely` is typed to `KyselyPikkuDB` and wires `SerializePlugin`; the secret service config is `{ key, keyVersion, previousKey, audit }`, not `{ kekSecret, salt }`, and `getSecret` returns a `SecretValue`
536
+ - pikku-emails: template variables are always optional and never required-able; unresolved placeholders render blank rather than failing; documents `pikku emails init`
537
+ - pikku-rtl: rewritten off i18next — there is no `t()` or `i18n.changeLanguage` anywhere in the repo; Arabic is a `messages/ar.json` listed in `project.inlang/settings.json`
538
+ - pikku-i18n: enum labels use the singular `enum__<group>__<member>` namespace `@pikku/paraglide` generates from, not hand-written `enums__` maps; notes the console's wrapped `m` as a leftover rather than a pattern, and that the `mKey`/`mList` runtime resolvers have been removed for good
539
+ - pikku-deps: the summary has `totalIssues`/`totalUpdates` and no `info` bucket, issue `url`/`cvssScore`/`recommendedVersion` are nullable rather than optional, lockfile detection covers pnpm and npm too, and a non-zero `bun audit` exit only counts as data when it produced output
540
+ - pikku-feature: stage changed files by path — `git add -A` sweeps up regenerated artifacts and, on a shared checkout, another agent's work
541
+ - pikku-jose: `decode` verifies the signature and expiry (it is not an unchecked read), keys resolve by the token's `kid` rather than being tried in turn, and the algorithm is fixed HS256
542
+ - pikku-machine-auth: documents restricting a key below its owner via `scopes` on the mapped session, the deliberate verify-vs-scope failure split, and that `betterAuthStatelessSession` has no api-key path
543
+ - pikku-redis / pikku-mongodb: the secret-service config is `{ key, keyVersion, previousKey, … }`, not the fabricated `{ kekSecret, salt }`; both packages also ship a `SessionStore`
544
+ - pikku-pino: log methods take trailing meta varargs and are `Safe<>`-guarded against secrets; `debug` takes a string only
545
+ - pikku-aws / pikku-backblaze: every `ContentService` method takes an args object with a logical `bucket` stored as a path prefix, not positional arguments; `S3ContentConfig` is `{ bucketName, region, endpoint }` and `B2ContentConfig` has no `cdnUrl`; documents `signURL` failing open, the fixed 3600s presign, SQS's 900s delay ceiling and throwing `getJob`, and that `AWSSecrets.getSecret` returns a `SecretValue` and reports every failure as the same fatal error
546
+ - pikku-gateway-slack: `SlackGatewayAdapter` takes `{ signingSecret, tokenResolver }` — there is no `botToken`, one adapter serves every workspace; `verifySlackSignature` is `(secret, signature, timestamp, body)` and returns a boolean; `parseSlashCommand` returns camelCase fields with the raw payload on `raw`; the generic `send()` is a no-op, so replies must go through `createBoundSend`/`SlackGatewayHelper`
547
+ - pikku-ai-vercel: model strings are `provider/model`, not `provider:model`; documents the `'*'` catch-all, `withApiKey`, the transcribe/speech/image/embed methods, and that the service key must be `aiAgentRunner`
548
+ - pikku-ai-voice: rewritten — `@pikku/ai-voice` is a deprecated empty package with no `STTService`/`TTSService`; `voiceInput`/`voiceOutput` come from `@pikku/core/ai-agent` and attach via `aiMiddleware`, with per-script voices, `NoSpeechDetectedError`, and speak-only-when-spoken-to
549
+ - both above: there is no `wireAIAgent` — agents are declared with `pikkuAIAgent` from the generated agent types
550
+ - pikku-schema-ajv / pikku-schema-cfworker: the two validators are not drop-in equivalents — AJV caches by name forever and fills defaults in place, cfworker recompiles on a changed schema and applies no defaults; a missing schema throws a bare string rather than an `Error`
551
+ - pikku-n8n-import: the output directory is `--out/-o`, not a positional argument, and `pikku import n8n` already accepts a directory and flattens array/`{workflows:[]}` exports — an un-importable workflow is skipped while the rest of a batch still imports
552
+ - pikku-template-clone: `create-pikku` keeps only the chosen package manager's lockfile and may have written an empty `yarn.lock`, so commit it after the first install rather than as scaffolded
553
+ - pikku-fabric: the wirings file comment claimed a `wireMCPTool` that has never existed, and the conversion checklist named `fabric.config.json` with a `production.branch` — the real file is `pikkufabric.config.json` with `production.domain`; notes that several CLI messages print the shorter name anyway
554
+ - pikku-fabric-debug: `metrics` also requires `--branch`, and `--follow`'s own help text advertises SSE for what is a 2-second client poll
555
+ - pikku-deploy-express: documents `getHttpServer`/`enableReaper`, that the health check is registered in the constructor (before any middleware, so it cannot be wrapped in auth), what `init()` installs and in what order, and that Express buffers the body so the parser limit is the only place `maxBodySize` can stop an oversized request
556
+ - pikku-deploy-fastify: `enableCors` throws `Method not implemented.`; the health check lives in `init()`, not the constructor; the plugin registers a catch-all `fastify.all('/*')` and only sets `bodyLimit` when `maxBodySize` is supplied, so Fastify's stricter 1MB default otherwise stands
557
+ - pikku-deploy-uws: `PikkuUWSServer` has no `enableCors`, static assets or `content`; `init()` registers the health check plus catch-all HTTP and websocket handlers, `httpOptions` never reaches the websocket one and `loadSchemas` is never passed; `stop()` throws a bare string and waits a fixed 2s; documents the byte-counting `maxBodySize` 413 and why `@pikku/ws` needs `noServer: true`
558
+ - pikku-deploy-lambda: `runFetch` is payload v1 and `runFetchV2` v2 — only v2 echoes an origin or returns a 500; scheduled handlers should use `runLambdaScheduled`, which runs every task in the bundle and swallows per-task failures; the SQS worker's `batchItemFailures` needs `ReportBatchItemFailures` to mean anything; websocket handlers return a real `APIGatewayProxyResult` that must not be replaced with a hardcoded 200; documents the handler factories, `SQS_QUEUE_URL_*` resolution and the binary-unsupported/stale-connection eventhub behaviour
559
+ - pikku-deploy-cloudflare: the hand-rolled `setup-services.ts` never called `setSingletonServices`, so every request would have thrown a CF 1101 — use the exported `setupServices(env, factories)` and the handler factories; documents `runFetch`'s 426 upgrade path, `cf-ray` traceId and `exposeErrors: false` default, that `runScheduled` stops after the first cron match, and the `WEBSOCKET_HIBERNATION_SERVER` binding plus the 1008/403 connect-denial path
560
+ - pikku-deploy-nextjs: `pikkuAPIRequest` strips a leading `/api` (toggle with `removeAPIPrefix`) and passes no wiring options; the helper set includes `patch` and has no `staticPatch`/`staticDel`; the static variants differ by `skipUserSession`, not just where they run, and both bubble errors; documents `PikkuNextJSWorkerRPC` and `toNextJsAuthHandler`
561
+ - pikku-deploy-azure: the exports are `AzInvocationLogger` and `PikkuAZTimerRequest` — `PikkuAzFunctionsLogger` never existed; real deployments go through `createAzureHandler(factories, handlerTypes)` returning `{ http, queue, timer }`; `createAzureWebSocketHandler` is a 501 stub; documents the text-flattened HTTP response, `AZURE_QUEUE_NAME_*` resolution, the 7-day visibility cap, the timer running every task without per-task error handling, and `setLevel` being a no-op
562
+ - pikku-product-second-opinion: stop asserting a fixed TanStack Start release stage — `@tanstack/react-start`'s major tracks the Router line, so the version says nothing about maturity; check the vendor at write-up time
563
+
564
+ - 2f72189: Point the `versions check` hints at a command that exists.
565
+
566
+ Three different failures told you to run `npx pikku versions-update`. There is
567
+ no such command — `update` is a subcommand of `versions` — so anyone following
568
+ the hint hit "unknown command" at the moment they were trying to repair a
569
+ contract manifest. It now prints `npx pikku versions update`.
570
+
571
+ The pikku-versioning skill carried a paragraph warning agents the hint was
572
+ wrong; with the hint fixed, that warning is gone.
573
+
574
+ - 7b0da5e: Point `versions check` at a command that exists.
575
+
576
+ Three of its diagnostics told you to run `npx pikku versions-update`. There is
577
+ no such command — `update` is a subcommand of `versions`, so following the hint
578
+ gets an unknown-command error at the exact moment you have a failing check to
579
+ clear. They now print `npx pikku versions update`.
580
+
581
+ The pikku-versioning skill carried a paragraph warning agents off the bad hint.
582
+ With the hint corrected the warning is the only thing left naming a command that
583
+ does not exist, so it goes too.
584
+
585
+ ## 0.12.5
586
+
587
+ ### Patch Changes
588
+
589
+ - fd72e58: Drop `scenario.step` — a scenario step is now always a `given`, `when` or
590
+ `then`.
591
+
592
+ `step` rendered no keyword, which made it the phase to reach for whenever a
593
+ step did not obviously fit one of the three. That is exactly the step a reader
594
+ cannot check: a scenario is read by people deciding whether it describes the
595
+ behaviour they wanted, and a row that says what it does without saying whether
596
+ it is setup, action or claim tells them nothing to agree or disagree with. It
597
+ was also the escape hatch from the assertion lint — a scenario with no `then`
598
+ could be made to stop complaining by demoting its steps rather than by
599
+ asserting anything.
600
+
601
+ Replace `scenario.step(...)` with whichever of `given`, `when` or `then` the
602
+ step actually is. `then` is not a rename: it makes the step's bindings
603
+ witnesses rather than alternatives, so every declared surface runs and they
604
+ must agree.
605
+
606
+ - 75e81b1: Document `pikkuServerLifecycle` in the skills corpus. `pikku-concepts` now presents both bootstrap paths (letting `pikku dev`/`pikku serve` own the server vs. embedding in your own runtime) instead of only the hand-rolled entrypoint, `pikku-services` gains a `pikkuServerLifecycle` reference covering hook ordering, discovery rules and the `afterStop`-runs-after-services-stop caveat, and `pikku-config` documents the `lint` severity map including `customServerBootstrap`.
607
+
608
+ ## 0.12.4
609
+
610
+ ### Patch Changes
611
+
612
+ - 8075f6a: Confine `SecretService` to the places an app is wired.
613
+
614
+ `secrets` is now omitted from the services every function, AI agent, workflow,
615
+ permission and wire receives, and the function runner replaces it with a
616
+ throwing accessor so a cast cannot reach past the type. It stays available in
617
+ `pikkuServices`, `pikkuWireServices`, addon service factories and middleware —
618
+ read a secret there, give it to a service, and have the function ask that
619
+ service.
620
+
621
+ Alongside it:
622
+ - `wireSecret` gains `allowedHosts`, refusing a secret attached to a host it was
623
+ not declared for. Permissive by default; strict via
624
+ `config.secrets.requireAllowedHosts`.
625
+ - `pikku-graph`'s `httpRequest` resolves and attaches its credential inside a new
626
+ `httpRequester` service instead of holding the plaintext in the function.
627
+ - New inspector diagnostics: `PKU950` (a `SecretService` exposed under another
628
+ service name), `PKU951` (a secret read that no `wireSecret` declares) and
629
+ `PKU952` (a secret read with a non-literal key).
630
+
631
+ ## 0.12.3
632
+
633
+ ### Patch Changes
634
+
635
+ - a7b26c5: rename the inspected declarations to `define*`: `wireScope` → `defineScope`, `wireSecret` → `defineSecret`, `wireVariable` → `defineVariable`, `wireCredential` → `defineCredential`
636
+
637
+ `wire*` meant two unrelated things. A transport wiring attaches a function to
638
+ something that can invoke it — `wireHTTP`, `wireChannel`, `wireScheduler`,
639
+ `wireQueueWorker` and the rest — and the thing it wires runs. These four wire
640
+ nothing: they are no-ops that exist only so the call typechecks, they are
641
+ tree-shaken out of the build, and their whole job is to be found by the
642
+ inspector's AST pass and turned into a type union. One word for both left the
643
+ declaration reading like a registration with a runtime.
644
+
645
+ So the vocabulary splits: **`wire*` is a transport, `define*` is an inspected
646
+ declaration.**
647
+
648
+ ```ts
649
+ import { defineScope } from '@pikku/core/scope'
650
+ import { defineSecret } from '@pikku/core/secret'
651
+ import { defineVariable } from '@pikku/core/variable'
652
+ import { defineCredential } from '@pikku/core/credential'
653
+
654
+ defineScope({ admin: { scopes: { invoices: { scopes: { create: {} } } } } })
655
+ ```
656
+
657
+ **Breaking:** no alias is kept. Rename the four call sites; the module subpaths
658
+ (`@pikku/core/scope`, `/secret`, `/variable`) are unchanged.
659
+
660
+ The inspector matches these by identifier text, so a stale `wire*` call is not a
661
+ type error — it is silently not extracted, and the generated union comes back
662
+ empty. That fails as "this scope isn't declared" on code that was fine a moment
663
+ ago, nowhere near the declaration. Grep for the old names rather than trusting a
664
+ clean build.
665
+
666
+ An addon published with `.pikku` output generated before this release re-exports
667
+ `wireSecret` from `@pikku/core/secret` and will not typecheck against this core
668
+ until it is rebuilt and republished.
669
+
670
+ - 457cb25: Add `definePersonas()`: the people a project's scenarios and virtual users run
671
+ as, declared in code.
672
+
673
+ There used to be three names for two-and-a-bit things — an _actor_ in
674
+ `scenarios.actors`, a _persona_ in `scenarios.personas`, and a _virtual user_
675
+ declared separately against an actor. In practice almost every actor was its own
676
+ kind, so the second set carried no information and the third was a third place
677
+ for a name to drift. There is now one declaration:
678
+
679
+ ```ts
680
+ definePersonas({
681
+ shopper: {
682
+ name: 'Sam Shopper',
683
+ jobTitle: 'Shopper',
684
+ personality: 'Buys in a hurry and leaves tabs open',
685
+ roles: ['customer'],
686
+ disposition: 'careless',
687
+ goals: ['Buy something without reading anything'],
688
+ account: {},
689
+ },
690
+ })
691
+ ```
692
+
693
+ A persona is a person: what they are like, what they want, the roles they hold,
694
+ and **one** account they sign in with — `account: {}` plus `linkedAccounts` for
695
+ the rare case of more, modelled on how better-auth does linking. A persona with a
696
+ `disposition` is a virtual user; `runnable: false` marks someone who only ever
697
+ exists to be acted upon — banned, shared with, reset — and is never handed a
698
+ session.
699
+
700
+ **A persona names roles, never scopes.** Scopes come from `defineSystemRole()`
701
+ expansion, so the build fails if a persona names a role nobody declared, and
702
+ fails again if a role confers a scope no `defineScope` declares. Running one only
703
+ ever has to check that its roles are still valid.
704
+
705
+ **Addresses are computed, never declared.** `personaEmail(id, domain, runId)`
706
+ derives `<id>[+runId]@<domain>` from `scenarios.emailDomain`, so a seed, a
707
+ scenario run and a virtual-user run cannot disagree about who they are signing in
708
+ as. `scenarios.actors` and `scenarios.personas` are gone from
709
+ `pikku.config.json` — only `emailDomain` remains.
710
+
711
+ `actor` survives in exactly one place: the name of a **slot in a scenario step**,
712
+ which is the role a persona is cast in for that step. `pikkuVirtualUser()`,
713
+ `kind`, `grants` and the `actor` field are removed; the `actors` service is now
714
+ `personas`, and the CLI's `virtual-user` commands are now `pikku persona list` /
715
+ `pikku persona run`. `budget` and `allowApprovalRequired` moved to run flags —
716
+ how much you will spend today is not a fact about a person.
717
+
718
+ `@pikku/cucumber` drops its `Actor` class and `ActorDispatchContext`: a
719
+ hand-rolled cookie jar that a persona's own typed session replaces outright.
720
+
721
+ - 86a50b9: scenario: replace `browser: true` + `func` with per-surface bindings on `pikkuScenarioStep`
722
+
723
+ A step now declares one implementation per surface it can be driven through:
724
+
725
+ ```ts
726
+ export const buysTheItem = pikkuScenarioStep<{ sku: string }, { orderId: string }>({
727
+ name: 'buysTheItem',
728
+ description: 'buys the item',
729
+ browser: async (services, data, { browser }) => { ... },
730
+ default: async (services, data, { rpc }) => { ... },
731
+ })
732
+ ```
733
+
734
+ `pikku scenario run --run browser|cli|default` picks which surface the run drives,
735
+ and the two phases resolve bindings differently:
736
+ - **Actions** (`given` / `when` / `step`) run exactly one binding — the run
737
+ surface if it has one, otherwise `default`. A step with neither now fails with
738
+ `ScenarioNoSurfaceBinding` instead of silently running server-side.
739
+ - **Assertions** (`then`) are witnesses, not alternatives: every declared binding
740
+ runs and they must agree. Two surfaces reporting different things fails the run
741
+ with `ScenarioWitnessDisagreement` rather than reporting a pass. An assertion
742
+ with no witness the run can execute at all fails with `ScenarioNoWitness` —
743
+ without it the step returns `undefined` and renders as a tick, reporting a pass
744
+ for something nobody checked.
745
+
746
+ A scenario written as a step ladder that never calls `then` is now a **PKU680**
747
+ critical. It proves only that nothing threw, so an assertion-free ladder of
748
+ browser-bound actions would score perfect coverage while checking nothing.
749
+
750
+ The report gains a surface-coverage line — `n/m steps ran on browser`, counted
751
+ over every step, so an action that fell back to the server lowers the ratio
752
+ rather than needing a footnote. That also makes surfaces comparable over one
753
+ denominator: a scenario is `4/4` on a default run and `3/4` on a browser one.
754
+ Assertions that fell back are named separately and gate `--strict`, since a
755
+ sentence claiming the actor saw something nobody looked at is a different problem
756
+ from an action taking a shortcut.
757
+
758
+ **Breaking:** `browser: true` and the third `B extends boolean` type argument are
759
+ gone. Rename `func` to `default` (or to `browser` where the step drove a browser)
760
+ and drop the type argument.
761
+
762
+ ## 0.12.2
763
+
764
+ ### Patch Changes
765
+
766
+ - b89d3b3: Bring the knowledge base into OSS: a package, a CLI gate, a console browser and a skill
767
+
768
+ `knowledge/` is where a project records the things `pikku meta` cannot tell you —
769
+ what a slice is for, which rule was chosen and what it rules out, what is still an
770
+ open question. Tables, routes, schemas and permissions are generated, so a note
771
+ that repeats them is a copy that will drift, and the profile refuses the sections
772
+ where that happens.
773
+ - **`@pikku/knowledge`** (new) reads the notes, builds the link graph in both
774
+ directions, and validates the app-project profile: every note typed, every
775
+ section indexed, every slice carrying a third-person gherkin scenario and at
776
+ most three entities, and every `resource:` URI resolving against the generated
777
+ meta. The resource check fails closed on drift and open on ignorance — a prefix
778
+ whose meta is absent is skipped rather than called dangling.
779
+ - **`pikku knowledge validate`** and **`pikku knowledge index`** replace the dead
780
+ three-flat-files check. Both exit non-zero on an inconsistent base, so a
781
+ pipeline can stop on one; `index` refreshes each `index.md` listing while
782
+ leaving the prose around it alone, and now gives a section that holds only
783
+ sub-sections an index of its own instead of leaving it unreachable.
784
+ - **The console** gains a read-only Knowledge page: notes grouped by section,
785
+ a rendered document with its tags, resources, links in both directions and the
786
+ findings against it, and intra-bundle markdown links that open the linked note
787
+ instead of leaving the page. Read-only by design — a note is edited in the repo,
788
+ in the same commit as the code it describes.
789
+ - **The `pikku-knowledge` skill** documents the format for agents, and Fabric
790
+ builds on it rather than restating it.
791
+ - **`@pikku/inspector`**: a zod schema imported from a built workspace package
792
+ resolved to that package's `.d.ts`, which has no runtime exports at all, so
793
+ every schema in it was reported missing. The emitted JS beside it is imported
794
+ instead.
795
+
796
+ - e14c530: Drop OpenCode-specific discovery guidance from the bundled skills
797
+
798
+ Step 1 of the execution checklist in 43 skills opened with "Prefer OpenCode
799
+ tools such as `pikku-meta` when available; otherwise run the relevant
800
+ `pikku meta ... --json` command". The skills ship to every agent that reads
801
+ them, most of which have no such tools, so the preferred branch was dead
802
+ advice that an agent had to reason past before reaching the instruction that
803
+ actually applies.
804
+
805
+ The step now just says to run `pikku meta ... --json`. The README still notes
806
+ that the frontmatter shape is the one Claude Code, opencode and pi.dev all
807
+ parse — that is a compatibility fact about the format, not a routing hint.
808
+
809
+ ## 0.12.1
810
+
811
+ ### Patch Changes
812
+
813
+ - 637e668: Move the bundled agent skills out of `@pikku/cli` into a new MIT-licensed `@pikku/skills` package.
814
+
815
+ The skills are the open core — the instruction set any harness reads to build, wire and deploy a Pikku project — but they shipped inside `@pikku/cli`, whose `files` array carried `skills/` under BUSL-1.1 with no carve-out. Their terms now stand on their own package and no longer depend on the CLI that installs them.
816
+
817
+ This also fixes `pikku skills install` on the native binaries. `bun build --compile` only bundles the JS import graph, so 81 markdown files reached through `readdir` never made it in: every Homebrew install failed with `Could not locate bundled skills directory`, while npm installs worked. `@pikku/skills` ships both the `skills/` directory and an embedded path → contents manifest, and reads prefer the directory when one exists — so skill edits stay live in development, and the binary falls back to the manifest it now carries.
818
+
819
+ No skill content changed, and `pikku skills install` takes the same flags.