@pikku/skills 0.12.58 → 0.12.60

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 (73) hide show
  1. package/dist/skills.gen.js +2 -2
  2. package/package.json +10 -7
  3. package/skills/pikku-a11y/SKILL.md +5 -3
  4. package/skills/pikku-addon/SKILL.md +8 -9
  5. package/skills/pikku-addon/references/addon-package-manifest.md +53 -0
  6. package/skills/pikku-agent/SKILL.md +16 -13
  7. package/skills/pikku-agent/references/runner-vercel.md +1 -1
  8. package/skills/pikku-architect/SKILL.md +7 -10
  9. package/skills/pikku-auth/SKILL.md +16 -20
  10. package/skills/pikku-auth/references/better-auth.md +74 -37
  11. package/skills/pikku-auth/references/sessions.md +8 -0
  12. package/skills/pikku-build/SKILL.md +36 -68
  13. package/skills/pikku-build/references/app.md +27 -828
  14. package/skills/pikku-build/references/feature.md +3 -5
  15. package/skills/pikku-build/references/phase-1-what-it-is.md +167 -0
  16. package/skills/pikku-build/references/phase-2-people-and-apps.md +133 -0
  17. package/skills/pikku-build/references/phase-3-plan.md +62 -0
  18. package/skills/pikku-build/references/phase-4-build.md +253 -0
  19. package/skills/pikku-build/references/phase-5-prove-and-ship.md +148 -0
  20. package/skills/pikku-build/references/platform.md +5 -6
  21. package/skills/pikku-build/references/quick.md +2 -2
  22. package/skills/pikku-build/references/ship.md +11 -5
  23. package/skills/pikku-changes/SKILL.md +13 -7
  24. package/skills/pikku-concepts/SKILL.md +26 -258
  25. package/skills/pikku-concepts/references/cli.md +63 -0
  26. package/skills/pikku-concepts/references/functions.md +117 -0
  27. package/skills/pikku-concepts/references/language.md +5 -5
  28. package/skills/pikku-concepts/references/packages.md +6 -4
  29. package/skills/pikku-concepts/references/testing.md +14 -0
  30. package/skills/pikku-convert-screen/SKILL.md +7 -1
  31. package/skills/pikku-deploy/SKILL.md +29 -30
  32. package/skills/pikku-deploy/references/ws.md +1 -1
  33. package/skills/pikku-emails/SKILL.md +7 -3
  34. package/skills/pikku-guide/SKILL.md +7 -10
  35. package/skills/pikku-i18n/SKILL.md +18 -19
  36. package/skills/pikku-i18n/references/messages.md +6 -8
  37. package/skills/pikku-knowledge/SKILL.md +42 -45
  38. package/skills/pikku-kysely/SKILL.md +50 -13
  39. package/skills/pikku-list-query/SKILL.md +27 -14
  40. package/skills/pikku-meta/SKILL.md +13 -16
  41. package/skills/pikku-meta/references/audit.md +1 -45
  42. package/skills/pikku-meta/references/versioning.md +1 -1
  43. package/skills/pikku-middleware/SKILL.md +23 -13
  44. package/skills/pikku-middleware/references/middleware-patterns.md +2 -2
  45. package/skills/pikku-n8n-import/SKILL.md +7 -8
  46. package/skills/pikku-permissions/SKILL.md +14 -8
  47. package/skills/pikku-react/SKILL.md +7 -6
  48. package/skills/pikku-realtime/SKILL.md +16 -11
  49. package/skills/pikku-report/SKILL.md +8 -10
  50. package/skills/pikku-scenario/SKILL.md +8 -12
  51. package/skills/pikku-scenario/references/steps.md +3 -4
  52. package/skills/pikku-scenario/references/virtual-users.md +1 -1
  53. package/skills/pikku-seo/SKILL.md +16 -8
  54. package/skills/pikku-service-backends/SKILL.md +26 -28
  55. package/skills/pikku-services/SKILL.md +8 -9
  56. package/skills/pikku-services/references/services.md +1 -1
  57. package/skills/pikku-software-archaeology/README.md +12 -1
  58. package/skills/pikku-software-archaeology/SKILL.md +9 -2
  59. package/skills/pikku-software-archaeology/references/pikku-mapping.md +1 -1
  60. package/skills/pikku-software-archaeology/references/sources/express.md +75 -0
  61. package/skills/pikku-software-archaeology/references/sources/laravel.md +77 -0
  62. package/skills/pikku-software-archaeology/references/sources/nestjs.md +73 -0
  63. package/skills/pikku-software-archaeology/references/sources/rails.md +72 -0
  64. package/skills/pikku-software-archaeology/references/tests-to-scenarios.md +72 -0
  65. package/skills/pikku-tailwind/SKILL.md +15 -16
  66. package/skills/pikku-theme/SKILL.md +15 -17
  67. package/skills/pikku-ui/SKILL.md +4 -3
  68. package/skills/pikku-webhook/SKILL.md +10 -11
  69. package/skills/pikku-wiring/SKILL.md +45 -47
  70. package/skills/pikku-wiring/references/channel.md +1 -1
  71. package/skills/pikku-wiring/references/scheduler.md +1 -1
  72. package/skills/pikku-wiring/references/trigger.md +24 -1
  73. package/skills/pikku-workflow/SKILL.md +7 -8
@@ -1,9 +1,13 @@
1
1
  ---
2
2
  name: pikku-permissions
3
3
  description: >-
