@pikku/skills 0.12.9 → 0.12.11

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 (75) hide show
  1. package/CHANGELOG.md +768 -0
  2. package/dist/skills.gen.js +1 -1
  3. package/package.json +4 -4
  4. package/skills/pikku-addon/SKILL.md +20 -14
  5. package/skills/pikku-addon/references/addon-package-manifest.md +2 -2
  6. package/skills/{pikku-ai-agent → pikku-agent}/SKILL.md +24 -24
  7. package/skills/pikku-ai-vercel/SKILL.md +18 -18
  8. package/skills/pikku-ai-voice/SKILL.md +15 -15
  9. package/skills/pikku-audit/SKILL.md +28 -13
  10. package/skills/pikku-aws/SKILL.md +2 -2
  11. package/skills/pikku-better-auth/SKILL.md +97 -17
  12. package/skills/pikku-build-app/SKILL.md +621 -0
  13. package/skills/pikku-build-app/references/multi-app.md +117 -0
  14. package/skills/pikku-build-app/references/ship.md +98 -0
  15. package/skills/pikku-build-app/references/theming.md +70 -0
  16. package/skills/pikku-build-platform/SKILL.md +239 -0
  17. package/skills/pikku-build-quick/SKILL.md +238 -0
  18. package/skills/pikku-cli/SKILL.md +7 -7
  19. package/skills/pikku-cli/references/complete-example.md +1 -1
  20. package/skills/pikku-concepts/SKILL.md +10 -7
  21. package/skills/pikku-concepts/references/concept-mapping.md +1 -1
  22. package/skills/pikku-config/SKILL.md +5 -3
  23. package/skills/pikku-deploy-azure/SKILL.md +5 -4
  24. package/skills/pikku-deploy-cloudflare/SKILL.md +9 -9
  25. package/skills/pikku-deploy-uws/SKILL.md +5 -2
  26. package/skills/pikku-deps/SKILL.md +42 -3
  27. package/skills/pikku-emails/SKILL.md +5 -5
  28. package/skills/pikku-fabric/SKILL.md +27 -3
  29. package/skills/pikku-fabric-debug/SKILL.md +1 -1
  30. package/skills/pikku-feature/SKILL.md +5 -4
  31. package/skills/pikku-http/SKILL.md +4 -4
  32. package/skills/pikku-http/references/http-options.md +13 -13
  33. package/skills/pikku-i18n/SKILL.md +2 -1
  34. package/skills/pikku-info/SKILL.md +1 -1
  35. package/skills/pikku-knowledge/SKILL.md +13 -13
  36. package/skills/pikku-kysely/SKILL.md +68 -41
  37. package/skills/pikku-machine-auth/SKILL.md +10 -10
  38. package/skills/pikku-mcp/SKILL.md +23 -20
  39. package/skills/pikku-middleware/SKILL.md +19 -12
  40. package/skills/pikku-middleware/references/middleware-patterns.md +14 -8
  41. package/skills/pikku-mongodb/SKILL.md +11 -11
  42. package/skills/pikku-n8n-import/SKILL.md +12 -12
  43. package/skills/pikku-n8n-import/SPEC.md +3 -0
  44. package/skills/pikku-n8n-import/references/addon-mapping.md +14 -8
  45. package/skills/pikku-n8n-import/references/code-translation.md +26 -22
  46. package/skills/pikku-n8n-import/references/loops-and-control.md +7 -7
  47. package/skills/pikku-paraglide/SKILL.md +11 -6
  48. package/skills/pikku-permissions/SKILL.md +19 -15
  49. package/skills/pikku-product-second-opinion/README.md +3 -3
  50. package/skills/pikku-product-second-opinion/SKILL.md +83 -73
  51. package/skills/pikku-product-second-opinion/example/sample-report.md +15 -12
  52. package/skills/pikku-product-second-opinion/references/report-template.md +10 -7
  53. package/skills/pikku-queue/SKILL.md +1 -1
  54. package/skills/pikku-react/SKILL.md +53 -13
  55. package/skills/pikku-realtime/SKILL.md +51 -19
  56. package/skills/pikku-rpc/SKILL.md +1 -1
  57. package/skills/pikku-rtl/SKILL.md +1 -1
  58. package/skills/pikku-scenario/SKILL.md +164 -44
  59. package/skills/pikku-schedule/SKILL.md +6 -1
  60. package/skills/pikku-schema-ajv/SKILL.md +2 -2
  61. package/skills/pikku-schema-cfworker/SKILL.md +1 -1
  62. package/skills/pikku-security/SKILL.md +9 -5
  63. package/skills/pikku-services/SKILL.md +27 -18
  64. package/skills/pikku-services/references/audit-wire-service.md +14 -8
  65. package/skills/pikku-software-archaeology/SKILL.md +27 -23
  66. package/skills/pikku-software-archaeology/references/blueprint.schema.json +580 -102
  67. package/skills/pikku-software-archaeology/scripts/validate.mjs +146 -69
  68. package/skills/pikku-tag-middleware/SKILL.md +1 -0
  69. package/skills/pikku-template-clone/SKILL.md +2 -1
  70. package/skills/pikku-trigger/SKILL.md +3 -3
  71. package/skills/pikku-versioning/SKILL.md +87 -3
  72. package/skills/pikku-websocket/SKILL.md +4 -3
  73. package/skills/pikku-workflow/SKILL.md +2 -2
  74. package/skills/pikku-workflow/references/workflow-reference.md +24 -10
  75. package/skills/pikku-ws/SKILL.md +5 -2
