@pikku/skills 0.12.29 → 0.12.32

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/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.29",
3
+ "version": "0.12.32",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/pikkujs/pikku.git",
7
+ "directory": "packages/skills"
8
+ },
4
9
  "description": "The Pikku agent skills — the instruction set coding agents read to build, wire and deploy Pikku projects",
5
10
  "author": "yasser.fadl@gmail.com",
6
11
  "license": "MIT",
@@ -10,13 +15,13 @@
10
15
  "type": "module",
11
16
  "scripts": {
12
17
  "embed": "node scripts/embed.mjs",
13
- "tsc": "yarn embed && tsc",
14
- "build": "yarn embed && tsc -b",
18
+ "tsc": "bun run embed && tsc",
19
+ "build": "bun run embed && tsc -b",
15
20
  "ncu": "npx npm-check-updates",
16
21
  "test": "bash run-tests.sh",
17
22
  "test:watch": "bash run-tests.sh --watch",
18
23
  "test:coverage": "bash run-tests.sh --coverage",
19
- "prepublishOnly": "yarn build"
24
+ "prepublishOnly": "bun run build"
20
25
  },
21
26
  "exports": {
22
27
  ".": "./dist/index.js"
@@ -27,10 +32,9 @@
27
32
  ],
28
33
  "devDependencies": {
29
34
  "@types/node": "^24.13.3",
30
- "typescript": "^6.0.3",
31
35
  "yaml": "^2.9.0"
32
36
  },
33
37
  "engines": {
34
38
  "node": ">=24"
35
39
  }
36
- }
40
+ }
@@ -324,6 +324,11 @@ wireAddon({ name: 'todos', package: '@my-org/addon-todos' })
324
324
 
325
325
  After registration, run `yarn pikku all` to generate types for the addon's functions.
326
326
 
327
+ Give each addon its own wiring file. A deployment unit imports a `wireAddon`
328
+ file only while at least one addon that file wires survives the unit's filter,
329
+ so wiring two addons from one file means a unit needing either one registers
330
+ both and bundles both packages' dependencies.
331
+
327
332
  If the addon ships tables, `pikku db generate` then writes one migration per
328
333
  addon — named after the package, carrying the addon's own SQL — after Better
329
334
  Auth's and the runtime's, so an addon table may reference `user` or a runtime
@@ -23,6 +23,85 @@ how a worker deploy fails at runtime instead of at build.
23
23
  Always run `plan` before `apply`, and read it. It names what will be created,
24
24
  updated and deleted — the deletions are the reason to look.
25
25
 