4
- Use when deciding WHO may call a function: resource ownership, role gates, admin-only actions, or any "only their own rows" rule. Covers the `permissions` field, `pikkuPermission`, `pikkuAuth`, scopes and `defineScope`, `addGlobalPermission`, the three gates, `permissionsInBody`, and where ownership belongs versus where it does not.
5
- TRIGGER when: writing or reviewing any function that touches a row a user owns, gating an action on a role, building the permissions half of a contract in build PHASE 2, about to write an `if` in a function body that decides whether the caller is allowed, or hitting `MissingScopeError`.
6
- DO NOT TRIGGER when: the question is how a caller proves who they are, such as sign-in, sessions, tokens, API keys or seeding a virtual user (use pikku-auth), or how to shape a paginated list (use pikku-list-query).
4
+ Deciding WHO may call a function: resource ownership, role gates, admin-only actions, "only their
5
+ own rows" rules; the `permissions` field, pikkuPermission, pikkuAuth, scopes/defineScope,
6
+ addGlobalPermission, permissionsInBody. TRIGGER when: writing or reviewing a function that touches
7
+ a user-owned row, gating on a role, building the permissions half of a contract in build PHASE 2,
8
+ about to write an `if` in a body deciding whether the caller is allowed, or hitting
9
+ MissingScopeError. DO NOT TRIGGER for how a caller proves identity: sign-in, sessions, tokens, API
10
+ keys, seeding a virtual user (use pikku-auth), or paginated lists (use pikku-list-query).
7
11
  installGroups: [core]
8
12
  ---
9
13
 
@@ -12,7 +16,7 @@ installGroups: [core]
12
16
  ## First: is the caller a machine with a token?
13
17
 
14
18
  Then this is NOT a permissions problem. Resolve the token in `addHTTPMiddleware('*')`
15
- middleware that calls `setSession`, make the function a `pikkuFunc`, and gate it with
19
+ middleware that calls `setInitialSession`, make the function a `pikkuFunc`, and gate it with
16
20
  `scopes` (pikku-auth, `../pikku-auth/references/machine-auth.md`). A `permissions` check that verifies a
17
21
  bearer token and returns `true` is authentication wearing an authorization hat, and it
18
22
  leaves the function sessionless. The only exception is a bootstrap endpoint whose caller
@@ -54,8 +58,8 @@ user's row by passing its id. Every function that takes a row id needs BOTH: `au
54
58
  for the session, and a `permissions` entry for the ownership.
55
59
 
56
60
  Equally: do NOT write an `isSignedIn` permission that returns `!!session`. That re-checks
57
- authentication, which `auth: true` already did. A permission answers *may this user do
58
- this* — role, ownership, tier — never *is there a session*.
61
+ authentication, which `auth: true` already did. A permission answers _may this user do
62
+ this_ — role, ownership, tier — never _is there a session_.
59
63
 
60
64
  ## Single row vs list — where ownership actually goes
61
65
 
@@ -88,11 +92,13 @@ export const isBookOwner = pikkuPermission(
88
92
  .where('bookId', '=', bookId)
89
93
  .executeTakeFirst()
90
94
  return book?.ownerId === session?.userId
91
- },
95
+ }
92
96
  )
93
97
 
94
98
  // Session-only: no input needed. Use for role and flag gates.