@@ -49,17 +49,17 @@ await mongo.close()
49
49
 
50
50
  ### Available Services
51
51
 
52
- | Service | Interface | Purpose |
53
- | --------------------------- | ------------------------------------- | ---------------------------------------------- |
54
- | `MongoDBChannelStore` | `ChannelStore` | WebSocket channel state persistence |
55
- | `MongoDBEventHubStore` | `EventHubStore` | Event hub state persistence |
56
- | `MongoDBWorkflowService` | `PikkuWorkflowService` | Workflow definition storage |
57
- | `MongoDBWorkflowRunService` | `WorkflowRunService` | Workflow execution tracking |
58
- | `MongoDBDeploymentService` | `DeploymentService` | Deployment state management |
59
- | `MongoDBAIStorageService` | `AIStorageService, AIRunStateService` | AI conversation/run storage |
60
- | `MongoDBAgentRunService` | `AgentRunService` | Agent execution tracking |
61
- | `MongoDBSecretService` | `SecretService` | Encrypted secret storage (envelope encryption) |
62
- | `MongoDBSessionStore` | `SessionStore` | Persisted user sessions |
52
+ | Service | Interface | Purpose |
53
+ | ---------------------------- | ------------------------------------------- | ---------------------------------------------- |
54
+ | `MongoDBChannelStore` | `ChannelStore` | WebSocket channel state persistence |
55
+ | `MongoDBEventHubStore` | `EventHubStore` | Event hub state persistence |
56
+ | `MongoDBWorkflowService` | `PikkuWorkflowService` | Workflow definition storage |
57
+ | `MongoDBWorkflowRunService` | `WorkflowRunService` | Workflow execution tracking |
58
+ | `MongoDBDeploymentService` | `DeploymentService` | Deployment state management |
59
+ | `MongoDBAgentStorageService` | `AgentStorageService, AgentRunStateService` | AI conversation/run storage |
60
+ | `MongoDBAgentRunService` | `AgentRunService` | Agent execution tracking |
61
+ | `MongoDBSecretService` | `SecretService` | Encrypted secret storage (envelope encryption) |
62
+ | `MongoDBSessionStore` | `SessionStore` | Persisted user sessions |
63
63
 
64
64
  All services take a `Db` instance in their constructor and have an `init()` method that creates collections/indexes.
65
65
 
@@ -59,13 +59,13 @@ than the exit code to know what landed. Relay every skipped workflow to the user
59
59
  Every unmapped node is a stub that throws `… — implement me`. Classify each by its
60
60
  JSDoc marker and route to the matching reference:
61
61
 
62
- | Stub marker / signal | Handle via |
63
- |---|---|
64
- | `STUB — generated from n8n node "…" (type "n8n-nodes-base.<svc>…")` | `references/addon-mapping.md` |
65
- | `STUB — generated from n8n Code node "…"` | `references/code-translation.md` |
66
- | A `control` stub — Loop Over Items / **splitInBatches**, Switch expr-mode | `references/loops-and-control.md` |
67
- | `STUB — … vector-store … #902` | rare now (RAG ships as `<store>:query`/`:ingest`); a residual one = an unmapped store → report it, don't guess |
68
- | Importer `diagnostics` (already exited 1) | explain the reason; the workflow is un-importable as-is |
62
+ | Stub marker / signal | Handle via |
63
+ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
64
+ | `STUB — generated from n8n node "…" (type "n8n-nodes-base.<svc>…")` | `references/addon-mapping.md` |
65
+ | `STUB — generated from n8n Code node "…"` | `references/code-translation.md` |
66
+ | A `control` stub — Loop Over Items / **splitInBatches**, Switch expr-mode | `references/loops-and-control.md` |
67
+ | `STUB — … vector-store … #902` | rare now (RAG ships as `<store>:query`/`:ingest`); a residual one = an unmapped store → report it, don't guess |
68
+ | Importer `diagnostics` (already exited 1) | explain the reason; the workflow is un-importable as-is |
69
69
 
