@pikku/skills 0.12.1 → 0.12.4

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 (50) hide show
  1. package/dist/skills.gen.js +1 -1
  2. package/package.json +1 -1
  3. package/skills/pikku-addon/SKILL.md +1 -1
  4. package/skills/pikku-ai-agent/SKILL.md +1 -1
  5. package/skills/pikku-ai-vercel/SKILL.md +1 -1
  6. package/skills/pikku-ai-voice/SKILL.md +1 -1
  7. package/skills/pikku-aws/SKILL.md +1 -1
  8. package/skills/pikku-backblaze/SKILL.md +1 -1
  9. package/skills/pikku-better-auth/SKILL.md +35 -24
  10. package/skills/pikku-cli/SKILL.md +1 -1
  11. package/skills/pikku-concepts/SKILL.md +8 -1
  12. package/skills/pikku-concepts/references/concept-mapping.md +2 -2
  13. package/skills/pikku-config/SKILL.md +80 -40
  14. package/skills/pikku-cron/SKILL.md +1 -1
  15. package/skills/pikku-deploy-azure/SKILL.md +1 -1
  16. package/skills/pikku-deploy-cloudflare/SKILL.md +1 -1
  17. package/skills/pikku-deploy-express/SKILL.md +1 -1
  18. package/skills/pikku-deploy-fastify/SKILL.md +1 -1
  19. package/skills/pikku-deploy-lambda/SKILL.md +1 -1
  20. package/skills/pikku-deploy-nextjs/SKILL.md +1 -1
  21. package/skills/pikku-deploy-uws/SKILL.md +1 -1
  22. package/skills/pikku-fabric/SKILL.md +6 -6
  23. package/skills/pikku-feature/SKILL.md +7 -7
  24. package/skills/pikku-gateway-slack/SKILL.md +1 -1
  25. package/skills/pikku-http/SKILL.md +1 -1
  26. package/skills/pikku-info/SKILL.md +1 -1
  27. package/skills/pikku-jose/SKILL.md +1 -1
  28. package/skills/pikku-knowledge/SKILL.md +207 -0
  29. package/skills/pikku-kysely/SKILL.md +1 -1
  30. package/skills/pikku-mcp/SKILL.md +1 -1
  31. package/skills/pikku-mongodb/SKILL.md +1 -1
  32. package/skills/pikku-pino/SKILL.md +1 -1
  33. package/skills/pikku-queue/SKILL.md +1 -1
  34. package/skills/pikku-react/SKILL.md +1 -1
  35. package/skills/pikku-react-query/SKILL.md +1 -1
  36. package/skills/pikku-realtime/SKILL.md +1 -1
  37. package/skills/pikku-redis/SKILL.md +1 -1
  38. package/skills/pikku-rpc/SKILL.md +1 -1
  39. package/skills/pikku-scenario/SKILL.md +208 -6
  40. package/skills/pikku-schedule/SKILL.md +1 -1
  41. package/skills/pikku-schema-ajv/SKILL.md +1 -1
  42. package/skills/pikku-schema-cfworker/SKILL.md +1 -1
  43. package/skills/pikku-services/SKILL.md +1 -1
  44. package/skills/pikku-software-archaeology/README.md +16 -6
  45. package/skills/pikku-software-archaeology/references/pikku-mapping.md +30 -30
  46. package/skills/pikku-trigger/SKILL.md +1 -1
  47. package/skills/pikku-versioning/SKILL.md +1 -1
  48. package/skills/pikku-websocket/SKILL.md +1 -1
  49. package/skills/pikku-workflows-client/SKILL.md +1 -1
  50. package/skills/pikku-ws/SKILL.md +1 -1
@@ -6,7 +6,8 @@ description: >-
6
6
  over the real transport against a running server — so a flow doubles as an e2e test and a
7
7
  staged/production health check. Covers scenario.do / expectEventually / expectError /
8
8
  expectService, declared steps via pikkuScenarioStep (including browser steps driven by
9
- @pikku/playwright), actors and environments in pikku.config.json, SCENARIO_ACTOR_SECRET, the
9
+ @pikku/playwright) written as intent rather than as clicks, with the actions factored into
10
+ shared browser utilities, actors and environments in pikku.config.json, SCENARIO_ACTOR_SECRET, the
10
11
  `pikku scenario list|run` commands, live function coverage via `pikku dev --coverage`, and
11
12
  plain unit tests for pure function logic. TRIGGER when: user asks about scenarios, testing a
12
13
  Pikku function, test coverage, end-to-end flows, browser/UI e2e, or health checks. DO NOT