95
- export const isAdmin = pikkuAuth(async (_services, session) => session?.role === 'admin')
99
+ export const isAdmin = pikkuAuth(
100
+ async (_services, session) => session?.role === 'admin'
101
+ )
96
102
  ```
97
103
 
98
104
  ## OR and AND
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  name: pikku-react
3
3
  description: >-
4
- Use when a React frontend talks to a Pikku backend — PikkuProvider and createPikku at the app
5
- root, the generated React Query hooks (usePikkuQuery, usePikkuMutation, usePikkuInfiniteQuery),
6
- direct usePikkuRPC / usePikkuFetch calls, realtime subscriptions, and agent and workflow hooks. TRIGGER when: writing a React component that fetches or mutates backend
7
- data, wiring PikkuProvider, paginating, running or tracking a workflow from the client. DO NOT TRIGGER when: working on the
8
- backend (use pikku-wiring), defining the workflow itself (use pikku-workflow), rendering fetched values such as
9
- dates (use pikku-tailwind), or writing user-facing copy (use pikku-i18n).
4
+ React frontend talking to a Pikku backend: PikkuProvider/createPikku, generated React Query hooks
5
+ (usePikkuQuery, usePikkuMutation, usePikkuInfiniteQuery), usePikkuRPC/usePikkuFetch, realtime
6
+ subscriptions, agent and workflow hooks. TRIGGER when: writing a React component that fetches or
7
+ mutates backend data, wiring PikkuProvider, paginating, or running/tracking a workflow from the
8
+ client. DO NOT TRIGGER for backend work (use pikku-wiring), defining the workflow (use
9
+ pikku-workflow), rendering fetched values such as dates (use pikku-tailwind) or user-facing copy
10
+ (use pikku-i18n).
10
11
  installGroups: [client]
11
12
  ---
12
13
 
@@ -1,15 +1,13 @@
1
1
  ---
2
2
  name: pikku-realtime
3
3
  description: >-
4
- Use when making a view live in a Pikku app — a board, shared list, dashboard, ticker, bidding
5
- room, live count — or when adding two-way chat or presence. Covers the default event-hub SSE path
6
- (`eventHub.publish` on the backend, `PikkuRealtime.subscribeToTopic` on the client) and the
7
- two-way WebSocket channel. TRIGGER when: the user wants live updates, realtime, "update without
8
- refresh", a live board/feed/ticker/room, presence or chat, or data that more than one signed-in
9
- user can change should reflect to others. DO NOT TRIGGER when: a one-shot query/refetch is enough
10
- (data only one user changes, or a manual refresh is acceptable), for background jobs (use
11
- pikku-workflow, or pikku-wiring's queue and scheduler references), or for the full wiring
12
- reference of channels, SSE routes and the `/events` scaffold (use pikku-wiring).
4
+ Making a view live in a Pikku app (board, shared list, dashboard, ticker, bidding room, live
5
+ count) or adding two-way chat/presence: the default event-hub SSE path (`eventHub.publish`,
6
+ `PikkuRealtime.subscribeToTopic`) and the WebSocket channel. TRIGGER when: asked for live updates,
7
+ realtime, "update without refresh", presence or chat, or data several signed-in users can change
8
+ should reflect to others. DO NOT TRIGGER when a one-shot refetch is enough, for background jobs
9
+ (use pikku-workflow, or pikku-wiring's queue/scheduler references), or for the full
10
+ channel/SSE/`/events` wiring reference (use pikku-wiring).
13
11
  installGroups: [core, client]
14
12
  ---
15
13
 
@@ -115,8 +113,15 @@ import { pikkuChannelFunc, wireChannel } from '#pikku/channel'
115
113
 
116
114
  export const onMessage = pikkuChannelFunc<{ text: string }>({
117
115
  func: async ({ eventHub }, input, { channel, session }) => {
118
- const message = { id: crypto.randomUUID(), text: input.text, userId: session!.userId }
119
- await eventHub.publish('room', channel.channelId, { topic: 'room', data: message })
116
+ const message = {
117
+ id: crypto.randomUUID(),
118
+ text: input.text,
119
+ userId: session!.userId,
120
+ }
121
+ await eventHub.publish('room', channel.channelId, {
122
+ topic: 'room',
123
+ data: message,
124
+ })
120
125
  return message
121
126
  },
122
127
  })
@@ -1,15 +1,13 @@
1
1
  ---
2
2
  name: pikku-report
3
3
  description: >-
4
- Use when pikku itself cost you time — wrong generated types, a check that passes when it should
5
- not, output that is quietly wrong, a skill that misled you — or when the user asks you to report
6
- a framework bug or file a finding. Owns `pikku cloud report` (a finding is about pikku, not the
7
- app), the product-vs-harness kinds, the workaround-first ladder, and asking the user at hand-over
8
- whether to send what was filed. TRIGGER when: the framework fought you, codegen produced
9
- something broken, a skill told you to run something that does not exist, the user says "report
10
- this to pikku" / "file a finding", or you are handing over a build. DO NOT TRIGGER when: the bug
11
- is in the app you are building (fix it there), you are debugging a deployed stage,
12
- or you are tempted to patch pikku's source (never do that from an app).
4
+ Reporting Pikku itself when it cost you time (wrong generated types, a check that passes when it
5
+ should not, quietly wrong output, a misleading skill) or when asked to file a framework bug:
6
+ `pikku cloud report`, product-vs-harness kinds, the workaround-first ladder, asking the user at
7
+ hand-over whether to send findings. TRIGGER when: the framework fought you, codegen is broken, a
8
+ skill told you to run something that does not exist, the user says "report this to pikku" / "file
9
+ a finding", or you are handing over a build. DO NOT TRIGGER for bugs in the app you are building,
10
+ debugging a deployed stage, or patching pikku's source (never from an app).
13
11
  installGroups: [core]
14
12
  ---
15
13
 
@@ -62,7 +60,7 @@ baseline noise that was already failing before you started.
62
60
  - `--kind harness` — a skill misled you: it told you to run something that does
63
61
  not exist, described a flag that is spelled differently, or contradicted what
64
62
  the CLI actually did. Pass `--skill <name>` and `--passage "<the line or
65
- section>"`. This is the most useful kind to file, because it is fixable
63
+ section>"`. This is the most useful kind to file, because it is fixable
66
64
  immediately — so file it even when the cost was small.
67
65
 
68
66
  ## The command
@@ -1,18 +1,14 @@
1
1
  ---
2
2
  name: pikku-scenario
3
3
  description: >-
4
- Use when writing or running Pikku scenarios, running a virtual user against a stage, or when
5
- asked to test Pikku functions or improve coverage. A scenario (pikkuScenario) drives the app
6
- the way users do — steps run as virtual users over the real transport against a running server — so a
7
- flow doubles as an e2e test and a staged/production health check. Covers scenario.do /
8
- expectEventually / expectError / expectService / expectScore, declared steps via
9
- pikkuScenarioStep (browser steps driven by @pikku/playwright) written as intent rather than
10
- clicks, virtual users / virtual users / environments in pikku.config.json, VIRTUAL_USER_SIGNING_KEY, the
11
- `pikku scenario` and `pikku virtual-user` commands, and live coverage
12
- via `pikku dev --coverage`. TRIGGER when: user asks about scenarios, testing a Pikku function,
13
- coverage, e2e flows, browser/UI e2e, health checks, virtual users, or adversarial
14
- runs against a stage. DO NOT TRIGGER when: user asks about running an existing suite (use
15
- Bash) or CI config.
4
+ Writing and running Pikku scenarios (pikkuScenario): virtual users drive the app over the real
5
+ transport, so a flow doubles as an e2e test and a stage/production health check. Covers
6
+ scenario.do/expectEventually/expectError/expectService/expectScore, pikkuScenarioStep (Playwright
7
+ browser steps), virtual users and environments in pikku.config.json, VIRTUAL_USER_SIGNING_KEY,
8
+ `pikku scenario`/`pikku virtual-user`, coverage via `pikku dev --coverage`. TRIGGER when: asked
9
+ about scenarios, testing a Pikku function, coverage, e2e or browser flows, health checks, virtual
10
+ users or adversarial stage runs. DO NOT TRIGGER for running an existing suite (use Bash) or CI
11
+ config.
16
12
  installGroups: [core]