70
70
  Read a reference file only when you actually hit that stub class.
71
71
 
@@ -99,11 +99,11 @@ Missing integrations — install these or the nodes stay stubs:
99
99
 
100
100
  ## References
101
101
 
102
- | Open when you need to… | Read |
103
- |---|---|
104
- | map an integration stub (gmailTool, slackTool, googleSheets, plain action nodes) to an installed addon `ref(...)` | `references/addon-mapping.md` |
105
- | translate an n8n Code node body into a Pikku function body | `references/code-translation.md` |
106
- | lower a Loop Over Items / splitInBatches loop, or a Switch that stayed a stub | `references/loops-and-control.md` |
102
+ | Open when you need to… | Read |
103
+ | ----------------------------------------------------------------------------------------------------------------- | --------------------------------- |
104
+ | map an integration stub (gmailTool, slackTool, googleSheets, plain action nodes) to an installed addon `ref(...)` | `references/addon-mapping.md` |
105
+ | translate an n8n Code node body into a Pikku function body | `references/code-translation.md` |
106
+ | lower a Loop Over Items / splitInBatches loop, or a Switch that stayed a stub | `references/loops-and-control.md` |
107
107
 
108
108
  ## Final summary
109
109
 
@@ -11,6 +11,7 @@ remainder with judgment, report gaps that need a human decision, and verify.
11
11
  ## Scope
12
12
 
13
13
  In scope:
14
+
14
15
  - Running `pikku import n8n` and triaging its output.
15
16
  - Filling integration stubs (→ addon refs), Code stubs (→ function bodies), and
16
17
  loop/control stubs (→ `graph:map`/reduce/branch).
@@ -18,6 +19,7 @@ In scope:
18
19
  - Verifying via `pikku all` + `tsc` + a zero-surviving-stub check.
19
20
 
20
21
  Out of scope:
22
+
21
23
  - Extending `@pikku/n8n-import` itself (it is frozen; do not add per-service tables
22
24
  or new compiler rules to it).
23
25
  - Authoring workflows from scratch (`pikku-workflow`) or hand-written addon wiring
@@ -47,6 +49,7 @@ Out of scope:
47
49
  ## Source And Evidence Model
48
50
 
49
51
  Authoritative sources:
52
+
50
53
  - `@pikku/n8n-import` codegen (stub markers, manifest shape, `import-n8n` command).
51
54
  - `@pikku/addon-graph` function contracts (`graph:map`/`fanout`, `branch`).
52
55
  - Installed `@pikku/addon-*` source (function names verified by grep, never guessed).
@@ -18,9 +18,11 @@ rewrite the stub.
18
18
  "n8nType": "n8n-nodes-base.gmailTool",
19
19
  "n8nName": "Send a message in Gmail",
20
20
  "parameters": { "sendTo": "...", "message": "...", "subject": "..." },
21
- "credentials": { "gmailOAuth2": { "id": "...", "name": "Personal Gmail" } },
21
+ "credentials": {
22
+ "gmailOAuth2": { "id": "...", "name": "Personal Gmail" },
23
+ },
22
24
  "isAgentTool": true,
23
- "agentName": "Inbox Assistant"
25
+ "agentName": "Inbox Assistant",
24
26
  }