@@ -166,6 +167,200 @@ export const credentialFeature = pikkuFeature({
166
167
 
167
168
  The **feature is the run unit**: `--flows` on a scenario whose every feature entry carries `data` errors and names the features containing it, because the feature is what supplies that data. Use `--features` for those. A scenario referenced bare anywhere, or in no feature at all, still runs standalone.
168
169
 
170
+ ### Steps describe intent, not actions
171
+
172
+ A scenario records what someone was **trying to do**, never the keystrokes they used to do it. This is the one decision that determines whether a suite survives its first redesign, and it applies to every step name you write.
173
+
174
+ | Action ladder — wrong | Intent ladder — right |
175
+ | -------------------------------------- | ---------------------------------------------- |
176
+ | `Given opens /shop` | `Given the shopper is browsing the shop` |
177
+ | `When clicks the category filter` | `When the shopper buys the £5 strawberry milkshake` |
178
+ | `And clicks "Drinks"` | `Then it is in their basket` |
179
+ | `And clicks the first product card` | |
180
+ | `And clicks Add to basket` | |
181
+ | `Then sees "1 item"` | |
182
+
183
+ Three things go wrong with the left-hand column, and all three are expensive:
184
+
185
+ - **A layout change rewrites every scenario that touched that screen.** In the right-hand column it rewrites one function.
186
+ - **The report is the deliverable.** `buys the £5 strawberry milkshake` is readable by someone who has never seen the app; `clicks [data-testid=add]` tells them nothing about whether the product works.
187
+ - **An action step cannot arrive on its own.** It assumes the previous click left the browser somewhere, so the scenario only runs front-to-back, as a whole, in one order.
188
+
189
+ So there are three layers, and only two of them are named in the report:
190
+
191
+ | Layer | What it is | On the ladder |
192
+ | ------------------------------ | --------------------------------------------------- | ------------- |
193
+ | Scenario | The flow, written as intents | yes — the ladder |
194
+ | Step (`pikkuScenarioStep`) | One intent | yes — one row |
195
+ | Utility | An ordinary TS function over `browser` | no |
196
+
197
+ Utilities are **not steps**. They are plain exported functions, they take the browser handle, and they hold the clicking:
198
+
199
+ ```typescript
200
+ // shop.browser.ts — shared actions. Not steps: nothing here is an intent.
201
+ import type { PikkuBrowserWire } from '@pikku/core/workflow'
202
+ import type {} from '@pikku/playwright'
203
+
204
+ /** Arrive on the shop, from wherever the browser happens to be. */
205
+ export const ensureOnShop = async (browser: PikkuBrowserWire) => {
206
+ if (!new URL(browser.page.url()).pathname.startsWith('/shop')) {
207
+ await browser.goto('/shop')
208
+ }
209
+ await browser
210
+ .locate({ testId: 'product-grid' })
211
+ .first()
212
+ .waitFor({ state: 'visible' })
213
+ }
214
+
215
+ export const searchFor = async (browser: PikkuBrowserWire, query: string) => {
216
+ await browser.locate({ testId: 'shop-search' }).first().fill(query)
217
+ await browser.page.keyboard.press('Enter')
218
+ }
219
+
220
+ export const filterByCategory = async (
221
+ browser: PikkuBrowserWire,
222
+ category: string
223
+ ) => {
224
+ await browser.locate({ testId: 'category-filter' }).first().click()
225
+ await browser
226
+ .locate({ testId: 'category-option', where: { 'data-category': category } })
227
+ .first()
228
+ .click()
229
+ }
230
+
231
+ export const addToBasket = async (browser: PikkuBrowserWire, name: string) => {
232
+ const card = browser
233
+ .locate({ testId: 'product-card', containing: name })
234
+ .first()
235
+ await card.waitFor({ state: 'visible' })
236
+ await card.locate('[data-testid=add-to-basket]').click()
237
+ }
238
+ ```
239
+
240
+ The step composes them, and it is the step — one row — that the report shows:
241
+
242
+ ```typescript
243
+ export const buysTheItem = pikkuScenarioStep<
244
+ { name: string },
245
+ { name: string }
246
+ >({
247
+ name: 'buysTheItem',
248
+ description: 'finds one item in the shop and puts it in the basket',
249
+ template: 'buys the {name}',
250
+ // One intent, one implementation per surface an actor can drive it through.
251
+ browser: async (_services, { name }, { browser }) => {
252
+ await ensureOnShop(browser)
253
+ await searchFor(browser, name)
254
+ await addToBasket(browser, name)
255
+ return { name }
256
+ },
257
+ default: async ({ rpc }, { name }) => {
258
+ const item = await rpc.invoke('findItemByName', { name })
259
+ await rpc.invoke('addToBasket', { itemId: item.id })
260
+ return { name }
261
+ },
262
+ })
263
+ ```
264
+
265
+ The two bindings are **alternatives**: `pikku scenario run --run browser` clicks through the shop, `--run default` (the fast suite) takes the server-side path, and both report the same sentence.
266
+
267
+ ```typescript
268
+ await scenario.when(
269
+ 'buys a milkshake',
270
+ 'buysTheItem',
271
+ { name: '£5 strawberry milkshake' },
272
+ { actor: actors.shopper }
273
+ )
274
+ // reporter renders: When the shopper buys the £5 strawberry milkshake ✓ 1.2s
275
+ ```
276
+
277
+ **Every intent step begins by arriving.** `ensureOnShop` is not defensive noise — it is what lets a scenario start at any step, run alone, and be reordered without touching it. It checks first and navigates only if needed, so a scenario already on the shop pays nothing. This is about the *browser's* starting position, not the database: there is still no state reset (see above), and you still scope what you create.
278
+
279
+ **The same utilities, a different intent.** A scenario about filtering has filtering as its subject, so there the filter *is* the intent — same helper, its own step:
280
+
281
+ ```typescript
282
+ export const filtersTheShop = pikkuScenarioStep<
283
+ { category: string },
284
+ { shown: number }
285
+ >({
286
+ name: 'filtersTheShop',
287
+ description: 'narrows the catalogue to one category',
288
+ template: 'filters the shop by {category}',
289
+ browser: async (_services, { category }, { browser }) => {
290
+ await ensureOnShop(browser)
291
+ await filterByCategory(browser, category)
292
+ return {
293
+ shown: await browser.locate({ testId: 'product-card' }).count(),
294
+ }
295
+ },
296
+ default: async ({ rpc }, { category }) => ({
297
+ shown: (await rpc.invoke('listItems', { categorySlug: category })).length,
298
+ }),
299
+ })
300
+ ```
301
+
302
+ Two scenarios, two intents, one set of utilities. That is the shape to aim for: when a helper is reused by a step whose *subject* it is, promote it to a step there — never the reverse.
303
+
304
+ **Non-browser steps need none of this.** Without a browser there is no navigation to absorb and no DOM to hide, so an intent maps to one RPC and `scenario.do` names it directly:
305
+
306
+ ```typescript
307
+ const order = await scenario.do(
308
+ 'Shopper checks out',
309
+ 'createOrder',
310
+ { basketId, shippingAddress },
311
+ { actor: actors.shopper }
312
+ )
313
+ ```
314
+
315
+ Reach for a `pikkuScenarioStep` on the non-browser side only when one intent genuinely spans several RPCs, or when the step asserts something the RPC result alone does not say.
316
+
317
+ ### `then` bindings are witnesses, not alternatives
318
+
319
+ This is the one place the surface bindings do **not** behave like a switch, and it is the part worth reading twice.
320
+
321
+ On a `given` or `when`, the bindings are alternatives — clicking Buy and calling `createOrder` are two ways to cause one effect, so exactly one runs.
322
+
323
+ On a `then`, they are not two implementations of one assertion. They are two *different claims*:
324
+
325
+ | binding | what it actually proves |
326
+ | ------- | ----------------------- |
327
+ | `default` | the order row says `paid` — the system of record is right |
328
+ | `browser` | the confirmation panel says paid — the truth reached the human |
329
+
330
+ The gap between them is the bug nobody catches: 200 OK, database correct, user still watching a spinner. So a `then` runs **every** binding it declares and fails if they disagree.
331
+
332
+ ```typescript
333
+ export const seesTheOrderConfirmed = pikkuScenarioStep<
334
+ { orderId: string },
335
+ { status: string }
336
+ >({
337
+ name: 'seesTheOrderConfirmed',
338
+ template: 'sees order {orderId} confirmed',
339
+ // Both run on `--run browser`. Each returns what it observed, and the runner
340
+ // compares them — so this fails when the page disagrees with the database.
341
+ browser: async (_services, { orderId }, { browser }) => ({
342
+ status: await browser
343
+ .locate({ testId: 'order-status', where: { 'data-order': orderId } })
344
+ .getAttribute('data-status'),
345
+ }),
346
+ default: async ({ rpc }, { orderId }) => ({
347
+ status: (await rpc.invoke('getOrder', { orderId })).status,
348
+ }),
349
+ })
350
+ ```
351
+
352
+ Three rules follow, and they are the ones that get broken:
353
+
354
+ - **A browser witness must observe on the page.** One that quietly calls an RPC to check the result is worse than no binding at all — it reports a tick for a surface it never looked at.
355
+ - **Return what you observed, don't just assert.** A witness returning a value lets the runner diff the two. A witness that only throws still works, but it can never disagree with anything, so it proves less. Read structured state with `where` on the test-id selector rather than parsing translated copy.
356
+ - **A step with no binding for the run's surface is counted, not excused.** `--run browser` prints `n/m steps ran on browser` over *every* step, so an action that quietly fell back to the server lowers the number just as an assertion does. A `then` that fell back is additionally named — `--strict` fails on those, because a sentence saying the actor saw something nobody looked at is a different problem from a shortcut. Not being in the UI *is* the finding: do not add a browser binding that fakes it.
357
+
358
+ **Always give a `then` a `default` witness.** It is the floor every run can fall back to, and an assertion with no witness the run can execute is fatal (`ScenarioNoWitness`) — not a coverage gap. The distinction is the point: a `then` checked server-side under `--run browser` did happen, it just wasn't seen where the prose claims; one checked nowhere never happened at all, and without the error it would return `undefined` and render as a tick. A browser-only `then` is therefore a step that fails the fast suite, which is rarely what you want.
359
+
360
+ **Every scenario must assert.** A flow of only `given`/`when` is a PKU680 critical — it proves nothing threw. Since coverage counts every step, an assertion-free ladder of browser-bound actions would score a perfect `3/3` while checking nothing, so clicking through the UI and never looking at the result is the cheapest way to fake the number. The rule closes that.
361
+
362
+ Assertions with no possible browser witness are a different thing and should not be written as a `then`: "the audit log recorded it" is a system check, and "the receipt email arrives" is `expectEventually`, which is always out-of-band and always server-side.
363
+
169
364
  ### Declared steps (`pikkuScenarioStep`)
170
365
 
171
366
  `scenario.do` can only name an RPC. A **step** is a named, typed unit of scenario behaviour whose body is an ordinary pikku function — so it can call several RPCs as its actor, assert, or drive a browser.
@@ -210,8 +405,12 @@ Rules that bite:
210
405
 
211
406
  ### Browser steps
212
407
 
408
+ `browser` is a boolean on the step, and it is the whole switch: `browser: true` and a browser is guaranteed present on the wire, `browser: false` (the default) and there is none. There is no third state and nothing to null-check — `wire.browser` is optional in the type only for the steps that did not ask for one.
409
+
213
410
  A step declaring `browser: true` gets `wire.browser` — a session bound to **its actor**, signed in through the same `signInPath` + `SCENARIO_ACTOR_SECRET` path the HTTP actors use, so the browser and the RPC calls are one identity. Calling such a step without an actor is a critical error (`PKU677`).
214
411
 
412
+ Browser steps are where **intent, not actions** earns its keep: the step is one intent, the clicking lives in shared utilities, and the step arrives before it acts. Write the mechanics below into utilities and keep the step body to three or four calls that read as a sentence.
413
+
215
414
  ```typescript
216
415
  export const opensTheCart = pikkuScenarioStep<
217
416
  { path: string },
@@ -290,7 +489,7 @@ SCENARIO_ACTOR_SECRET=… pikku scenario run local --features credentialFeature
290
489
  SCENARIO_ACTOR_SECRET=… pikku scenario run local --tags smoke,scenario
291
490
  ```
292
491
 
293
- `run` takes the environment as a **required positional** — the key from `scenarios.environments`. `--flows`/`-f` filters by scenario name, `--features` by feature id, `--tags`/`-t` by tag (match-any). Every filter narrows the same plan, so narrowing a feature to two of its five scenarios still runs the feature's hooks exactly once around those two.
492
+ `run` takes the environment as a **required positional** — the key from `environments`. `--flows`/`-f` filters by scenario name, `--features` by feature id, `--tags`/`-t` by tag (match-any). Every filter narrows the same plan, so narrowing a feature to two of its five scenarios still runs the feature's hooks exactly once around those two.
294
493
 
295
494
  Output is `PASS <name> (<ms>) → <output>` / `FAIL <name> (<ms>): <error>`, then `N/M scenarios passed against '<env>'`. A scenario inside a feature is named `<Feature> › <scenario> <data>`.
296
495
 
@@ -300,13 +499,13 @@ Output is `PASS <name> (<ms>) → <output>` / `FAIL <name> (<ms>): <error>`, the
300
499
 
301
500
  Coverage is attributed by running scenarios against a server that is collecting it. It is **not** derived from unit tests.
302
501
 
303
- Prerequisites in `pikku.config.json`:
502
+ Prerequisite in `pikku.config.json`:
304
503
 
305
504
  ```json
306
- { "scaffold": { "scenarios": "auth" }, "verboseMeta": true }
505
+ { "scaffold": { "scenarios": "auth" } }
307
506
  ```
308
507
 
309
- `scaffold.scenarios` generates the coverage and stub RPCs into your project (`pikkuScenarioTakeLiveCoverage`, `pikkuScenarioResetLiveCoverage`, `pikkuScenarioResetStubs`, `pikkuScenarioGetStubCalls`), so scenario runs work against any server. `verboseMeta` is required — the coverage RPC reads the verbose functions meta and returns `null` without it.
508
+ `scaffold.scenarios` generates the coverage and stub RPCs into your project (`pikkuScenarioTakeLiveCoverage`, `pikkuScenarioResetLiveCoverage`, `pikkuScenarioResetStubs`, `pikkuScenarioGetStubCalls`), so scenario runs work against any server. The coverage RPC reads `<outDir>/function/pikku-functions-meta-verbose.gen.json` off disk at request time — codegen always writes it, but it has to be deployed alongside the app or the RPC returns `null`.
310
509
 
311
510
  ```bash
312
511
  pikku dev --coverage # V8 precise coverage, in-process
@@ -374,8 +573,11 @@ Services are plain objects — a Pikku function is pure business logic, so a moc
374
573
  | A scenario per function | Scenarios are user flows. One flow covers many functions; that is the point. |
375
574
  | Assuming a clean database | There is no state reset — it may be a staging server. Scope what you create. |
376
575
  | `sleep()` before asserting | Use `expectEventually`. |
576
+ | A step named `clicksAddToBasket` / `opensThePage` | That is an action, not an intent. Name the step for what the actor wanted; put the clicking in a utility. |
577
+ | A browser step that assumes it is already on a page | It can then only run mid-flow. Arrive first — check the URL, navigate if needed. |
578
+ | A `browser: true` step guarding `if (!browser)` | `browser: true` guarantees it. The guard hides the real error, which is a missing actor (`PKU677`). |
377
579
  | `expectEventually` in a `pikkuWorkflowFunc` | `PKU675` — scenario-only. |
378
- | Coverage silently 0 | Server not run with `--coverage`, `verboseMeta` off, `scaffold.scenarios` unset, or no actors configured. |
580
+ | Coverage silently 0 | Server not run with `--coverage`, verbose functions meta not deployed, `scaffold.scenarios` unset, or no actors configured. |
379
581
 
380
582
  `@pikku/cucumber` is a **browser/e2e** harness (`Actor`, `BrowserWorld`, `PersonaData`, `DbUtils`) — out of scope here.
381
583
 
@@ -15,7 +15,7 @@ installGroups: [core]
15
15
 
16
16
  Use this skill as an execution checklist, not reference material.
17
17
 
18
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
18
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
19
19
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
20
20
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
21
21
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
@@ -14,7 +14,7 @@ installGroups: [core]
14
14
 
15
15
  Use this skill as an execution checklist, not reference material.
16
16
 
17
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
17
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
18
18
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
19
19
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
20
20
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
@@ -15,7 +15,7 @@ installGroups: [core, fabric]
15
15
 
16
16
  Use this skill as an execution checklist, not reference material.
17
17
 
18
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
18
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
19
19
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
20
20
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
21
21
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
@@ -16,7 +16,7 @@ installGroups: [core]
16
16
 
17
17
  Use this skill as an execution checklist, not reference material.
18
18
 
19
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
19
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
20
20
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
21
21
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
22
22
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
@@ -6,7 +6,7 @@ Reverse-engineers an existing repository into a **Product Blueprint**: the produ
6
6
  Existing Repository → pikku-software-archaeology → .knowledge/ blueprint → new Pikku application
7
7
  ```
8
8
 
9
- This is **not** a code indexer or doc generator. It extracts *intent over implementation*: `POST /api/users/:id/status` becomes the command `ActivateUser`; three scattered `if (inv.user_id !== req.user.id)` checks become one `InvoiceOwnerOnly` policy with three `enforcedAt` citations.
9
+ This is **not** a code indexer or doc generator. It extracts _intent over implementation_: `POST /api/users/:id/status` becomes the command `ActivateUser`; three scattered `if (inv.user_id !== req.user.id)` checks become one `InvoiceOwnerOnly` policy with three `enforcedAt` citations.
10
10
 
11
11
  ## Design decision: the AI is the parser
12
12
 
@@ -19,8 +19,9 @@ In Claude Code, from (or pointing at) the target repo:
19
19
  > Use the pikku-software-archaeology skill to extract a product blueprint from /path/to/repo
20
20
 
21
21
  The agent then:
22
+
22
23
  1. **Surveys** the repo (manifests, entry points, routes, jobs, webhooks, schema, config, TODO/HACK markers) — facts only.
23
- 2. **Excavates the test suite** — `describe`/`it` names become workflow scenarios; assertions confirm policies and upgrade confidence; rules that exist *only* in tests are captured.
24
+ 2. **Excavates the test suite** — `describe`/`it` names become workflow scenarios; assertions confirm policies and upgrade confidence; rules that exist _only_ in tests are captured.
24
25
  3. **Extracts** through twelve lenses (domains, entities, commands, …) per the pipeline in `SKILL.md`. Large repos fan out subagents per lens and merge.
25
26
  4. **Cross-checks and validates**:
26
27
  ```bash
@@ -37,15 +38,23 @@ Concept names are the stable IDs. On re-run after code changes, re-extract only
37
38
 
38
39
  ## How Pikku consumes the blueprint
39
40
 
40
- Full mapping table in `references/pikku-mapping.md`. Summary: entities → Kysely migrations + Zod schemas; commands/queries → `pikkuFunc`s; api surfaces → `wireHTTP`; policies → shared permission functions (collapsing duplicated legacy checks); system workflows → `wireScheduler`/`wireQueueWorker`/`pikkuWorkflowFunc`; integrations → injected services with `wireSecret`/`wireCredential`; test-derived scenarios → `pikkuUserFlow` stories / e2e tests. Humans resolve `migration.json.decisionsNeeded` before any generation starts.
41
+ Full mapping table in `references/pikku-mapping.md`. Summary: entities → Kysely migrations + Zod schemas; commands/queries → `pikkuFunc`s; api surfaces → `wireHTTP`; policies → shared permission functions (collapsing duplicated legacy checks); system workflows → `wireScheduler`/`wireQueueWorker`/`pikkuWorkflowFunc`; integrations → injected services with `defineSecret`/`defineCredential`; test-derived scenarios → `pikkuUserFlow` stories / e2e tests. Humans resolve `migration.json.decisionsNeeded` before any generation starts.
41
42
 
42
43
  ## How uncertainty is represented
43
44
 
44
45
  Every extracted concept carries:
45
46
 
46
47
  ```json
47
- { "evidence": [{ "file": "controllers/invoices.js", "lines": "52", "note": "guard: only drafts editable" }],
48
- "confidence": "high" }
48
+ {
49
+ "evidence": [
50
+ {
51
+ "file": "controllers/invoices.js",
52
+ "lines": "52",
53
+ "note": "guard: only drafts editable"
54
+ }
55
+ ],
56
+ "confidence": "high"
57
+ }
49
58
  ```
50
59
 
51
60
  - **high** — the behavior itself is in the cited code/schema/test. Generates directly.
@@ -53,7 +62,8 @@ Every extracted concept carries:
53
62
  - **low** — plausible reconstruction. Never auto-generated; surfaced for human review.
54
63
 
55
64
  Two further distinctions keep facts and guesses separate:
56
- - `events[].explicit: false` — the event was *reconstructed* from side-effect clusters (email + status flip), not emitted by the code.
65
+
66
+ - `events[].explicit: false` — the event was _reconstructed_ from side-effect clusters (email + status flip), not emitted by the code.
57
67
  - Comments/docs vs code: comments describe intent, code describes behavior. Disagreements are recorded as the code's behavior plus a `gaps.json` entry.
58
68
 
59
69
  ## Repo layout
@@ -2,36 +2,36 @@
2
2
 
3
3
  The `.knowledge/` blueprint is designed so each concept maps onto exactly one Pikku primitive. A generator (or an agent following `pikku-feature`) walks the JSON files in this order:
4
4
 
5
- | Blueprint source | Pikku target |
6
- |---|---|
7
- | `entities.json` attributes + relationships + constraints | Kysely migrations + generated `DB` types; Zod schemas per entity |
8
- | `entities.json` states/transitions | a `status` column + transition guards inside the owning commands (or a state-machine helper) |
9
- | `commands.json` | `pikkuFunc` / `pikkuSessionlessFunc` with `input:` Zod schema built from `input[]`; `preconditions` become guard clauses; name is the camelCased command name (`SendInvoice` → `sendInvoice`) |
10
- | `queries.json` | `pikkuFunc` reads; `scoping` becomes the mandatory `WHERE` / session filter |
11
- | `events.json` | EventHub topics (realtime) or queue messages; `consumedBy` become `wireQueueWorker` handlers — implicit events (`explicit: false`) get promoted to real emissions |
12
- | `policies.json` (authorization) | Pikku `permissions` / middleware; one policy = one named permission function, wired everywhere `enforcedAt` listed — this collapses duplicated legacy checks into a single definition |
13
- | `policies.json` (validation) | Zod schema refinements on the command's `input` |
14
- | `workflows.json` kind=user | frontend flows + the commands they chain |
15
- | `workflows.json` kind=system, with `schedule` | `wireScheduler` entries |
16
- | `workflows.json` multi-step / checkpointing | `pikkuWorkflowFunc` with one `workflow.do(...)` step per blueprint step |
17
- | `workflows.json` `scenarios[]` | **`pikkuUserFlow` stories — this is the canonical target.** Each scenario's given/when/outcome maps 1:1 onto a user-flow step sequence; group scenarios by their workflow into one flow per journey. Only scenarios with no user-facing surface (pure system workflows: cron sweeps, webhook ingest) fall back to API/e2e tests |
18
- | `api.json` | `wireHTTP` routes: keep `path`+`method` for compatibility, point at the mapped command/query func; `auth: none`/capability-URL surfaces get `auth: false` |
19
- | `api.json` kind=webhook-in | `wireHTTP` with `auth: false` + signature-verification middleware from the integration |
20
- | `integrations.json` | services in `services.ts` (constructor-injected classes); `configVia` env vars become `wireSecret` / config; per-user credentials become `wireCredential` |
21
- | `architecture.json` notes | deployment config (ports, raw-body routes, proxy expectations) |
22
- | `invariants.json` enforcedBy=db-constraint | migration constraints (UNIQUE, CHECK, FK) |
23
- | `invariants.json` enforcedBy=code-guard/nothing | guard clauses + a test each; `atRiskBecause` entries get a hardening task |
24
- | `gaps.json` | excluded from generation; `open-product-decision` + `migration.json.decisionsNeeded` go to a human BEFORE generation starts |
25
- | `migration.json.mappings` | the work plan: one mapping = one migration slice |
26
- | `interfaces.json` kind=cli | `wireCLI` entrypoints — the CLI commands are the same funcs the routes expose |
27
- | `interfaces.json` kind=mcp | `wireMCP` — each MCP tool IS a `pikkuFunc` (reuse the command/query funcs; don't author tool duplicates) |
28
- | `interfaces.json` kind=openapi-rest / sdk | generated, not hand-written: the OpenAPI spec + typed client SDK fall out of the `wireHTTP` routes + codegen |
29
- | `interfaces.json` kind=websocket-realtime | `pikku-realtime` EventHub topics / channels |
30
- | `frontend.json` | `apps/app` (TanStack Start) shell: router, `@pikku/mantine` theme, `pikku-react-query` data layer, `better-auth` client — the target stack the legacy UI is rebuilt onto |
31
- | `frontend-routes.json` | TanStack Router routes under `apps/app/src/routes/**` (thin data containers calling `usePikkuQuery`); `dataFrom` names become the generated hooks; subpath routes for rich detail views |
32
- | `frontend-components.json` rebuild=`mantine-standard`/`mantine-composition` | components in `packages/components` composed from `@pikku/mantine` — the trivial/straightforward bulk |
33
- | `frontend-components.json` rebuild=`custom-logic` | the PORT list — each becomes a `packages/components` component that reimplements the bespoke behavior (chart/table/editor); its `dependencies` inform whether the lib is kept or replaced. These are the frontend's real work items |
34
- | `frontend-components.json` rebuild=`custom-style` | normalize to Mantine/theme tokens; usually deleted-and-recomposed, not ported |
5
+ | Blueprint source | Pikku target |
6
+ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | `entities.json` attributes + relationships + constraints | Kysely migrations + generated `DB` types; Zod schemas per entity |
8
+ | `entities.json` states/transitions | a `status` column + transition guards inside the owning commands (or a state-machine helper) |
9
+ | `commands.json` | `pikkuFunc` / `pikkuSessionlessFunc` with `input:` Zod schema built from `input[]`; `preconditions` become guard clauses; name is the camelCased command name (`SendInvoice` → `sendInvoice`) |
10
+ | `queries.json` | `pikkuFunc` reads; `scoping` becomes the mandatory `WHERE` / session filter |
11
+ | `events.json` | EventHub topics (realtime) or queue messages; `consumedBy` become `wireQueueWorker` handlers — implicit events (`explicit: false`) get promoted to real emissions |
12
+ | `policies.json` (authorization) | Pikku `permissions` / middleware; one policy = one named permission function, wired everywhere `enforcedAt` listed — this collapses duplicated legacy checks into a single definition |
13
+ | `policies.json` (validation) | Zod schema refinements on the command's `input` |
14
+ | `workflows.json` kind=user | frontend flows + the commands they chain |
15
+ | `workflows.json` kind=system, with `schedule` | `wireScheduler` entries |
16
+ | `workflows.json` multi-step / checkpointing | `pikkuWorkflowFunc` with one `workflow.do(...)` step per blueprint step |
17
+ | `workflows.json` `scenarios[]` | **`pikkuUserFlow` stories — this is the canonical target.** Each scenario's given/when/outcome maps 1:1 onto a user-flow step sequence; group scenarios by their workflow into one flow per journey. Only scenarios with no user-facing surface (pure system workflows: cron sweeps, webhook ingest) fall back to API/e2e tests |
18
+ | `api.json` | `wireHTTP` routes: keep `path`+`method` for compatibility, point at the mapped command/query func; `auth: none`/capability-URL surfaces get `auth: false` |
19
+ | `api.json` kind=webhook-in | `wireHTTP` with `auth: false` + signature-verification middleware from the integration |
20
+ | `integrations.json` | services in `services.ts` (constructor-injected classes); `configVia` env vars become `defineSecret` / config; per-user credentials become `defineCredential` |
21
+ | `architecture.json` notes | deployment config (ports, raw-body routes, proxy expectations) |
22
+ | `invariants.json` enforcedBy=db-constraint | migration constraints (UNIQUE, CHECK, FK) |
23
+ | `invariants.json` enforcedBy=code-guard/nothing | guard clauses + a test each; `atRiskBecause` entries get a hardening task |
24
+ | `gaps.json` | excluded from generation; `open-product-decision` + `migration.json.decisionsNeeded` go to a human BEFORE generation starts |
25
+ | `migration.json.mappings` | the work plan: one mapping = one migration slice |
26
+ | `interfaces.json` kind=cli | `wireCLI` entrypoints — the CLI commands are the same funcs the routes expose |
27
+ | `interfaces.json` kind=mcp | `wireMCP` — each MCP tool IS a `pikkuFunc` (reuse the command/query funcs; don't author tool duplicates) |
28
+ | `interfaces.json` kind=openapi-rest / sdk | generated, not hand-written: the OpenAPI spec + typed client SDK fall out of the `wireHTTP` routes + codegen |
29
+ | `interfaces.json` kind=websocket-realtime | `pikku-realtime` EventHub topics / channels |
30
+ | `frontend.json` | `apps/app` (TanStack Start) shell: router, `@pikku/mantine` theme, `pikku-react-query` data layer, `better-auth` client — the target stack the legacy UI is rebuilt onto |
31
+ | `frontend-routes.json` | TanStack Router routes under `apps/app/src/routes/**` (thin data containers calling `usePikkuQuery`); `dataFrom` names become the generated hooks; subpath routes for rich detail views |
32
+ | `frontend-components.json` rebuild=`mantine-standard`/`mantine-composition` | components in `packages/components` composed from `@pikku/mantine` — the trivial/straightforward bulk |
33
+ | `frontend-components.json` rebuild=`custom-logic` | the PORT list — each becomes a `packages/components` component that reimplements the bespoke behavior (chart/table/editor); its `dependencies` inform whether the lib is kept or replaced. These are the frontend's real work items |
34
+ | `frontend-components.json` rebuild=`custom-style` | normalize to Mantine/theme tokens; usually deleted-and-recomposed, not ported |
35
35
 
36
36
  ## Order of generation
37
37
 
@@ -16,7 +16,7 @@ installGroups: [core]
16
16
 
17
17
  Use this skill as an execution checklist, not reference material.
18
18
 
19
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
19
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
20
20
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
21
21
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
22
22
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
@@ -16,7 +16,7 @@ installGroups: [core]
16
16
 
17
17
  Use this skill as an execution checklist, not reference material.
18
18
 
19
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
19
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
20
20
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
21
21
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
22
22
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
@@ -15,7 +15,7 @@ description: >-
15
15
 
16
16
  Use this skill as an execution checklist, not reference material.
17
17
 
18
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
18
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
19
19
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
20
20
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
21
21
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
@@ -10,7 +10,7 @@ installGroups: [core]
10
10
 
11
11
  Use this skill as an execution checklist, not reference material.
12
12
 
13
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
13
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
14
14
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
15
15
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
16
16
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
@@ -13,7 +13,7 @@ description: >-
13
13
 
14
14
  Use this skill as an execution checklist, not reference material.
15
15
 
16
- 1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
16
+ 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
17
17
  2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
18
18
  3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
19
19
  4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.