17
13
  ---
18
14
 
@@ -358,10 +358,9 @@ is the app's language, not the team's. Ask, or leave it `en`.
358
358
 
359
359
  **Where a non-`en` `metaLocale` still shows English, today.** The reporter composes a
360
360
  sentence as `<Keyword> <virtual user> <template>` (`composeStepProse`), and the keyword is
361
- an English literal. The Console translates the Given/When/Then keywords into its own
362
- UI language; the CLI reporter does not, so `metaLocale: "de"` gives you German step
361
+ an English literal. The CLI reporter does not translate the Given/When/Then keywords, so `metaLocale: "de"` gives you German step
363
362
  prose inside an English frame — `Given shopper kauft 1 Äpfel`. Write templates that read
364
363
  acceptably in that frame rather than trying to defeat it. A second gap: where a
365
- function or scenario declares no `title`, the Console falls back to splitting the
366
- **identifier** into an English-looking label (`toEnglishName`), so under a
364
+ function or scenario declares no `title`, the label falls back to splitting the
365
+ **identifier** into English words (`toEnglishName`), so under a
367
366
  non-`en` `metaLocale` meta is worth authoring rather than leaving to the fallback.
@@ -47,7 +47,7 @@ harmless but worth knowing before you align it to the surrounding code.
47
47
  One more thing worth knowing before writing a rich virtual user: **`virtualUser.converse`
48
48
  builds its prompt from `name`, `jobTitle`, `personality` and the scenario's
49
49
  `task` only.** Fields like `disposition`, `goals` and `roles` are read and
50
- stored, and the console shows them, but they do not reach the conversing
50
+ stored, but they do not reach the conversing
51
51
  virtual user's instructions. Anything that must shape how someone talks belongs in
52
52
  `personality` or in the task.
53
53
 
@@ -1,9 +1,12 @@
1
1
  ---
2
2
  name: pikku-seo
3
3
  description: >-
4
- On-page SEO rules for the app's PUBLIC pages: per-route head() titles and meta descriptions, Open Graph tags, crawlable links and URLs, JSON-LD on the landing page, and noindex for the logged-in area.
5
- TRIGGER when: building or reworking any public page (landing, pricing, about, blog/content pages), writing page titles or meta tags, or the user asks about SEO / Google / discoverability / social sharing previews.
6
- DO NOT TRIGGER when: the task is accessibility, including heading order, landmarks or alt text (use pikku-a11y), or working on logged-in /app screens (they are noindexed — only the one robots rule below applies), backend functions, database, or deployment.
4
+ On-page SEO for the app's PUBLIC pages: per-route head() titles and meta descriptions, Open Graph,
5
+ crawlable links/URLs, landing-page JSON-LD, noindex for the logged-in area. TRIGGER when: building
6
+ or reworking a public page (landing, pricing, about, blog), writing page titles or meta tags, or
7
+ asked about SEO, Google, discoverability or social share previews. DO NOT TRIGGER for
8
+ accessibility incl. heading order, landmarks, alt text (use pikku-a11y), logged-in /app screens
9
+ (noindexed; only the one robots rule applies), backend, database or deployment.
7
10
  installGroups: [client]
8
11
  ---
9
12
 
@@ -31,7 +34,10 @@ export const Route = createFileRoute('/pricing')({
31
34
  'Simple per-seat pricing for Acme Scheduling. Start free, upgrade when your team grows — no setup fees, cancel anytime.',
32
35
  },
33
36
  { property: 'og:title', content: 'Pricing — Acme Scheduling' },
34
- { property: 'og:description', content: 'Simple per-seat pricing. Start free.' },
37
+ {
38
+ property: 'og:description',
39
+ content: 'Simple per-seat pricing. Start free.',
40
+ },
35
41
  ],
36
42
  }),
37
43
  component: PricingPage,
@@ -90,16 +96,18 @@ or offers (fake schema is a Google penalty, not a boost):
90
96
 