25
27
  ```
26
28
  2. **Installed addons** — `@pikku/addon-*` in the project's `package.json`
@@ -35,12 +37,12 @@ rewrite the stub.
35
37
  Map `n8nType` to a package by reading its source. Common shapes (**guesses, not
36
38
  authoritative** — always verify against installed source):
37
39
 
38
- | n8n type prefix | typical addon candidate |
39
- |---|---|
40
- | `n8n-nodes-base.gmail` / `gmailTool` | `@pikku/addon-email-gmail` |
41
- | `n8n-nodes-base.slack` / `slackTool` | `@pikku/addon-chat-slack` |
42
- | `n8n-nodes-base.googleSheets` / `…Tool` | `@pikku/addon-sheets-google` |
43
- | `n8n-nodes-base.notion` / `notionTool` | `@pikku/addon-docs-notion` |
40
+ | n8n type prefix | typical addon candidate |
41
+ | ------------------------------------------ | ---------------------------- |
42
+ | `n8n-nodes-base.gmail` / `gmailTool` | `@pikku/addon-email-gmail` |
43
+ | `n8n-nodes-base.slack` / `slackTool` | `@pikku/addon-chat-slack` |
44
+ | `n8n-nodes-base.googleSheets` / `…Tool` | `@pikku/addon-sheets-google` |
45
+ | `n8n-nodes-base.notion` / `notionTool` | `@pikku/addon-docs-notion` |
44
46
  | `n8n-nodes-base.telegram` / `telegramTool` | `@pikku/addon-chat-telegram` |
45
47
 
46
48
  If no installed addon plausibly covers the n8n type, stop and report it — do not
@@ -74,6 +76,7 @@ over guessing.
74
76
  Two outcomes, by `isAgentTool`:
75
77
 
76
78
  **A) `isAgentTool: true`** — the stub is an agent tool referenced via `ref()`:
79
+
77
80
  1. Delete the stub file.
78
81
  2. In the agent file, replace `ref('agentGmailtool__sendAMessageInGmail')` in
79
82
  `tools: [...]` with `ref('messageSend')` (the resolved addon function).
@@ -81,13 +84,16 @@ Two outcomes, by `isAgentTool`:
81
84
  `node_modules/@pikku/addon-*`).
82
85
 
83
86
  If you can't delete safely, leave a one-line re-export instead of a stub:
87
+
84
88
  ```ts
85
89
  import { messageSend } from '@pikku/addon-email-gmail'
86
90
  export const agentGmailtool__sendAMessageInGmail = messageSend
87
91
  ```
92
+
88
93
  Default is delete + retarget; wrappers add maintenance burden.
89
94
 
90
95
  **B) `isAgentTool: false`** — the stub is a graph node:
96
+
91
97
  1. Open `<workflow>.graph.ts`.
92
98
  2. In `nodes: { … }` find the entry whose value is the stub rpc name.
93
99
  3. Replace it with the addon function name (`'messageSend'`).
@@ -20,39 +20,39 @@ refactor, add error handling, or invent fields.
20
20
  3. **Apply the rubric.**
21
21
  4. **Edit only the function body.** Leave imports, schemas, JSDoc, name, description, refs untouched unless step 5 forces it.
22
22
  5. **If the schemas are wrong** (code reads `$json.userId: string` but input is `items: z.array(z.unknown())`), tighten with the smallest change. Prefer `z.unknown()` over `z.any()`. Never widen output to `z.any()`.
23
- 6. **Add one comment** at the top of the body noting the mode: `// translated from n8n Code node, mode: runOnceForAllItems`. This is the *only* comment you may add.
23
+ 6. **Add one comment** at the top of the body noting the mode: `// translated from n8n Code node, mode: runOnceForAllItems`. This is the _only_ comment you may add.
24
24
  7. **Typecheck** (`yarn tsc` from the package root); fix errors with the smallest change.
25
25
 
26
26
  ## Rubric
27
27
 
28
28
  ### Envelope unwrapping — all-items mode
29
29
 
30
- | n8n | Pikku |
31
- |---|---|
32
- | `items` | `(data.items ?? []) as any[]` (or typed if known) |
33
- | `items[i].json.X` | `items[i].X` |
34
- | `items[i].json` | `items[i]` |
35
- | `items[i].binary` | **NOT supported** — leave a TODO and explain |
36
- | `items.length` | `items.length` |
37
- | `items.map(i => i.json.X)` | `items.map((i: any) => i.X)` |
30
+ | n8n | Pikku |
31
+ | -------------------------- | ------------------------------------------------- |
32
+ | `items` | `(data.items ?? []) as any[]` (or typed if known) |
33
+ | `items[i].json.X` | `items[i].X` |
34
+ | `items[i].json` | `items[i]` |
35
+ | `items[i].binary` | **NOT supported** — leave a TODO and explain |
36
+ | `items.length` | `items.length` |
37
+ | `items.map(i => i.json.X)` | `items.map((i: any) => i.X)` |
38
38
 
39
39
  ### Envelope unwrapping — each-item mode
40
40
 
41
- | n8n | Pikku |
42
- |---|---|
43
- | `$json.X` / `$input.item.json.X` | `data.X` (input is the item itself) |
44
- | `$input.item.json` | `data` |
45
- | `$input.all()` | not available per-item — change to all-items mode |
41
+ | n8n | Pikku |
42
+ | -------------------------------- | ------------------------------------------------- |
43
+ | `$json.X` / `$input.item.json.X` | `data.X` (input is the item itself) |
44
+ | `$input.item.json` | `data` |
45
+ | `$input.all()` | not available per-item — change to all-items mode |
46
46
 
47
47
  ### Return statement
48
48
 