26
+ ### How many workers you get
27
+
28
+ By default a unit holds every function that builds the same set of singleton
29
+ services. One unit per function is available too, but on a large app it means a
30
+ hundred workers whose bundles are mostly the same framework code repeated, and a
31
+ build and an upload for each. `deploy.grouping` in `pikku.config.json` sets the
32
+ shape:
33
+
34
+ ```json
35
+ "deploy": {
36
+ "grouping": {
37
+ "strategy": "single",
38
+ "rules": [
39
+ { "unit": "console", "addon": "console" },
40
+ { "unit": "pdf", "tags": ["pdf"] }
41
+ ]
42
+ }
43
+ }
44
+ ```
45
+
46
+ `strategy` is the fallback for a function no rule matches:
47
+
48
+ | strategy | fallback |
49
+ | --- | --- |
50
+ | `services` | one unit per distinct set of singleton services (the default) |
51
+ | `function` | one unit each |
52
+ | `single` | one shared unit |
53
+
54
+ Rules are ordered, first match wins, and match on `tags`, an `addon` namespace
55
+ or `routes` globs. A rule always beats the strategy, so under `function` a rule
56
+ merges and under `single` or `services` it carves out.
57
+
58
+ `services` is the default because it is the middle ground: `single` is too
59
+ coarse to reason about and `function` pays a cold start and a deploy step per
60
+ function. Functions are keyed
61
+ on the singleton services their bodies destructure, minus the ones every unit
62
+ builds anyway (`config`, `logger`, `variables`, `schema`, `secrets`, and the
63
+ per-request `rpc`/`mcp`/`channel`/`userSession`). Units are named for that set —
64
+ `svc-todo-store`, `svc-event-hub-todo-store`, `svc-base` for functions needing
65
+ nothing else — and each records it as `servicesKey` in the manifest. On the
66
+ `templates/functions` app it turns 44 units into 10.
67
+
68
+ The deploy target is part of the key, so a `server` unit is named `-server` and
69
+ can never merge with its serverless twin. That is what keeps `services` from
70
+ tripping the mixed-target refusal below: two functions can carry identical
71
+ services and still run in different places, because a function may name
72
+ `deploy: 'server'` itself without any service crossing it.
73
+
74
+ Read the shape before trusting it. The partition depends entirely on how varied
75
+ the app's service use is: an app where nearly every function reaches the same
76
+ database collapses to roughly `single` with a few carve-outs. Run `pikku deploy
77
+ plan` and look at the unit list; set `"strategy": "function"` if you want a unit
78
+ per function back.
79
+
80
+ Changing the strategy renames units, and a unit name is what a queue consumer, a
81
+ scheduled task and a `dependsOn` point at. The manifest rewrites all three, but
82
+ anything holding a unit name outside the manifest does not follow. Units dropped
83
+ from the manifest are not deleted by OSS `deploy()` either (pikkujs/pikku#543) —
84
+ fabric sweeps its dispatch namespace after each deploy, plain pikku leaves the
85
+ old workers in place.
86
+
87
+ `tags` matches the tags a function inherits from its wirings — `wireHTTP({ ...,
88
+ tags: ['pdf'] })` — as well as any on the function itself, which is where almost
89
+ every project writes them.
90
+
91
+ Two things that bite:
92
+
93
+ - **Grouping cannot change a deploy target.** Put a `serverlessIncompatible`
94
+ function in a group with serverless ones and the build fails naming both
95
+ sides. Give it its own rule — that refusal is the design, not a bug. To find
96
+ the culprit, read `deployment-manifest.json`: a unit carries `targetForcedBy`
97
+ naming the services that crossed it to `server`, and `groupedBy` naming the
98
+ rule that made it, so you can see which rule to carve the function out of.
99
+ - **Grouping widens secret scope.** Every function in a unit reads every secret
100
+ that unit is granted, so treat a merge as a security decision too.
101
+
102
+ Group by something that means something — a domain, a secret scope, a deploy
103
+ cadence. There is deliberately no automatic packing by bundle size.
104
+
26
105
  The frontends build independently (`bun run build` at the root builds every
27
106
  workspace). Serve each behind its own hostname, and put the API behind `/api` on
28
107
  **all of them**, mirroring the Vite proxy from the multi-app reference: `/api/auth/*` keeps its
@@ -284,6 +284,20 @@ pikku knowledge plan defer <milestone> <item> -r "<why>"
284
284
 
285
285
  `progress` reconciles the plan against pikku's generated meta — set membership, never anyone's status — and exits non-zero while the first pass is short, or while anything already built contradicts the plan. Unbuilt work in a later pass is reported, not blocked; a function that shipped wide open against a planned permission rule blocks from any pass, because that is a hole rather than a backlog. Writing a plan is its own seat: read `pikku-architect`. Building against one is `pikku-build`.
286
286
 
287
+ ### A finished milestone is a tombstone
288
+
289
+ **Once a milestone reaches `built`, its note and its plan are closed. Do not edit either.** Not to correct the wording, not to fold in what the build actually turned out to need, not to add the item everyone agrees should have been there. A finished milestone is the record of what was agreed and what was measured against it, and a record that can be revised afterwards measures nothing.
290
+
291
+ This is the rule the shape of the thing already implies. `progress` reconciles a plan against generated meta and fails when what shipped contradicts it — a check with no force at all if the losing side of the contradiction may simply be rewritten. `attempts:` brakes a note nothing can satisfy, and refunds that budget when the note's content really changes; a `built` note that keeps changing is that brake removed. Both only work while the plan stays still.
292
+
293
+ So when a `built` milestone turns out to be wrong or incomplete, **the answer is always a new note, never an edit to the old one**:
294
+
295
+ - It needed more than it said → a new milestone, which may name the old one.
296
+ - It was built differently than planned → that is what `progress` is for. Reconcile forward, or record a decision saying why the plan was not the right shape.
297
+ - It was simply wrong → a decision note that supersedes it. The wrong milestone stays where it is; a base whose history is edited cannot answer *why* anything is the way it is, which is most of what a base is for.
298
+
299
+ The exception, and it is narrow: bookkeeping the loop owns. `statusAt:` and `attempts:` are written by whatever moved the note, at any status, and are bookkeeping rather than content. Nothing else about a `built` note moves again.
300
+
287
301
  ## Profiles built on this one
288
302
 
289
303
  OKF permits frontmatter fields a reader does not know, and the parser ignores them rather than failing. That is the extension point: a tool layered on Pikku can add its own sections and fields on top of everything above without forking the format.
@@ -30,6 +30,16 @@ request-scoped logger or audit buffer is a wire service, and startup work that
30
30
  needs the singletons goes in `pikkuServerLifecycle` rather than in a module's
31
31
  top level.
32
32
 
33
+ ## Services the runtime injects
34
+
35
+ `pikku dev` and `pikku serve` build a set of singletons before your
36
+ `createSingletonServices` runs and hand them in as `existingServices` — among
37
+ them `content`, a `LocalContent` storing files under `.pikku-runtime/content`
38
+ and serving them from `/upload` and `/assets`. **You never construct these in
39
+ `services.ts`, and their absence from that file is not evidence they are off.**
40
+ The optional `content` block in `pikku.config.json` only overrides that
41
+ service's paths and size limit; omitting it does not disable it.
42
+
33
43
  ## Pick the reference
34
44
 
35
45
  | You are… | Read |
@@ -1,6 +1,5 @@
1
1
  # Pikku Services (Dependency Injection)
2
2
 
3
-
4
3
  ## Before You Start
5
4
 
6
5
  ```bash
@@ -195,6 +194,13 @@ const createSingletonServices = pikkuServices(async (config) => {
195
194
  })
196
195
  ```
197
196
 
197
+ A deployment split regenerates this manifest **per unit**, so the same
198
+ `services.ts` sees a different manifest in each bundle and each unit builds only
199
+ what its own functions, middleware and permissions reach. A `services.ts` that
200
+ constructs unconditionally gets none of that: every unit pays for every service,
201
+ and the split buys nothing but duplicated bytes. Branching on the manifest is
202
+ what makes the split real.
203
+
198
204
  ### Audit Wire Service
199
205
 
200
206
  `createInvocationAudit` + `createAuditedKysely` add per-request audit buffering that flushes on request close (no-op if `audit` is unconfigured). For the full pattern, no-op behavior, custom-event usage, and Fabric notes, read `references/audit-wire-service.md`.
@@ -166,49 +166,25 @@ export const deleteTodo = pikkuFunc({
166
166
  })
167
167
  ```
168
168
 
169
- ## MCP Server Setup
169
+ ## Reaching the server
170
170
 
171
- `PikkuMCPServer` takes the server config and a logger — not your services. It
172
- loads the generated `mcp.gen.json`, and the bootstrap import is what registers
173
- your functions.
171
+ You do not start an MCP server. `pikku dev`, `pikku serve` and a deployed app all
172
+ mount one for you: codegen writes `.pikku/mcp/mcp.gen.json`, the runtime loads it,
173
+ and the server is served at **`/mcp`** — so a tool you export is reachable at
174
+ `https://<your-app-url>/mcp` with nothing else to wire. Set `mcpPath` to move it.
174
175
 
175
- ```typescript
176
- // start.ts
177
- import { PikkuMCPServer } from '@pikku/modelcontextprotocol'
178
- import { createConfig, createSingletonServices } from './services.js'
179
- import mcpJSON from '../.pikku/mcp/mcp.gen.json' with { type: 'json' }
180
- import '../.pikku/pikku-bootstrap.gen.js'
181
-
182
- const config = await createConfig()
183
- const singletonServices = await createSingletonServices(config)
184
-
185
- const server = new PikkuMCPServer(
186
- {
187
- name: 'pikku-mcp-server',
188
- version: '1.0.0',
189
- mcpJSON,
190
- capabilities: { logging: {}, tools: {}, resources: {}, prompts: {} },
191
- },
192
- singletonServices.logger
193
- )
194
-
195
- await server.init()
196
-
197
- // stdio — the transport desktop MCP clients spawn
198
- await server.connectStdio()
199
- singletonServices.logger = server.createMCPLogger()
200
-
201
- // …or streamable HTTP, for a hosted server
202
- const { close } = await server.connectHTTP({ port: 3000, host: '127.0.0.1' })
203
- ```
176
+ Two consequences worth stating outright, because both read as breakage:
204
177
 
205
- `capabilities` is a filter, not documentation: a surface you leave out is not
206
- advertised and its endpoints are never loaded, which is how you ship a tools-only
207
- server.
178
+ - The mount is **conditional on there being something to serve**. An `mcp.gen.json`
179
+ with no tools, resources or prompts is not mounted at all, so `/mcp` 404s until
180
+ the first `mcp: true` function or `pikkuMCP*Func` exists.
181
+ - There is no `wires/mcp` directory and no `mcp` block in `pikku.config.json`.
182
+ A tool *is* its own registration, so the absence of both is what correct MCP
183
+ wiring looks like — not evidence that something was missed.
208
184
 
209
- Over stdio the protocol owns stdout, so an ordinary console logger corrupts the
210
- frames — that is what `createMCPLogger()` is for. Swap the logger before
211
- anything logs.
185
+ When you add a tool, tell whoever asked for it the URL. An assistant that cannot
186
+ be pointed at an endpoint has not been connected to anything, and `/mcp` is the
187
+ whole answer.
212
188
 
213
189
  ## Red flags
214
190
 
@@ -218,4 +194,4 @@ anything logs.
218
194
  | `uri`/`title` rejected on `pikkuMCPResourceFunc` | Those belong on `wireMCPResource` |
219
195
  | Resource returning `{ uri, blob, mimeType }` | Resources are text only: `{ uri, text }` |
220
196
  | Client sees a tool with no description | `mcp: true` without a `description` — check the codegen warning |
221
- | stdio client disconnects on the first log line | Logger still writing to stdout; use `createMCPLogger()` |
197
+ | `/mcp` 404s | Nothing to serve yet — the mount is skipped until one tool, resource or prompt exists |