91
97
  ```tsx
92
98
  head: () => ({
93
- meta: [
94
- /* title + description as above */
95
- ],
99
+ meta: [/* title + description as above */],
96
100
  scripts: [
97
101
  {
98
102
  type: 'application/ld+json',
99
103
  children: JSON.stringify({
100
104
  '@context': 'https://schema.org',
101
105
  '@graph': [
102
- { '@type': 'Organization', name: 'Acme Scheduling', description: '…' },
106
+ {
107
+ '@type': 'Organization',
108
+ name: 'Acme Scheduling',
109
+ description: '…',
110
+ },
103
111
  { '@type': 'WebSite', name: 'Acme Scheduling' },
104
112
  ],
105
113
  }),
@@ -1,16 +1,14 @@
1
1
  ---
2
2
  name: pikku-service-backends
3
3
  description: >-
4
- Use when picking or wiring a backend for one of Pikku's core service interfaces — ContentService
5
- (S3, Backblaze B2), QueueService (SQS), SecretService (AWS Secrets Manager, Redis, MongoDB),
6
- SchemaService (AJV, cfworker), ChannelStore, EventHubStore, WorkflowService, SessionStore or
7
- AgentRunService (Redis, MongoDB). Covers which backend to choose, what each one silently does
8
- differently, and the failures they swallow. TRIGGER when: code uses S3Content, B2Content,
9
- SQSQueueService, AWSSecrets, RedisChannelStore, RedisSecretService, MongoDBChannelStore,
10
- PikkuMongoDB, AjvSchemaService or CFWorkerSchemaService, or the user asks how to store files,
11
- secrets, channel state or sessions. DO NOT TRIGGER when: defining service factories themselves
12
- (use pikku-services), SQL via Kysely (use pikku-kysely), or the Lambda/Cloudflare runtimes
13
- themselves (use pikku-deploy).
4
+ Picking or wiring a backend for Pikku's core service interfaces: ContentService (S3, B2),
5
+ QueueService (SQS), SecretService (AWS Secrets Manager, Redis, MongoDB), SchemaService (AJV,
6
+ cfworker), ChannelStore, EventHubStore, WorkflowService, SessionStore, AgentRunService (Redis,
7
+ MongoDB). Covers which to choose, silent differences, swallowed failures. TRIGGER when: code uses
8
+ S3Content, B2Content, SQSQueueService, AWSSecrets, Redis*/MongoDB* stores, PikkuMongoDB,
9
+ AjvSchemaService or CFWorkerSchemaService, or asked how to store files, secrets, channel state or
10
+ sessions. DO NOT TRIGGER for defining service factories (use pikku-services), Kysely SQL (use
11
+ pikku-kysely) or the Lambda/Cloudflare runtimes (use pikku-deploy).
14
12
  installGroups: [core]
15
13
  ---
16
14
 
@@ -26,17 +24,17 @@ behind the interface.
26
24
 
27
25
  ## Pick a backend
28
26
 
29
- | Interface | Backends | Package |
30
- | --- | --- | --- |
31
- | `ContentService` | `S3Content`, `B2Content` | `@pikku/aws-services`, `@pikku/backblaze` |
32
- | `QueueService` | `SQSQueueService` | `@pikku/aws-services` |
33
- | `SecretService` | `AWSSecrets`, `RedisSecretService`, `MongoDBSecretService` | `@pikku/aws-services`, `@pikku/redis`, `@pikku/mongodb` |
34
- | `SchemaService` | `AjvSchemaService`, `CFWorkerSchemaService` | `@pikku/schema-ajv`, `@pikku/schema-cfworker` |
35
- | `ChannelStore`, `EventHubStore` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
36
- | `PikkuWorkflowService`, `WorkflowRunService` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
37
- | `SessionStore`, `AgentRunService`, `DeploymentService` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
38
- | `AgentStorageService`, `AgentRunStateService` | MongoDB **only** | `@pikku/mongodb` |
39
- | `LeaseService` | `RedisLeaseService` | `@pikku/redis` |
27
+ | Interface | Backends | Package |
28
+ | ------------------------------------------------------ | ---------------------------------------------------------- | ------------------------------------------------------- |
29
+ | `ContentService` | `S3Content`, `B2Content` | `@pikku/aws-services`, `@pikku/backblaze` |
30
+ | `QueueService` | `SQSQueueService` | `@pikku/aws-services` |
31
+ | `SecretService` | `AWSSecrets`, `RedisSecretService`, `MongoDBSecretService` | `@pikku/aws-services`, `@pikku/redis`, `@pikku/mongodb` |
32
+ | `SchemaService` | `AjvSchemaService`, `CFWorkerSchemaService` | `@pikku/schema-ajv`, `@pikku/schema-cfworker` |
33
+ | `ChannelStore`, `EventHubStore` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
34
+ | `PikkuWorkflowService`, `WorkflowRunService` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
35
+ | `SessionStore`, `AgentRunService`, `DeploymentService` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
36
+ | `AgentStorageService`, `AgentRunStateService` | MongoDB **only** | `@pikku/mongodb` |
37
+ | `LeaseService` | `RedisLeaseService` | `@pikku/redis` |
40
38
 
41
39
  SQL is the third option for every store interface in that table —
42
40
  `KyselyChannelStore`, `KyselyWorkflowService`, `KyselySecretService` and friends
@@ -79,12 +77,12 @@ wrapper has to be constructed and initialised before any of them.
79
77
 
80
78
  Where they diverge:
81
79
 
82
- | | `S3Content` | `B2Content` |
83
- | --- | --- | --- |
84
- | Signing failure | **Fails open** — logs and returns the *unsigned* URL | Throws |
85
- | `writeFile` memory | Streams | **Buffers the whole stream** to compute a SHA-1 |
86
- | Client-side upload integrity | Presigned, expires at a fixed 3600s | `X-Bz-Content-Sha1: do_not_verify` — unverified |
87
- | Credential rotation | Picked up by the SDK provider chain | Auth is cached for the instance's lifetime — construct a new `B2Content` |
80
+ | | `S3Content` | `B2Content` |
81
+ | ---------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------ |
82
+ | Signing failure | **Fails open** — logs and returns the _unsigned_ URL | Throws |
83
+ | `writeFile` memory | Streams | **Buffers the whole stream** to compute a SHA-1 |
84
+ | Client-side upload integrity | Presigned, expires at a fixed 3600s | `X-Bz-Content-Sha1: do_not_verify` — unverified |
85
+ | Credential rotation | Picked up by the SDK provider chain | Auth is cached for the instance's lifetime — construct a new `B2Content` |
88
86
 
89
87
  The S3 fail-open is the one to design around: on a private CloudFront
90
88
  distribution the client gets a 403, and on a public one you have just handed out
@@ -119,7 +117,7 @@ Swapping them changes behaviour without changing types:
119
117
  becomes `1` in the wiring layer, not here.
120
118
 
121
119
  Both throw `UnprocessableContentError` (422) on a failed validation, and both
122
- throw a **bare string** — `Missing validator for <name>` — for a *missing*
120
+ throw a **bare string** — `Missing validator for <name>` — for a _missing_
123
121
  schema. It is not an `Error`, so `catch (e) { e.message }` reads `undefined`.
124
122
  That almost always means codegen did not run.
125
123
 
@@ -1,15 +1,14 @@
1
1
  ---
2
2
  name: pikku-services
3
3
  description: >-
4
- Use for the service layer of a Pikku app — dependency injection with pikkuServices and
5
- pikkuWireServices, startup/shutdown work with pikkuServerLifecycle, configuration through the
6
- secrets, variables and credentials services, the audit sink and buffer, and structured logging.
7
- TRIGGER when: code uses pikkuServices/pikkuWireServices/pikkuServerLifecycle, defineSecret,
8
- defineVariable or defineCredential, user asks about services.ts, lifecycle.ts, dependency
9
- injection, env vars, secrets, API credentials, audit logging, AuditService, PinoLogger, or a
10
- built-in service. DO NOT TRIGGER when: user asks about middleware (use pikku-middleware),
11
- authentication or permissions (use pikku-auth), or a third-party backend such as Redis, S3 or
12
- MongoDB (use pikku-service-backends).
4
+ The Pikku service layer: dependency injection with pikkuServices/pikkuWireServices,
5
+ startup/shutdown via pikkuServerLifecycle, config through the secrets, variables and credentials
6
+ services, audit sink and buffer, structured logging. TRIGGER when: code uses
7
+ pikkuServices/pikkuWireServices/pikkuServerLifecycle/defineSecret/defineVariable/defineCredential,
8
+ or asked about services.ts, lifecycle.ts, DI, env vars, secrets, API credentials, audit logging,
9
+ PinoLogger or a built-in service. DO NOT TRIGGER for middleware (use pikku-middleware),
10
+ authentication or permissions (use pikku-auth), or third-party backends such as Redis, S3, MongoDB
11
+ (use pikku-service-backends).
13
12
  installGroups: [core]
14
13
  ---
15
14
 
@@ -63,7 +63,7 @@ A service factory should **construct** services, not run startup side effects. S
63
63
 
64
64
  ```typescript