49
- | n8n | Pikku |
50
- |---|---|
51
- | `return [{ json: X }]` | `return { items: [X] }` |
52
- | `return items.map(i => ({ json: ... }))` | `return { items: items.map(...) }` |
53
- | `return [{ json: X }, { json: Y }]` | `return { items: [X, Y] }` |
54
- | `return { json: X }` (each-item) | `return X` |
55
- | `return [...]` (already plain) | wrap in `{ items: [...] }` only if the output schema expects it |
49
+ | n8n | Pikku |
50
+ | ---------------------------------------- | --------------------------------------------------------------- |
51
+ | `return [{ json: X }]` | `return { items: [X] }` |
52
+ | `return items.map(i => ({ json: ... }))` | `return { items: items.map(...) }` |
53
+ | `return [{ json: X }, { json: Y }]` | `return { items: [X, Y] }` |
54
+ | `return { json: X }` (each-item) | `return X` |
55
+ | `return [...]` (already plain) | wrap in `{ items: [...] }` only if the output schema expects it |
56
56
 
57
57
  ### Built-ins — do NOT auto-translate
58
58
 
@@ -80,6 +80,7 @@ the first param).
80
80
  ## Example
81
81
 
82
82
  Before (stub):
83
+
83
84
  ```ts
84
85
  /**
85
86
  * STUB — generated from n8n Code node "Custom Code".
@@ -90,12 +91,15 @@ export const codeStubCustomCode = pikkuSessionlessFunc({
90
91
  input: CodeStubCustomCodeInput,
91
92
  output: CodeStubCustomCodeOutput,
92
93
  func: async (_services, _data) => {
93
- throw new Error('Stub: ported from n8n Code node "Custom Code" — implement me')
94
+ throw new Error(
95
+ 'Stub: ported from n8n Code node "Custom Code" — implement me'
96
+ )
94
97
  },
95
98
  })
96
99
  ```
97
100
 
98
101
  After:
102
+
99
103
  ```ts
100
104
  export const codeStubCustomCode = pikkuSessionlessFunc({
101
105
  description: 'Ported from n8n Code node "Custom Code"',
@@ -32,11 +32,11 @@ predecessor and any `$('<loop node>')` become `$item`.
32
32
 
33
33
  ### Decide the shape first
34
34
 
35
- | Loop body does… | Emit |
36
- |---|---|
37
- | transform each item independently (enrich, format, call one thing) | `graph:map` — child = the body |
38
- | accumulate across items (running total, build one object/array) | a **reduce**: a single generated function over the whole array, not a map — `graph:map` collects per-item results and *loses* the accumulator |
39
- | pure side-effect per item, nothing downstream consumes results | `graph:map` with **no `next`** (done branch empty) — the safest, unambiguous case |
35
+ | Loop body does… | Emit |
36
+ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
37
+ | transform each item independently (enrich, format, call one thing) | `graph:map` — child = the body |
38
+ | accumulate across items (running total, build one object/array) | a **reduce**: a single generated function over the whole array, not a map — `graph:map` collects per-item results and _loses_ the accumulator |
39
+ | pure side-effect per item, nothing downstream consumes results | `graph:map` with **no `next`** (done branch empty) — the safest, unambiguous case |
40
40
 
41
41
  ### Child arity
42
42
 
@@ -49,8 +49,8 @@ predecessor and any `$('<loop node>')` become `$item`.
49
49
 
50
50
  ### Done-branch semantics (ask if it matters)
51
51
 
52
- n8n's done output is version-dependent: it may carry the *original* items or the
53
- *accumulated* results. `graph:map`'s `next` receives the array of child results.
52
+ n8n's done output is version-dependent: it may carry the _original_ items or the
53
+ _accumulated_ results. `graph:map`'s `next` receives the array of child results.
54
54
  If a downstream node reads that array's shape and the distinction matters, add:
55
55
 
56
56
  ```ts
@@ -18,7 +18,7 @@ Use this as an execution checklist, not reference material.
18
18
  ## The rules that don't change
19
19
 
20
20
  - **Never resolve an enum key dynamically.** No `mKey('status.' + value)`, no `m['enum__status__' + value]()`, no `mExists`/`mList` helpers. Dynamic keys can't be type-checked or tree-shaken. Everything is a static `m.<literal>()` reference, generated into the map.
21
- - **The `enum__<group>__<member>` namespace.** `__` separates the prefix / group / member segments; a single `_` joins words *within* a segment (`enum__booking_status__form_received`). The prefix (`enum`) and separator (`__`) are configurable but leave them at the defaults.
21
+ - **The `enum__<group>__<member>` namespace.** `__` separates the prefix / group / member segments; a single `_` joins words _within_ a segment (`enum__booking_status__form_received`). The prefix (`enum`) and separator (`__`) are configurable but leave them at the defaults.
22
22
  - **Members must be valid JS identifiers.** Spell out leading digits — `two_guests`, not `2_guests`. The generator quotes an invalid member as a fallback but warns you to rename it.
23
23
  - **`asI18n(...)` is only for opaque server data** (names, slugs, ids returned from the API). Never `asI18n()` a hardcoded English string or an enum value — an enum value goes through its label map.
24
24
 
@@ -35,8 +35,8 @@ export type I18nMessage = () => I18nString
35
35
  export type EnumLabel<E extends string> = Record<E, I18nMessage>
36
36
 
37
37
  export const bookingStatus = {
38
- enquiry: m.enum__booking_status__enquiry,
39
- reserved: m.enum__booking_status__reserved,
38
+ enquiry: m.enum__booking_status__enquiry,
39
+ reserved: m.enum__booking_status__reserved,
40
40
  confirmed: m.enum__booking_status__confirmed,
41
41
  } satisfies EnumLabel<BookingStatus>
42
42
  export type BookingStatusKey = keyof typeof bookingStatus
@@ -61,18 +61,20 @@ const items = [{ label: m.common__nav__items__dashboard /* ← reference */ }]
61
61
  The DB column is the real source of truth for what an enum can be. The pikku CLI's db codegen emits a bare unions module — `.pikku/db/enums.gen.ts` — covering **both** Postgres native enums and SQLite `CHECK (col IN ('a','b',…))` constraints:
62
62
 
63
63
  ```ts
64
- export type BookingStatus = 'enquiry' | 'reserved' | 'confirmed' | 'ended' | 'cancelled'
64
+ export type BookingStatus =
65
+ 'enquiry' | 'reserved' | 'confirmed' | 'ended' | 'cancelled'
65
66
  ```
66
67
 
67
68
  Point `@pikku/paraglide` at that file (`enumsFile`) and each catalog group whose member set **exactly matches** a DB enum is typed `satisfies EnumLabel<DbEnum>`. The label map then **is** the reconciliation — no separate assertion:
68
69
 
69
70
  - catalog drops a DB member, or `en.json` is missing the key → `m.enum__…` doesn't exist / `Record<DbEnum,…>` isn't exhaustive → **`tsc` error naming the gap**.
70
71
  - a DB enum with **no** catalog group → `unmatchedDbEnums: 'emit'` (default) generates a label map referencing `enum__<table>_<column>__<member>` keys, so `tsc` tells you exactly which keys to add; `'warn'` only reports it.
71
- - a group with a member the DB lacks (a *derived* UI state, e.g. a `waitlisted` view of a `pending` row) → a drift warning. Make that a **standalone `m.<key>()` message**, not an enum member — the enum group must mirror the DB column exactly.
72
+ - a group with a member the DB lacks (a _derived_ UI state, e.g. a `waitlisted` view of a `pending` row) → a drift warning. Make that a **standalone `m.<key>()` message**, not an enum member — the enum group must mirror the DB column exactly.
72
73
 
73
74
  Labelling an enum that's never rendered costs nothing: Paraglide compiles only the messages actually referenced, so unused labels are tree-shaken away. So label every DB enum; don't add an opt-out.
74
75
 
75
76
  **To make a column an enum**, give it a closed domain in the migration so codegen can see it:
77
+
76
78
  - SQLite: `status TEXT NOT NULL CHECK (status IN ('enquiry','reserved','confirmed'))`
77
79
  - Postgres: a native `CREATE TYPE … AS ENUM (…)` column.
78
80
 
@@ -88,7 +90,10 @@ import { paraglideEnums } from '@pikku/paraglide/vite'
88
90
 