65
65
  // src/lifecycle.ts
66
- import { pikkuServerLifecycle } from '@pikku/core'
66
+ import { pikkuServerLifecycle } from '@pikku/core/lifecycle'
67
67
  import type { SingletonServices } from '../types/application-types.js'
68
68
 
69
69
  export const lifecycle = pikkuServerLifecycle<SingletonServices>({
@@ -38,7 +38,7 @@ Concept names are the stable IDs. On re-run after code changes, re-extract only
38
38
 
39
39
  ## How Pikku consumes the blueprint
40
40
 
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
+ 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 → `pikkuScenario` / `pikkuFeature` (see `references/tests-to-scenarios.md`). Humans resolve `migration.json.decisionsNeeded` before any generation starts.
42
42
 
43
43
  ## How uncertainty is represented
44
44
 
@@ -75,6 +75,8 @@ pikku-software-archaeology/
75
75
  ├── references/
76
76
  │ ├── blueprint.schema.json # the output contract (JSON Schema)
77
77
  │ ├── pikku-mapping.md # blueprint → Pikku primitives
78
+ │ ├── tests-to-scenarios.md # legacy test suite → blueprint scenarios → pikkuScenario
79
+ │ ├── sources/ # per-framework guides: rails, laravel, nestjs, express
78
80
  │ ├── second-opinion.md # blueprint → owner-facing report: method, voice, red flags
79
81
  │ └── second-opinion-report-template.md # the layered structure to fill in
80
82
  ├── example/
@@ -82,3 +84,12 @@ pikku-software-archaeology/
82
84
  └── scripts/
83
85
  └── validate.mjs # schema + cross-file validation (node, no deps)
84
86
  ```
87
+
88
+ ## Backlog: real-app test runs
89
+
90
+ The framework guides in `references/sources/` are written from general knowledge of each framework and have not been run against a real codebase yet. Run the skill on these, scoped to one slice each (not the whole repo), clone into a scratch directory rather than a pikku repo, and fix whatever the guides miss:
91
+
92
+ - **Redmine** (Rails, issue tracker, ~2006): the hardest test of `sources/rails.md` — callbacks, workflow states, plugins. Scope to issues + workflow transitions.
93
+ - **Immich** (NestJS, photo backend): exercises `sources/nestjs.md` — guards, cron/queues, event handlers. Scope to one domain (e.g. albums or sharing).
94
+
95
+ Things to check on each run: implicit events found, validator passes, which grep patterns in the guide come up empty, and how `tests-to-scenarios.md` handles the repo's own suite.
@@ -75,7 +75,14 @@ Build an inventory before interpreting anything:
75
75
  - TODO / FIXME / HACK / XXX / deprecated markers — each is a gaps.json candidate.
76
76
  - **The test suite** — locate it now, excavate it in Phase 2.
77
77
 
78
- Where intent hides, per ecosystem (read these first):
78
+ **Framework guides.** Once you know the framework, read its guide before extracting. Each lists where intent hides, how its idioms map to blueprint concepts, the traps, and a grep checklist. Read only the ones that apply:
79
+
80
+ - Rails: `references/sources/rails.md`
81
+ - Laravel: `references/sources/laravel.md`
82
+ - NestJS: `references/sources/nestjs.md`
83
+ - Express (also the approach for Koa/Fastify/Hono): `references/sources/express.md`
84
+
85
+ Other ecosystems fall back to this table. Where intent hides, per ecosystem (read these first):
79
86
 
80
87
  | Ecosystem | Highest-yield locations |
81
88
  | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -91,7 +98,7 @@ Where intent hides, per ecosystem (read these first):
91
98
  Tests are the closest thing to an executable product spec. For every test file:
92
99
 
93
100
  - `describe`/`context`/`it` names → **scenarios** (attach to the matching workflow in `workflows.json` under `scenarios[]`, with `fromTest` set).
94
- - User-flow/journey harnesses (`pikkuUserFlow` stories, `.feature` files, Playwright journeys) are the highest-grade scenario source — they already ARE given/when/outcome sequences; extract them verbatim.
101
+ - Journey harnesses (`pikkuScenario` flows, `.feature` files, Playwright/Capybara journeys, request/feature/e2e specs) are the highest-grade scenario source — they already ARE given/when/outcome sequences; extract them verbatim. Triage every test by kind first: see `references/tests-to-scenarios.md`.
95
102
  - Assertions → confirmations of policies and invariants (upgrade their `confidence` to `high`, add the test as evidence).
96
103
  - Fixtures/factories → entity attribute shapes and realistic example data.
97
104
  - Edge-case tests → business rules that exist **nowhere else in the code** (e.g. "replayed webhook events are idempotent" may only be stated in a test).
@@ -14,7 +14,7 @@ The `.knowledge/` blueprint is designed so each concept maps onto exactly one Pi
14
14
  | `workflows.json` kind=user | frontend flows + the commands they chain |
15
15
  | `workflows.json` kind=system, with `schedule` | `wireScheduler` entries |
16
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 |
17
+ | `workflows.json` `scenarios[]` | **`pikkuScenario` + `pikkuFeature` — this is the canonical target** (`pikkuUserFlow` was renamed to scenario in #862). Each blueprint scenario's given/when/outcome maps onto `scenario.given/when/then` steps; group by workflow into one scenario per journey and by domain into a `pikkuFeature`. Pure system workflows (cron sweeps, webhook ingest) are still scenarios: fire them with `scenario.runScheduledTask` or the webhook route, then `expectEventually`. See `tests-to-scenarios.md` |
18
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
19
  | `api.json` kind=webhook-in | `wireHTTP` with `auth: false` + signature-verification middleware from the integration |
20
20
  | `integrations.json` | services in `services.ts` (constructor-injected classes); `configVia` env vars become `defineSecret` / config; per-user credentials become `defineCredential` |
@@ -0,0 +1,75 @@
1
+ # Source guide: Express (Node.js)
2
+
3
+ Read this when the survey finds `express` in `package.json`. Express has no conventions, so every Express app is its own framework. The same product can be one 3,000-line `server.js` or a layered app that imitates Nest. Your job is to find which, then trace the request path by hand. The same approach works for Koa, Fastify and Hono; adjust the registration syntax.
4
+
5
+ ## Detect
6
+
7
+ `express` in dependencies, `app.listen`, `express.Router()`. Note the module system (CJS vs ESM), TypeScript or not, the ORM or query layer (Prisma, Sequelize, Knex, Mongoose, raw `pg`/`mysql2`), the session/auth approach (Passport, JWT, express-session) and the entry point named in `package.json` `main`/`scripts.start`.
8
+
9
+ ## Where intent hides
10
+
11
+ Start at the entry file and follow `app.use`. **Order is behavior**: middleware registered before a route applies to it, middleware registered after does not.
12
+
13
+ | Location | Blueprint concept |
14
+ | ------------------------------------------------------------------------- | ----------------------------------------------------------------- |
15
+ | entry file (`app.js`, `server.js`, `index.js`, `src/app.ts`): every `app.use` in order | global middleware chain, raw-body routes, CORS, mounts, architecture constraints |
16
+ | `routes/`, `routers/`, `api/`: `router.get/post/...`, `app.route()` | api surfaces; track the mount prefix (`app.use('/v1/invoices', r)`) |
17
+ | auth middleware (`requireAuth`, `isAdmin`, `passport.authenticate`, `jwt.verify`) | authorization policies; note which routes use it and which do not |
18
+ | handlers/controllers: inline `if (...) return res.status(4xx)` | preconditions and policies (every guard is one) |
19
+ | validation: `joi`, `zod`, `yup`, `express-validator`, `celebrate`, hand-rolled `if (!req.body.x)` | command `input[]` + validation policies |
20
+ | models: Mongoose schemas, Sequelize models, Prisma schema, SQL files, migrations (`knex`, `umzug`), or raw SQL strings | entities, constraints |
21
+ | `services/`, `lib/`, `utils/`, `helpers/` | business logic; the real commands usually live here |
22
+ | `jobs/`, `workers/`, `cron/`, `node-cron`, `agenda`, `bull`/`bullmq`, `setInterval` at module scope | system workflows |
23
+ | webhook routes (`/webhooks/stripe`, signature checks, `express.raw`) | integration ingress; command + signature policy |
24
+ | `socket.io` / `ws` setup | realtime interface |
25
+ | `process.env.*`, `config/`, `dotenv` | integrations, toggles, deployment constraints |
26
+ | `test/`, `__tests__/`, `*.test.js` (supertest/jest/mocha) | scenarios; edge cases not in the code |
27
+
28
+ ## Idiom -> blueprint -> Pikku
29
+
30
+ | Express idiom | Blueprint concept | Pikku shortcut |
31
+ | ----------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------ |
32
+ | `router.post('/:id/send', auth, handler)` | command (`SendInvoice`), not "POST /:id/send" | `pikkuFunc` + `wireHTTP` keeping path for compatibility |
33
+ | `router.use(requireAuth)` before a set of routes | authorization policy `enforcedAt` each route in scope | auth by default; list public routes explicitly |
34
+ | `if (resource.userId !== req.user.id) return 403` | ownership policy, repeated per handler | one named permission function, wired everywhere |
35
+ | `req.body` hand-checks / Joi schema | `input[]` + validation policy | Zod schema |
36
+ | `res.status(409)` on duplicate / state conflict | precondition on entity state | guard clause inside the func |
37
+ | `await sendEmail(...)` at the end of a handler | **implicit event consumer** | emit event, consumer in `wireQueueWorker` |
38
+ | `setTimeout`/`setInterval`/`node-cron` in module scope | system workflow (often undocumented) | `wireScheduler` |
39
+ | `emitter.emit/on` (`EventEmitter`) | in-process event (not durable) | EventHub / queue; note the lost-on-crash gap |
40
+ | Mongoose/Sequelize hooks (`pre('save')`, `beforeCreate`) | implicit events / validation | explicit step in command |
41
+ | `express.raw()` / `bodyParser.raw` for one route | deployment constraint (signature verification) | raw-body config on that `wireHTTP` route |
42
+ | `app.use(express.static)` + SPA fallback | frontend hosting | separate deploy / TanStack Start |
43
+ | `res.render` (EJS/Pug/Handlebars) | server-rendered frontend (see SKILL.md note) | TanStack route + `usePikkuQuery` |
44
+ | `req.session` / `req.user` set by Passport | session model | Pikku session |
45
+
46
+ ## Traps
47
+
48
+ - **Public by omission.** The unguarded route is the default. Build a table of every route vs which middleware applies and look for ones that have none; the legacy apps that "have auth" often have three unguarded admin endpoints. Each is a gaps.json security entry.
49
+ - **Middleware order bugs.** A route registered before `app.use(auth)` is public even though it sits in the same file. Read order top to bottom, with mounts.
50
+ - **`next(err)` and the error handler** define the real error contract (status codes, shapes). Read the final `app.use((err, req, res, next) => ...)` and record the mapping.
51
+ - **Business logic in handlers.** If no service layer exists, the handler IS the command: its guards, DB writes, side effects and response form one concept. Name it from the effect.
52
+ - **Duplicated handlers.** Copy-pasted routes (`/v1` and `/v2`, or admin and user versions) that drifted. List both, flag the difference as a gap.
53
+ - **Hidden writers.** Several routes and a cron job may all write one status column. Grep the column to find every transition, as with Rails.
54
+ - **Callbacks and fire-and-forget.** `doThing().catch(console.error)` without `await` means a side effect that can fail silently: an invariant with `enforcedBy: nothing`.
55
+ - **Tenancy by hand.** Whether `WHERE tenant_id = req.user.tenantId` appears on every query is the question. Missing on one is a gap.
56
+ - **Mongo and loose schemas.** With schemaless data, the entity shape is spread across writers. Reconstruct attributes from schemas, fixtures and serializers, and say which are optional in practice.
57
+ - **Env-driven branches** (`if (process.env.NODE_ENV === 'production')`, feature flags): record the flag and the behavior difference.
58
+ - **Monolith with many entry points**: `server.js`, `worker.js`, `cron.js`, `scripts/`. Each runnable is a component in `architecture.json`.
59
+
60
+ ## Search checklist
61
+
62
+ ```
63
+ grep -rnE "app\.use\(|router\.use\(" . # middleware + mounts, in file order
64
+ grep -rnE "(app|router)\.(get|post|put|patch|delete|all|route)\(" --include=*.{js,ts} .
65
+ grep -rnE "status\((401|403|404|409|422)\)" --include=*.{js,ts} . # guards = policies
66
+ grep -rnE "req\.user|req\.session|req\.auth" --include=*.{js,ts} .
67
+ grep -rnE "setInterval|setTimeout\(.*[0-9]{4,}|cron\.schedule|new CronJob|agenda\.define|\.process\(" .
68
+ grep -rnE "express\.raw|bodyParser\.raw|stripe\.webhooks|constructEvent|createHmac" .
69
+ grep -rnE "emit\(|\.on\('" --include=*.{js,ts} .
70
+ grep -rnE "process\.env\.[A-Z_]+" -o --include=*.{js,ts} . | sort | uniq -c | sort -rn
71
+ ```
72
+
73
+ ## Confidence notes
74
+
75
+ Behavior in a handler plus a supertest scenario is `high`. A rule that only appears in one route of several similar ones is `medium` (decide whether the others should have it, and record the decision needed). Because there is no declarative structure, expect more `medium` than in Nest, and prefer a short `decisionsNeeded` list over guessing.