89
91
  export default defineConfig({
90
92
  plugins: [
91
- paraglideVitePlugin({ project: './project.inlang', outdir: './src/paraglide' }),
93
+ paraglideVitePlugin({
94
+ project: './project.inlang',
95
+ outdir: './src/paraglide',
96
+ }),
92
97
  paraglideEnums({
93
98
  catalog: './messages/en.json',
94
99
  outFile: './src/i18n/i18n-enum.gen.ts',
@@ -26,14 +26,14 @@ export const deleteBook = pikkuFunc({
26
26
  await db.deleteBook(bookId)
27
27
  },
28
28
  permissions: {
29
- owner: isBookOwner, // ← authorization here
29
+ owner: isBookOwner, // ← authorization here
30
30
  },
31
31
  })
32
32
 
33
33
  // WRONG — permission check inside func body
34
34
  export const deleteBook = pikkuFunc({
35
35
  func: async ({ db }, { bookId }, { session }) => {
36
- if (!session) throw new UnauthorizedError() // ← never do this
36
+ if (!session) throw new UnauthorizedError() // ← never do this
37
37
  await db.deleteBook(bookId)
38
38
  },
39
39
  })
@@ -54,7 +54,7 @@ Use for checks that read the session but need no request data — and that asser
54
54
  something **beyond** merely having a session (a flag, a tier, a claim).
55
55
 
56
56
  ```typescript
57
- import { pikkuAuth } from '#pikku'
57
+ import { pikkuAuth } from '#pikku/function'
58
58
 
59
59
  // Good: a real gate on the session's contents, not just its existence.
60
60
  export const isVerified = pikkuAuth(
@@ -71,21 +71,21 @@ function already enforces. A function that needs a signed-in user sets
71
71
  ```typescript
72
72
  // WRONG — redundant with auth: true; adds a permission that gates nothing.
73
73
  export const isSignedIn = pikkuAuth(async (_s, session) => !!session)
74
- pikkuFunc({ auth: true, permissions: { signedIn: isSignedIn }, /* ... */ })
74
+ pikkuFunc({ auth: true, permissions: { signedIn: isSignedIn } /* ... */ })
75
75
 
76
76
  // RIGHT — auth: true already requires the session; permissions are for capability.
77
- pikkuFunc({ auth: true, /* ... */ })
77
+ pikkuFunc({ auth: true /* ... */ })
78
78
  ```
79
79
 
80
- A permission answers "*may this user do this?*" (role, ownership, tier) — never
81
- "*is there a session?*".
80
+ A permission answers "_may this user do this?_" (role, ownership, tier) — never
81
+ "_is there a session?_".
82
82
 
83
83
  ### `pikkuPermission(fn)` — Data-Aware Checks
84
84
 
85
85
  Use when authorization depends on the actual request data (e.g., resource ownership).
86
86
 
87
87
  ```typescript
88
- import { pikkuPermission } from '#pikku'
88
+ import { pikkuPermission } from '#pikku/function'
89
89
 
90
90
  export const isBookOwner = pikkuPermission(
91
91
  async ({ db }, { bookId }, { session }) => {
@@ -132,24 +132,24 @@ export const deleteBook = pikkuFunc({
132
132
 
133
133
  ### Global (`addGlobalPermission`) — App-Wide AND Gate
134
134
 
135
- A global permission is an app-wide baseline that **every** function must additionally pass. It is an independent AND gate: it can only ever *narrow* access — it never grants access a function's own `permissions` would deny.
135
+ A global permission is an app-wide baseline that **every** function must additionally pass. It is an independent AND gate: it can only ever _narrow_ access — it never grants access a function's own `permissions` would deny.
136
136
 
137
137
  ```typescript
138
- import { addGlobalPermission } from '#pikku'
138
+ import { addGlobalPermission } from '#pikku/function'
139
139
 
140
140
  addGlobalPermission([isEmployee]) // every function now also requires an employee session
141
141
  ```
142
142
 
143
143
  Multiple `addGlobalPermission` calls accumulate and are AND'd together.
144
144
 
145
- > Wire-, tag-, and HTTP-route-level permissions (`addHTTPPermission`, `addTagPermission`, and a `permissions` field on HTTP/channel/MCP wirings) were **removed in #972**. Permissions now live only on the function definition, plus the optional global gate. Tags are organizational only — use tag/HTTP *middleware* (`addTagMiddleware`, `addHTTPMiddleware`) for cross-cutting request handling, not authorization.
145
+ > Wire-, tag-, and HTTP-route-level permissions (`addHTTPPermission`, `addTagPermission`, and a `permissions` field on HTTP/channel/MCP wirings) were **removed in #972**. Permissions now live only on the function definition, plus the optional global gate. Tags are organizational only — use tag/HTTP _middleware_ (`addTagMiddleware`, `addHTTPMiddleware`) for cross-cutting request handling, not authorization.
146
146
 
147
147
  ## Scopes — the AND Gate Above Permissions
148
148
 
149
149
  Scopes answer "what was this session granted?" before permissions ask "may this
150
150
  user do this to this resource?". They are AND-ed: every scope listed must be
151
151
  held. Because they are checked first and fail closed, a scope can only ever
152
- *narrow* access — it never grants what `permissions` would deny.
152
+ _narrow_ access — it never grants what `permissions` would deny.
153
153
 
154
154
  Declare the scope tree once with `defineScope`. The body is a no-op that
155
155
  tree-shakes away; the CLI reads the call by AST and generates a `ScopeId` union,
@@ -158,7 +158,7 @@ gating on nothing.
158
158
 
159
159
  ```typescript
160
160
  // src/scopes.ts
161
- import { defineScope } from '#pikku'
161
+ import { defineScope } from '#pikku/scopes'
162
162
 
163
163
  defineScope({
164
164
  admin: {
@@ -222,7 +222,11 @@ export const handleStripeWebhook = pikkuSessionlessFunc({
222
222
  permissionsInBody: true,
223
223
  auth: false,
224
224
  func: async ({ stripe }, data, { http }) => {
225
- stripe.webhooks.constructEvent(data.raw, http.request.header('stripe-signature'), secret)
225
+ stripe.webhooks.constructEvent(
226
+ data.raw,
227
+ http.request.header('stripe-signature'),
228
+ secret
229
+ )
226
230
  // ...
227
231
  },
228
232
  })
@@ -240,7 +244,7 @@ declared, inspectable, and reusable.
240
244
 
241
245
  ```typescript
242
246
  // src/permissions.ts
243
- import { pikkuAuth, pikkuPermission } from '#pikku'
247
+ import { pikkuAuth, pikkuPermission } from '#pikku/function'
244
248
 
245
249
  export const isVerified = pikkuAuth(
246
250
  async (_services, session) => !!session?.emailVerified
@@ -11,8 +11,8 @@ Existing repo → pikku-software-archaeology → .knowledge/ blueprint → pikku
11
11
 
12
12
  ## The split from pikku-software-archaeology
13
13
 
14
- - **pikku-software-archaeology** extracts *facts* into `.knowledge/` for a machine (Pikku) to rebuild from. Engineer/generator audience.
15
- - **pikku-product-second-opinion** reads that blueprint and writes an *opinionated report* for a human to decide from. Non-technical audience.
14
+ - **pikku-software-archaeology** extracts _facts_ into `.knowledge/` for a machine (Pikku) to rebuild from. Engineer/generator audience.
15
+ - **pikku-product-second-opinion** reads that blueprint and writes an _opinionated report_ for a human to decide from. Non-technical audience.
16
16
 
17
17
  One extracts; one advises. This skill consumes the other's output — it doesn't re-read the code.
18
18
 
@@ -30,7 +30,7 @@ One extracts; one advises. This skill consumes the other's output — it doesn't
30
30
  4. **Argue improvements in business outcomes** (more reliable / faster / cheaper / safer / easier to hand off), and say whether each is a cheap **rewire** or an expensive **rebuild** — never recommend a rewrite just because the code is messy.
31
31
  5. **Mark confidence.** Certain and "I'd need to check" are different sentences.
32
32
  6. **Cover the frontend and the other ways the app is used** when the blueprint has them — walk the screens as a journey, call out consistency, and flag the custom-logic pieces (charts/tables/editors) as the real work vs the cheap standard pieces. Name the ways the product can be driven (people/web, developers/API+SDK, AI agents/MCP, power users/CLI) — often a genuine strength.
33
- 7. **Give honest technology tradeoffs — both sides.** Every stack bet (framework, auth, hosting, key libraries) gets what-it-buys AND what-it-costs in business terms, tied to the founder's stage/goals. Don't cheerlead, don't trash, and **don't soften the disadvantages**. The app's own bets are derived from the blueprint (`architecture.json`/`integrations.json`/`frontend.json` + the repo's manifest) — never from a list in the skill, because a verdict you could write before reading the blueprint isn't a second opinion. Separately, and only when a rebuild is actually being recommended, the target stack (Pikku, Better Auth, TanStack Start) gets the *same* both-sides treatment with cons first-class — pinning someone's dependency for being pre-1.0 while staying quiet about the replacement being pre-1.0 too is a pitch, not an opinion.
33
+ 7. **Give honest technology tradeoffs — both sides.** Every stack bet (framework, auth, hosting, key libraries) gets what-it-buys AND what-it-costs in business terms, tied to the founder's stage/goals. Don't cheerlead, don't trash, and **don't soften the disadvantages**. The app's own bets are derived from the blueprint (`architecture.json`/`integrations.json`/`frontend.json` + the repo's manifest) — never from a list in the skill, because a verdict you could write before reading the blueprint isn't a second opinion. Separately, and only when a rebuild is actually being recommended, the target stack (Pikku, Better Auth, TanStack Start) gets the _same_ both-sides treatment with cons first-class — pinning someone's dependency for being pre-1.0 while staying quiet about the replacement being pre-1.0 too is a pitch, not an opinion.
34
34
 
35
35
  ## Files
36
36