@pikku/skills 0.12.57 → 0.12.59

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 (72) 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-agent/SKILL.md +16 -13
  6. package/skills/pikku-agent/references/runner-vercel.md +1 -1
  7. package/skills/pikku-architect/SKILL.md +7 -10
  8. package/skills/pikku-auth/SKILL.md +16 -20
  9. package/skills/pikku-auth/references/better-auth.md +74 -37
  10. package/skills/pikku-auth/references/sessions.md +8 -0
  11. package/skills/pikku-build/SKILL.md +36 -68
  12. package/skills/pikku-build/references/app.md +27 -828
  13. package/skills/pikku-build/references/feature.md +3 -5
  14. package/skills/pikku-build/references/phase-1-what-it-is.md +167 -0
  15. package/skills/pikku-build/references/phase-2-people-and-apps.md +133 -0
  16. package/skills/pikku-build/references/phase-3-plan.md +62 -0
  17. package/skills/pikku-build/references/phase-4-build.md +249 -0
  18. package/skills/pikku-build/references/phase-5-prove-and-ship.md +148 -0
  19. package/skills/pikku-build/references/platform.md +5 -6
  20. package/skills/pikku-build/references/quick.md +2 -2
  21. package/skills/pikku-build/references/ship.md +4 -5
  22. package/skills/pikku-changes/SKILL.md +13 -7
  23. package/skills/pikku-concepts/SKILL.md +19 -258
  24. package/skills/pikku-concepts/references/cli.md +63 -0
  25. package/skills/pikku-concepts/references/functions.md +117 -0
  26. package/skills/pikku-concepts/references/language.md +5 -5
  27. package/skills/pikku-concepts/references/packages.md +6 -4
  28. package/skills/pikku-concepts/references/testing.md +14 -0
  29. package/skills/pikku-convert-screen/SKILL.md +7 -1
  30. package/skills/pikku-deploy/SKILL.md +29 -30
  31. package/skills/pikku-deploy/references/ws.md +1 -1
  32. package/skills/pikku-emails/SKILL.md +7 -3
  33. package/skills/pikku-guide/SKILL.md +7 -10
  34. package/skills/pikku-i18n/SKILL.md +18 -19
  35. package/skills/pikku-i18n/references/messages.md +6 -8
  36. package/skills/pikku-knowledge/SKILL.md +42 -45
  37. package/skills/pikku-kysely/SKILL.md +50 -13
  38. package/skills/pikku-list-query/SKILL.md +27 -14
  39. package/skills/pikku-meta/SKILL.md +13 -16
  40. package/skills/pikku-meta/references/audit.md +1 -45
  41. package/skills/pikku-meta/references/versioning.md +1 -1
  42. package/skills/pikku-middleware/SKILL.md +23 -13
  43. package/skills/pikku-middleware/references/middleware-patterns.md +2 -2
  44. package/skills/pikku-n8n-import/SKILL.md +7 -8
  45. package/skills/pikku-permissions/SKILL.md +14 -8
  46. package/skills/pikku-react/SKILL.md +7 -6
  47. package/skills/pikku-realtime/SKILL.md +16 -11
  48. package/skills/pikku-report/SKILL.md +8 -10
  49. package/skills/pikku-scenario/SKILL.md +15 -18
  50. package/skills/pikku-scenario/references/steps.md +3 -4
  51. package/skills/pikku-scenario/references/virtual-users.md +1 -1
  52. package/skills/pikku-seo/SKILL.md +16 -8
  53. package/skills/pikku-service-backends/SKILL.md +26 -28
  54. package/skills/pikku-services/SKILL.md +8 -9
  55. package/skills/pikku-services/references/services.md +1 -1
  56. package/skills/pikku-software-archaeology/README.md +12 -1
  57. package/skills/pikku-software-archaeology/SKILL.md +9 -2
  58. package/skills/pikku-software-archaeology/references/pikku-mapping.md +1 -1
  59. package/skills/pikku-software-archaeology/references/sources/express.md +75 -0
  60. package/skills/pikku-software-archaeology/references/sources/laravel.md +77 -0
  61. package/skills/pikku-software-archaeology/references/sources/nestjs.md +73 -0
  62. package/skills/pikku-software-archaeology/references/sources/rails.md +72 -0
  63. package/skills/pikku-software-archaeology/references/tests-to-scenarios.md +72 -0
  64. package/skills/pikku-tailwind/SKILL.md +15 -16
  65. package/skills/pikku-theme/SKILL.md +15 -17
  66. package/skills/pikku-ui/SKILL.md +4 -3
  67. package/skills/pikku-webhook/SKILL.md +10 -11
  68. package/skills/pikku-wiring/SKILL.md +45 -47
  69. package/skills/pikku-wiring/references/channel.md +1 -1
  70. package/skills/pikku-wiring/references/scheduler.md +1 -1
  71. package/skills/pikku-wiring/references/trigger.md +24 -1
  72. package/skills/pikku-workflow/SKILL.md +7 -8
@@ -0,0 +1,72 @@
1
+ # Source guide: Ruby on Rails
2
+
3
+ Read this when the survey finds `Gemfile` + `config/routes.rb`. Rails is convention-heavy, so most intent sits in places the framework names for you. The traps are the things Rails does implicitly: callbacks, concerns, `default_scope`, and the console.
4
+
5
+ ## Detect
6
+
7
+ `Gemfile` with `rails`, `config/application.rb`, `app/models`, `db/schema.rb` or `db/structure.sql`. Note the Rails version (`Gemfile.lock`) and whether it is API-only (`config.api_only`). Also note engines under `engines/` or `gems/`: each is a candidate domain.
8
+
9
+ ## Where intent hides
10
+
11
+ | Location | Blueprint concept |
12
+ | ------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
13
+ | `db/schema.rb` / `structure.sql` (+ `db/migrate` for history) | entities, attributes, DB-enforced invariants (unique indexes, FKs, checks) |
14
+ | `app/models/*.rb`: validations, associations, scopes, enums | entities, relationships, policies (validation), states |
15
+ | `aasm`, `state_machines`, `enum status:` + every `update(status:)` | entity lifecycle: each write site is a transition |
16
+ | `config/routes.rb` (+ `routes/*.rb`, `concerns`) | api surfaces; `resources` generates 7 routes, so list only those enabled |
17
+ | `app/controllers/**`: `before_action`, `rescue_from`, strong params | commands/queries, policies, input shapes |
18
+ | `app/policies/*` (Pundit), `app/models/ability.rb` (CanCanCan) | policies (authorization) |
19
+ | `app/services`, `app/interactors`, `app/commands`, `app/operations` | commands, usually already named in domain language |
20
+ | `app/jobs`, `app/workers` (Sidekiq/ActiveJob/Resque), `config/schedule.rb` (whenever), `sidekiq.yml`, `config/recurring.yml` | system workflows, event consumers |
21
+ | `app/mailers`, `app/views/*_mailer` | integration + implicit event consumer ("InvoicePaid sends receipt") |
22
+ | `app/admin`, `activeadmin`, `rails_admin`, `administrate` | admin workflows and the real list of manageable entities |
23
+ | `lib/tasks/*.rake` | operator workflows (data fixes, imports, nightly sweeps) |
24
+ | `config/initializers/*` | integrations, global middleware, feature flags, hardcoded constants |
25
+ | `spec/` (RSpec) or `test/`: request, model, system specs | scenarios; edge-case rules that exist nowhere else |
26
+ | `db/seeds.rb`, `spec/factories` | realistic entity shapes and the roles/plans the product assumes |
27
+
28
+ ## Idiom -> blueprint -> Pikku
29
+
30
+ | Rails idiom | Blueprint concept | Pikku shortcut |
31
+ | ------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------- |
32
+ | `after_create` / `after_commit` / `after_save` | **implicit event** (`explicit: false`), consumers = what the callback does | emit via EventHub/queue; consumer becomes `wireQueueWorker` |
33
+ | `before_validation`, `validates`, `validate :x` | policy (validation) | Zod refinement on the command `input` |
34
+ | `before_action :authenticate_user!` / `authorize` | policy (authorization), `enforcedAt` per controller | one named permission function, wired once |
35
+ | `has_secure_password`, Devise, `devise_for` | `LogInUser` / `RegisterUser` commands; session policy | auth middleware / `better-auth`; do not hand-port Devise internals |
36
+ | `enum` / `aasm event :x` | entity states + transitions | `status` column + guards inside the owning command |
37
+ | `default_scope`, `acts_as_tenant`, `Current.account` | **tenancy scoping on every query** | session filter on every read; missing scope = gap |
38
+ | `dependent: :destroy` / `:nullify` | invariant on deletion + cascade side effects | FK `ON DELETE` in migration, or explicit command steps |
39
+ | `counter_cache`, `touch: true` | derived state (invariant, may drift) | computed in query, or updated inside the command |
40
+ | ActiveJob `perform_later` | command effect: async step | `wireQueueWorker` |
41
+ | `ActionCable` channels | realtime interface | EventHub topics |
42
+ | `ActiveStorage` / `CarrierWave` / `Shrine` | integration (file storage) + attachment on entity | storage service |
43
+ | `Rails.application.routes` `mount Sidekiq::Web` | operator tooling, not product: drop | excluded |
44
+ | ERB/Slim/Haml + ViewComponent + Stimulus/Turbo | frontend (see SKILL.md server-rendered note) | TanStack route + `usePikkuQuery`; Stimulus = `custom-logic` candidate |
45
+
46
+ ## Traps
47
+
48
+ - **Callbacks are the event system and nobody wrote it down.** Read every model's callback list, not just the controller. A status flip that also sends an email and bumps a counter is one event, `InvoicePaid`, with two consumers.
49
+ - **Concerns hide behavior.** `include Trackable` or `include Archivable` may add scopes, callbacks and validations to ten models. Open the concern and attribute its rules to every including model.
50
+ - **`default_scope` and `unscoped`.** A default scope silently filters deleted/other-tenant rows; every `unscoped` call is a deliberate escape worth a gap entry (security or reporting).
51
+ - **Fat controllers with no service layer**: the action body is the command. Name the command from the action plus its effects, not the route.
52
+ - **Monkeypatches and gems that override behavior** (`lib/core_ext`, `config/initializers`): grep for `class_eval`, `prepend`, `Module#refine`.
53
+ - **Soft delete** (`paranoia`, `discard`, `deleted_at`): the entity has an `archived` state; "delete" is a command with different semantics from the DB row removal.
54
+ - **Rails console and rake tasks are product surface.** If ops fix data in the console regularly (check READMEs, runbooks, `lib/tasks`), those fixes are missing commands. Record them in gaps.json.
55
+ - **Feature flags** (`flipper`, `rollout`, ENV): a flagged-off path is not dead; record the flag and who flips it.
56
+ - **Schema drift:** `schema.rb` is the truth, migrations are history. If they disagree, trust schema and note it. Raw SQL in migrations (`execute`) often hides triggers and views.
57
+
58
+ ## Search checklist
59
+
60
+ ```
61
+ grep -rnE "after_(create|commit|save|update|destroy)|before_(save|destroy)" app/models
62
+ grep -rnE "default_scope|unscoped|acts_as_tenant|Current\." app
63
+ grep -rnE "aasm|state_machine|enum |update(_attribute|_column)?s?\(.*status" app
64
+ grep -rnE "perform_(later|async|in)|\.deliver_(later|now)" app lib
65
+ grep -rnE "authorize|policy_scope|can\?|load_and_authorize" app
66
+ grep -rnE "Flipper|feature_enabled|ENV\[" app config lib
67
+ ls lib/tasks config/schedule.rb config/recurring.yml 2>/dev/null
68
+ ```
69
+
70
+ ## Confidence notes
71
+
72
+ A rule enforced by both a model validation and a request spec is `high`. A rule that only a callback implies (nobody tests it) is `medium`. Anything inferred from a view template alone is `low`.
@@ -0,0 +1,72 @@
1
+ # Legacy tests -> blueprint scenarios -> Pikku scenarios
2
+
3
+ Phase 2 reads the legacy test suite as a product spec. This file covers the two hops: legacy test -> `workflows.json` `scenarios[]` (extraction), then scenario -> `pikkuScenario` (rebuild). Pikku's side is defined by the `pikku-scenario` skill (`steps.md`, `coverage.md`); read it before generating, this file only records how legacy tests meet it.
4
+
5
+ ## Step 1: triage every test by kind
6
+
7
+ Not every test is a scenario. Classify first; only some become `scenarios[]`.
8
+
9
+ | Legacy test | Becomes in the blueprint | Becomes in Pikku |
10
+ | ------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------- |
11
+ | Request / feature / e2e (Rails request spec, Laravel Feature, Nest e2e, supertest) | `scenarios[]` on the workflow, `fromTest` set | `pikkuScenario` (`given`/`when`/`then`) |
12
+ | Browser journey (Playwright, Capybara, Dusk, Cypress) | `scenarios[]` with a UI surface | `pikkuScenarioStep` with a `browser` binding, written as intent |
13
+ | Model / validation test | upgrades a policy or invariant to `high`, cites the test | Zod refinement + a negative step (`expectError`), or a unit test |
14
+ | Authorization test (policy spec, "cannot see another user's invoice") | upgrades an authorization policy to `high` | scenario as the owner and as another virtual user, `expectError` |
15
+ | Job / cron / webhook test | `scenarios[]` on the **system** workflow | `scenario.runScheduledTask`, or post to the webhook, then `expectEventually` |
16
+ | Pure-logic unit test (calculation, parser, state machine table) | an invariant, with examples | plain `node:test` calling `func` directly |
17
+ | Heavily mocked unit test (RSpec doubles, Jest DB mocks, "expects `save` called once") | nothing: it pins the old implementation | drop it, and note it in `gaps.json` only if it hid a real rule |
18
+ | Skipped / pending / commented-out test | `gaps.json` (intended behavior never finished) | not generated |
19
+ | Snapshot test of rendered HTML or JSON | at most a weak scenario | rewrite as an assertion on the outcome, not the markup |
20
+
21
+ A rule attested by an implementation guard **and** a passing test is `high`. A scenario from a suite that does not run today is at most `medium`, because nobody has checked it still holds.
22
+
23
+ ## Step 2: write scenarios as intent
24
+
25
+ Extract each test as given / when / outcome in domain language (`reads a credential nobody stored`), not as HTTP verbs or selectors. Pikku step names describe what the user wanted, in English identifiers, and the prose is third-person with no virtual user in it (the reporter supplies the subject). Keep the legacy test name in `fromTest`; do not copy its phrasing if it is a method name (`test_returns_404_for_deleted`).
26
+
27
+ One scenario per **journey**, not per function and not per test. Several legacy tests that walk the same journey with different inputs become one scenario, or entries in a `pikkuFeature` (see table-driven tests below).
28
+
29
+ ## Step 3: translate the setup, because Pikku has no back door
30
+
31
+ A Pikku step runs in the CLI process and reaches the app **only over the wire as a virtual user**: `virtualUser.invoke`, `invokeRaw`, or a plain `fetch` to the target. There is no database handle, no secrets, and no state reset. Legacy tests lean on all of those, so most of the translation effort is here.
32
+
33
+ | Legacy setup | Pikku equivalent |
34
+ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
35
+ | Factories / fixtures inserting rows directly (`factory_bot`, `Model::factory()`, `repo.save`) | `given` steps that call the app's own commands as a virtual user. **If no command can create the state, the product is missing one: record it in `gaps.json`.** |
36
+ | Clean database per test (transactions, `RefreshDatabase`, truncate) | None. Use unique ids per run and scope what you create; `before`/`after` hooks for reset RPCs |
37
+ | Logged-in user helpers (`sign_in user`, `actingAs`, forged JWTs) | `defineVirtualUsers`: one entry per person/role, signed in by the runner |
38
+ | Roles built inline per test | one virtual user per role actually used (`shopper`, `support`, `admin`) |
39
+ | Frozen / travelled time (`travel_to`, `Carbon::setTestNow`, fake timers) | Not available against a live server. Fire schedulers with `runScheduledTask`; for time-based rules, expose the rule as testable input or keep it as a unit test |
40
+ | Stubbed externals / recorded HTTP (VCR, nock, `Http::fake`, Mockery) | stubbed services with the server in `--test` mode, asserted with `expectService`; or a mock server in the feature's `before`/`after` |
41
+ | `sleep` / waiting for the queue | `expectEventually(..., { within, interval })` |
42
+ | Direct DB assertions (`expect(Order.count)`, `assertDatabaseHas`) | assert through a query the app exposes. If no query shows it, the fact is invisible to users: question whether it matters, or expose it |
43
+
44
+ ## Step 4: assertions
45
+
46
+ - Every scenario must assert (PKU680): a flow of only `given`/`when` fails the inspector. Convert each legacy `expect`/`assert` into a `then`.
47
+ - A negative test (`returns 403`, `raises`, `assertForbidden`) becomes `expectError`, or `invokeRaw` when the refusal itself is the assertion. Authorization tests need two virtual users: the owner succeeds, someone else is refused.
48
+ - Do not assert on legacy status codes or response shapes unless they are part of a public contract (a documented API with outside consumers). Otherwise assert the outcome (the invoice is paid, the user sees it) and let `decisionsNeeded` record whether the old shape must be kept.
49
+ - A `then` runs every binding it declares and fails if they disagree (API says paid, browser still shows a spinner). When a legacy journey had both an API test and a browser test for the same outcome, they are witnesses of one `then`, not two scenarios.
50
+
51
+ ## Table-driven tests
52
+
53
+ RSpec `shared_examples`, PHPUnit `@dataProvider` / Pest `with()`, `it.each`, `test.each`: these are Examples. In Pikku, put the cases in a `pikkuFeature` as `{ scenario, data }` entries (one run per entry) or loop the steps. Do not write a scenario per row.
54
+
55
+ ## What each legacy idiom looks like
56
+
57
+ | Framework | Where the journey tests are | Idioms to translate |
58
+ | -------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
59
+ | Rails | `spec/requests`, `spec/system`, `test/integration` | `let`/`before` blocks and `factory_bot` = setup; `travel_to` = time; `VCR` = externals |
60
+ | Laravel | `tests/Feature`, `tests/Browser` (Dusk) | `RefreshDatabase`, `actingAs`, `Queue::fake`, `Mail::fake`, `Http::fake` |
61
+ | NestJS | `test/*.e2e-spec.ts` (supertest + `Test.createTestingModule`) | `overrideProvider` = stubbed service; `beforeEach` truncate = no reset in Pikku |
62
+ | Express | `test/`, `__tests__` (supertest, mocha/jest) | `nock` = externals; DB seeded in `beforeEach`; auth via forged token helper |
63
+
64
+ ## Coverage as the acceptance check
65
+
66
+ Do not invent a parallel coverage file. Pikku already computes coverage from scenarios: run `pikku scenario run <env> --coverage` against `pikku dev --coverage` and read `<outDir>/coverage/scenario-coverage.json`. For the rebuild, the useful comparison is:
67
+
68
+ 1. Every blueprint command, policy and invariant should be reachable from at least one scenario (check against the coverage output).
69
+ 2. Anything uncovered either has no legacy test (record it in `gaps.json` as untested-but-critical) or its test was dropped as implementation-pinning (confirm that was deliberate).
70
+ 3. The legacy suite's passing journeys should pass against the rebuild, so migrate them first and treat them as the regression bar.
71
+
72
+ Scenarios are flows and coverage is their consequence, so close gaps by extending a real journey, not by adding a scenario per function.
@@ -1,13 +1,12 @@
1
1
  ---
2
2
  name: pikku-tailwind
3
3
  description: >-
4
- Use when building a Tailwind + shadcn/ui interface on top of a Pikku backend — rendering dates that
5
- came back from a generated client, keeping layout flow-relative so the app survives an RTL locale,
6
- branching on colour scheme without hardcoding a shade, and choosing components. TRIGGER when:
7
- putting a value from usePikkuQuery or an RPC response on screen, writing margins/padding/alignment
8
- classes, choosing a date input or any shadcn component, handling light/dark, or fixing a
9
- `@shadcn/lint` error. DO NOT TRIGGER when: the data does not come from a Pikku client (this is only
10
- about what the generated clients hand you), or for user-facing copy (use pikku-i18n).
4
+ Tailwind + shadcn/ui on a Pikku backend: rendering dates from a generated client, flow-relative
5
+ layout that survives RTL, colour-scheme branching without hardcoded shades, choosing components.
6
+ TRIGGER when: putting a usePikkuQuery/RPC value on screen, writing margin/padding/alignment
7
+ classes, picking a date input or shadcn component, handling light/dark, or fixing a `@shadcn/lint`
8
+ error. DO NOT TRIGGER when the data does not come from a Pikku client, or for user-facing copy
9
+ (use pikku-i18n).
11
10
  installGroups: [client]
12
11
  ---
13
12
 
@@ -83,15 +82,15 @@ All of this applies to stub and fixture dates exactly as it does to real data.
83
82
  Write layout classes flow-relative so the UI works in both LTR and RTL languages. Pikku's i18n ships
84
83
  Arabic, Hebrew, Farsi and Urdu support, and a physical margin is what breaks under it.
85
84
 
86
- | Avoid | Use instead |
87
- | ------------------------ | --------------------------- |
88
- | `ml-*`, `mr-*` | `ms-*`, `me-*` |
89
- | `pl-*`, `pr-*` | `ps-*`, `pe-*` |
90
- | `left-*`, `right-*` | `start-*`, `end-*` |
91
- | `text-left`, `text-right`| `text-start`, `text-end` |
92
- | `border-l`, `border-r` | `border-s`, `border-e` |
93
- | `rounded-l`, `rounded-r` | `rounded-s`, `rounded-e` |
94
- | `flex-row-reverse` | the `dir` attribute |
85
+ | Avoid | Use instead |
86
+ | ------------------------- | ------------------------ |
87
+ | `ml-*`, `mr-*` | `ms-*`, `me-*` |
88
+ | `pl-*`, `pr-*` | `ps-*`, `pe-*` |
89
+ | `left-*`, `right-*` | `start-*`, `end-*` |
90
+ | `text-left`, `text-right` | `text-start`, `text-end` |
91
+ | `border-l`, `border-r` | `border-s`, `border-e` |
92
+ | `rounded-l`, `rounded-r` | `rounded-s`, `rounded-e` |
93
+ | `flex-row-reverse` | the `dir` attribute |
95
94
 
96
95
  Directional icons (chevrons, arrows) flip with `rtl:rotate-180`.
97
96
 
@@ -1,15 +1,13 @@
1
1
  ---
2
2
  name: pikku-theme
3
3
  description: >-
4
- Use when giving a Pikku Tailwind + shadcn/ui app its look: picking and applying a theme with `pikku
5
- ui theme list|apply`, composing brand colours, fonts, page and ink over a preset's structure, matching
6
- an existing look by hand, declaring component variants in the component's `cva` definition,
7
- replacing template names (`pikku validate` flags them), and keeping emails/theme.json on-brand.
8
- Also the light/dark and WCAG contrast rules a theme must clear. TRIGGER when: the user asks to
9
- theme, restyle, rebrand or recolour an app; the project has packages/theme (themes/*.json,
10
- active.json); a screen needs a colour, font, radius or shadow; or the app still shows a template
11
- name. DO NOT TRIGGER when: rendering data or RTL layout (use pikku-tailwind),
12
- writing email templates (use pikku-emails), or accessibility beyond colour (use pikku-a11y).
4
+ Giving a Pikku Tailwind + shadcn/ui app its look: `pikku ui theme list|apply`, brand
5
+ colours/fonts/page/ink over a preset, matching an existing look, cva component variants, replacing
6
+ template names (`pikku validate` flags them), emails/theme.json, plus light/dark and WCAG contrast
7
+ rules. TRIGGER when: asked to theme, restyle, rebrand or recolour; the project has packages/theme
8
+ (themes/*.json, active.json); a screen needs a colour, font, radius or shadow; or template names
9
+ still show. DO NOT TRIGGER for data rendering or RTL layout (use pikku-tailwind), email templates
10
+ (use pikku-emails) or accessibility beyond colour (use pikku-a11y).
13
11
  installGroups: [client]
14
12
  ---
15
13
 
@@ -86,14 +84,14 @@ pikku ui theme apply --preset storybook \
86
84
  pikku ui theme apply --preset quorum --structure monopro
87
85
  ```
88
86
 
89
- | Flag | Sets |
90
- | --- | --- |
91
- | `--preset <id>` | starting brand + structure; the theme is written as `themes/<id>.json` (no preset → `custom`, based on the first preset) |
92
- | `--structure <id>` | swap in another preset's structure (depth) under this brand |
93
- | `--primary`, `--secondary`, `--accent` | brand colours, hex |
94
- | `--font-heading`, `--font-body` | Google Fonts families |
95
- | `--page` | light-mode page colour (`structure.page`) — paper, linen, bone instead of white |
96
- | `--ink` | light-mode text colour (`structure.ink`) |
87
+ | Flag | Sets |
88
+ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
89
+ | `--preset <id>` | starting brand + structure; the theme is written as `themes/<id>.json` (no preset → `custom`, based on the first preset) |
90
+ | `--structure <id>` | swap in another preset's structure (depth) under this brand |
91
+ | `--primary`, `--secondary`, `--accent` | brand colours, hex |
92
+ | `--font-heading`, `--font-body` | Google Fonts families |
93
+ | `--page` | light-mode page colour (`structure.page`) — paper, linen, bone instead of white |
94
+ | `--ink` | light-mode text colour (`structure.ink`) |
97
95
 
98
96
  Flags are flat scalars, one value each; there is no nested `--colors` object. One call writes the
99
97
  theme, makes it active, regenerates `themes/index.ts` (so the running app reloads) and re-brands
@@ -1,9 +1,10 @@
1
1
  ---
2
2
  name: pikku-ui
3
3
  description: >-
4
- How a screen gets made: it is delegated to a subagent, which is handed the project's design rules and designs it with the impeccable skill.
5
- TRIGGER when: the user asks for a screen, page, view, form, dashboard or empty/error state, or asks for an existing screen to be redone or restyled.
6
- DO NOT TRIGGER when: the change is backend only, copy only (use pikku-i18n), or a one-line fix to a screen that already looks right.
4
+ How a screen gets made: delegated to a subagent handed the project's design rules, designing with
5
+ the impeccable skill. TRIGGER when: asked for a screen, page, view, form, dashboard or empty/error
6
+ state, or to redo/restyle an existing screen. DO NOT TRIGGER for backend-only changes, copy-only
7
+ changes (use pikku-i18n) or a one-line fix to a screen that already looks right.
7
8
  installGroups: [client]
8
9
  ---
9
10
 
@@ -1,16 +1,15 @@
1
1
  ---
2
2
  name: pikku-webhook
3
3
  description: >-
4
- Use when an application needs to SEND outgoing webhooks — notifying a customer's endpoint that
5
- something happened, with signing, retries and a delivery log. Covers WebhookService,
6
- QueueWebhookService, the `pikku-outgoing-webhooks` queue worker, `scaffold.webhook`,
7
- `config.webhook`, signature verification on the receiving side, and KyselyWebhookService's
8
- delivery history. TRIGGER when: code uses webhookService, QueueWebhookService,
9
- KyselyWebhookService, pikkuWebhookWorkerFunc, PIKKU_OUTGOING_WEBHOOK_QUEUE_NAME,
10
- SendWebhookInput or X-Pikku-Signature. TRIGGER when: the user asks to emit events to a
11
- customer URL, build a webhook endpoint settings screen, add a signing secret, or show
12
- delivery attempts. DO NOT TRIGGER when: the user is RECEIVING webhooks from a third party
13
- into a route — that is an ordinary wireHTTP function (use pikku-wiring).
4
+ Sending OUTGOING webhooks from a Pikku app (notify a customer's endpoint, with signing, retries
5
+ and a delivery log): WebhookService, QueueWebhookService, the `pikku-outgoing-webhooks` queue
6
+ worker, `scaffold.webhook`, `config.webhook`, receiver signature verification,
7
+ KyselyWebhookService delivery history. TRIGGER when: code uses webhookService,
8
+ QueueWebhookService, KyselyWebhookService, pikkuWebhookWorkerFunc,
9
+ PIKKU_OUTGOING_WEBHOOK_QUEUE_NAME, SendWebhookInput or X-Pikku-Signature; or asked to emit events
10
+ to a customer URL, build a webhook settings screen, add a signing secret or show delivery
11
+ attempts. DO NOT TRIGGER for RECEIVING third-party webhooks (an ordinary wireHTTP function; use
12
+ pikku-wiring).
14
13
  installGroups: [core]
15
14
  ---
16
15
 
@@ -201,7 +200,7 @@ const deliveries = await webhookService.listDeliveries({
201
200
  const detail = await webhookService.getDelivery(deliveryId) // { delivery, attempts }
202
201
  ```
203
202
 
204
- Building a console screen is `listDeliveries` for the list and `getDelivery` for
203
+ Building a deliveries screen is `listDeliveries` for the list and `getDelivery` for
205
204
  the drill-in. A hand-written select over `webhookDelivery` is a sign the wrong
206
205
  service is registered.
207
206
 
@@ -1,17 +1,15 @@
1
1
  ---
2
2
  name: pikku-wiring
3
3
  description: >-
4
- Use when exposing a Pikku function over a transport — HTTP routes and SSE, WebSocket channels,
5
- typed realtime pub/sub, internal and exposed RPC, queue workers, cron schedules, event triggers,
6
- MCP tools/resources/prompts, CLI commands, or a Slack gateway. Covers choosing the wiring, what
7
- every wiring shares, and what differs: function type, session source, and which calls throw
8
- instead of returning. TRIGGER when: code uses wireHTTP, defineHTTPRoutes, wireChannel,
9
- wireQueueWorker, wireScheduler, wireTrigger, wireTriggerSource, wireCLI, wireMCPResource,
10
- wireMCPPrompt, `mcp: true`, `sse: true`, `expose: true`, rpc.invoke, SlackGatewayAdapter, or the
11
- user asks how to expose, route, schedule, queue, stream or publish a function. DO NOT TRIGGER
12
- when: writing the function body (use pikku-concepts), authorization (use pikku-permissions), serving on
13
- a runtime (use pikku-deploy), building a live-updating view (use pikku-realtime), or sending
14
- outgoing webhooks (use pikku-webhook).
4
+ Exposing a Pikku function over a transport: HTTP and SSE, WebSocket channels, typed pub/sub, RPC,
5
+ queue workers, cron, event triggers, MCP tools/resources/prompts, CLI commands, Slack gateway;
6
+ choosing the wiring and what differs by function type, session source and throw-vs-return. TRIGGER
7
+ when: code uses wireHTTP, defineHTTPRoutes, wireChannel, wireQueueWorker, wireScheduler,
8
+ wireTrigger, wireTriggerSource, wireCLI, wireMCPResource, wireMCPPrompt, `mcp: true`, `sse: true`,
9
+ `expose: true`, rpc.invoke or SlackGatewayAdapter, or asked how to expose, route, schedule, queue,
10
+ stream or publish a function. DO NOT TRIGGER for function bodies (use pikku-concepts),
11
+ authorization (use pikku-permissions), runtimes (use pikku-deploy), live views (use
12
+ pikku-realtime) or outgoing webhooks (use pikku-webhook).
15
13
  installGroups: [core]
16
14
  ---
17
15
 
@@ -55,7 +53,7 @@ Three consequences that hold for every wiring below:
55
53
  - **Permissions are never declared on the wiring.** Wire-level permissions were
56
54
  removed in #972 — declare them on the function (`pikkuFunc({ permissions })`,
57
55
  see `pikku-permissions`) or app-wide via `addGlobalPermission`. Tags and
58
- patterns now target *middleware* only.
56
+ patterns now target _middleware_ only.
59
57
  - **The wire is the third argument**, not a service. `channel`, `rpc`, `session`,
60
58
  `setSession`, `mcp`, `queue`, `scheduledTask` and `cli` all live there.
61
59
 
@@ -70,30 +68,30 @@ exactly the typing that makes the wiring worth having, silently.
70
68
  naming the `#pikku` leaf to use instead. `@pikku/core/services` is the one
71
69
  subpath it exempts — the service implementations bootstrap picks.
72
70
 
73
- | Wiring | Import from |
74
- | --- | --- |
75
- | `wireHTTP`, `defineHTTPRoutes`, `wireHTTPRoutes` | `#pikku/http` |
76
- | `wireChannel`, `defineChannelRoutes` | `#pikku/channel` |
77
- | `wireQueueWorker` | `#pikku/queue` |
78
- | `wireScheduler` | `#pikku/scheduler` |
79
- | `wireTrigger`, `wireTriggerSource`, `pikkuTriggerFunc` | `#pikku/trigger` |
80
- | `wireCLI`, `pikkuCLICommand`, `pikkuCLIRender` | `#pikku/cli` |
81
- | `pikkuMCPToolFunc`, `pikkuMCPResourceFunc`, `pikkuMCPPromptFunc`, `wireMCPResource`, `wireMCPPrompt` | `#pikku/mcp` |
71
+ | Wiring | Import from |
72
+ | ---------------------------------------------------------------------------------------------------- | ------------------ |
73
+ | `wireHTTP`, `defineHTTPRoutes`, `wireHTTPRoutes` | `#pikku/http` |
74
+ | `wireChannel`, `defineChannelRoutes` | `#pikku/channel` |
75
+ | `wireQueueWorker` | `#pikku/queue` |
76
+ | `wireScheduler` | `#pikku/scheduler` |
77
+ | `wireTrigger`, `wireTriggerSource`, `pikkuTriggerFunc` | `#pikku/trigger` |
78
+ | `wireCLI`, `pikkuCLICommand`, `pikkuCLIRender` | `#pikku/cli` |
79
+ | `pikkuMCPToolFunc`, `pikkuMCPResourceFunc`, `pikkuMCPPromptFunc`, `wireMCPResource`, `wireMCPPrompt` | `#pikku/mcp` |
82
80
 
83
81
  ## Pick a wiring
84
82
 
85
- | Reach for | When | Reference |
86
- | --- | --- | --- |
87
- | **HTTP** | REST endpoints, web APIs, and SSE streams (`sse: true`, `get` only) | `references/http.md`; every field in `references/http-options.md` |
88
- | **Channel** | A hand-designed WebSocket protocol with your own action routing | `references/channel.md` |
89
- | **Realtime** | Typed pub/sub push — the scaffolded `/events` channel and SSE topics, plus subscribing to any other `sse: true` route or channel from the client | `references/realtime.md` |
90
- | **RPC** | One function calling another, or dispatching a name from outside | `references/rpc.md` |
91
- | **Queue** | Reliable background work that must survive a crash and retry | `references/queue.md` |
92
- | **Scheduler** | Recurring work on a cron expression | `references/scheduler.md` |
93
- | **Trigger** | Reacting in-process to an external event source (Redis pub/sub, PG LISTEN) | `references/trigger.md` |
94
- | **MCP** | Exposing functions to an AI assistant as tools, resources or prompts | `references/mcp.md` |
95
- | **CLI** | A terminal program with commands, subcommands and options | `references/cli.md`; end-to-end walkthrough in `references/cli-complete-example.md` |
96
- | **Gateway** | An inbound integration from a third-party product — Slack is the shipped adapter | `references/gateway-slack.md` |
83
+ | Reach for | When | Reference |
84
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
85
+ | **HTTP** | REST endpoints, web APIs, and SSE streams (`sse: true`, `get` only) | `references/http.md`; every field in `references/http-options.md` |
86
+ | **Channel** | A hand-designed WebSocket protocol with your own action routing | `references/channel.md` |
87
+ | **Realtime** | Typed pub/sub push — the scaffolded `/events` channel and SSE topics, plus subscribing to any other `sse: true` route or channel from the client | `references/realtime.md` |
88
+ | **RPC** | One function calling another, or dispatching a name from outside | `references/rpc.md` |
89
+ | **Queue** | Reliable background work that must survive a crash and retry | `references/queue.md` |
90
+ | **Scheduler** | Recurring work on a cron expression | `references/scheduler.md` |
91
+ | **Trigger** | Reacting in-process to an external event source (Redis pub/sub, PG LISTEN) | `references/trigger.md` |
92
+ | **MCP** | Exposing functions to an AI assistant as tools, resources or prompts | `references/mcp.md` |
93
+ | **CLI** | A terminal program with commands, subcommands and options | `references/cli.md`; end-to-end walkthrough in `references/cli-complete-example.md` |
94
+ | **Gateway** | An inbound integration from a third-party product — Slack is the shipped adapter | `references/gateway-slack.md` |
97
95
 
98
96
  Realtime and Channel are the pair most often confused. If the shape is "server
99
97
  pushes typed events to subscribers", use Realtime — `pikku enable events`
@@ -104,11 +102,11 @@ only when the client also sends structured messages you need to route on.
104
102
 
105
103
  ### Each wiring demands a particular function type
106
104
 
107
- | Wiring | Function type | Why |
108
- | --- | --- | --- |
109
- | HTTP, Channel, Queue, CLI, MCP | `pikkuFunc` / `pikkuSessionlessFunc` | Ordinary request/response |
110
- | Scheduler | **`pikkuVoidFunc`** | A cron has no input and no caller to return to |
111
- | Trigger *source* | **`pikkuTriggerFunc`** | Runs **once at startup**, not once per event |
105
+ | Wiring | Function type | Why |
106
+ | ------------------------------ | ------------------------------------ | ---------------------------------------------- |
107
+ | HTTP, Channel, Queue, CLI, MCP | `pikkuFunc` / `pikkuSessionlessFunc` | Ordinary request/response |
108
+ | Scheduler | **`pikkuVoidFunc`** | A cron has no input and no caller to return to |
109
+ | Trigger _source_ | **`pikkuTriggerFunc`** | Runs **once at startup**, not once per event |
112
110
 
113
111
  A trigger source is the one that surprises people: it sets up a listener, calls
114
112
  `trigger.invoke(...)` per event, and returns a teardown function. It receives
@@ -145,11 +143,11 @@ logging them as errors.
145
143
 
146
144
  ### Delivery semantics differ, and that is usually the real choice
147
145
 
148
- | Wiring | Delivery | Runs where |
149
- | --- | --- | --- |
150
- | Trigger | **At-most-once**, synchronous | In-process, alongside the listener |
151
- | Queue | **At-least-once** with retries and a dead-letter queue | Distributed workers |
152
- | Scheduler | Depends on the runtime — see below | Wherever the scheduler service runs |
146
+ | Wiring | Delivery | Runs where |
147
+ | --------- | ------------------------------------------------------ | ----------------------------------- |
148
+ | Trigger | **At-most-once**, synchronous | In-process, alongside the listener |
149
+ | Queue | **At-least-once** with retries and a dead-letter queue | Distributed workers |
150
+ | Scheduler | Depends on the runtime — see below | Wherever the scheduler service runs |
153
151
 
154
152
  Reach for a trigger to react immediately, and a queue when the work must not be
155
153
  lost. A trigger that must not drop events is a queue with extra steps.
@@ -160,11 +158,11 @@ cron expression can decide which tasks fire. `pikku-deploy` has the comparison.
160
158
 
161
159
  ### Codegen owns several wirings — do not hand-write them
162
160
 
163
- | Turn it on | Codegen writes | Never hand-write |
164
- | --- | --- | --- |
165
- | `pikku enable rpc` | `rpc-public.gen.ts` — the `POST /rpc/:rpcName` route | A second route on the same path collides |
161
+ | Turn it on | Codegen writes | Never hand-write |
162
+ | --------------------- | ---------------------------------------------------------------- | ------------------------------------------------- |
163
+ | `pikku enable rpc` | `rpc-public.gen.ts` — the `POST /rpc/:rpcName` route | A second route on the same path collides |
166
164
  | `pikku enable events` | `events.gen.ts` — the `/events` channel and `GET /events/:topic` | A hand-rolled `/events` misses disconnect cleanup |
167
- | `wireCLI` | `<program>-channel.gen.ts` — the same commands over a channel | It is regenerated on every build |
165
+ | `wireCLI` | `<program>-channel.gen.ts` — the same commands over a channel | It is regenerated on every build |
168
166
 
169
167
  Enabling the RPC endpoint says the endpoint exists, not who may call it — each
170
168
  `expose: true` function is still gated by its own `auth`, permissions and scopes.
@@ -44,7 +44,7 @@ permissions were removed in #972. Authorization lives on the function's own
44
44
  ### `pikkuChannelMiddleware(fn)`
45
45
 
46
46
  ```typescript
47
- import { pikkuChannelMiddleware } from '@pikku/core'
47
+ import { pikkuChannelMiddleware } from '@pikku/core/middleware'
48
48
 
49
49
  const middleware = pikkuChannelMiddleware(async (services, event, next) => {
50
50
  // Transform or filter events before/after
@@ -38,7 +38,7 @@ wireScheduler({
38
38
  })
39
39
  ```
40
40
 
41
- `runScheduledTask` builds its wire with a `sessionService`, so a `setSession`
41
+ `runScheduledTask` builds its wire with a `sessionService`, so a `setInitialSession`
42
42
  here is the session the function is frozen with. See the machine-auth section of
43
43
  `pikku-middleware` for the factory and for why a cron is not a user row.
44
44
 
@@ -77,6 +77,29 @@ The config-object form (`pikkuTriggerFunc({ func, title, description, tags,
77
77
  input, output })`) is also accepted when you want schemas or metadata on the
78
78
  source.
79
79
 
80
+ ### Events that belong to a user
81
+
82
+ A source that watches something on a user's behalf — polling each connected
83
+ user's mailbox, say — names that user as the second argument to `invoke`:
84
+
85
+ ```typescript
86
+ for (const userId of await credentialService.getUsersWithCredential('gmail')) {
87
+ for (const message of await fetchNewMessages(userId)) {
88
+ trigger.invoke(message, { pikkuUserId: userId })
89
+ }
90
+ }
91
+ ```
92
+
93
+ The target then runs as that user, exactly like a queue job or workflow run
94
+ carrying a `pikkuUserId`: its `type: 'wire'` credentials and its stored session
95
+ resolve for them, and a workflow target records them on the run. Without the
96
+ option the target runs as nobody.
97
+
98
+ The user is the source's assertion, not the event's — it is a separate argument
99
+ so nothing in the event data can set it. Pass only a user the source has
100
+ established itself, such as the one whose credential it just polled with, and
101
+ never an id read out of the event.
102
+
80
103
  ## Starting triggers
81
104
 
82
105
  Nothing fires until a `TriggerService` is started. For a single process,
@@ -84,7 +107,7 @@ Nothing fires until a `TriggerService` is started. For a single process,
84
107
  target and sets it up:
85
108
 
86
109
  ```typescript
87
- import { InMemoryTriggerService } from '@pikku/core/services'
110
+ import { InMemoryTriggerService } from '@pikku/core/services/in-memory-trigger'
88
111
 
89
112
  const triggerService = new InMemoryTriggerService()
90
113
  await triggerService.start()
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  name: pikku-workflow
3
3
  description: >-
4
- Use when building multi-step, durable workflows with Pikku — pikkuWorkflowFunc,
5
- pikkuWorkflowGraph, workflow.do / sleep / suspend / approval, retries, compensate, sub-workflows,
6
- and starting or resuming runs. Includes deciding whether something should be a workflow at all.
7
- TRIGGER when: code uses pikkuWorkflowFunc or pikkuWorkflowGraph, or the user asks about
8
- workflows, multi-step processes, durable execution, suspend/resume, approval gates, sagas or DAG
9
- orchestration. DO NOT TRIGGER when: the user wants a single background job (use the queue
10
- reference in pikku-wiring) or a cron task (use the scheduler reference in pikku-wiring), or is
11
- converting an n8n export (use pikku-n8n-import).
4
+ Multi-step durable workflows with Pikku: pikkuWorkflowFunc, pikkuWorkflowGraph,
5
+ workflow.do/sleep/suspend/approval, retries, compensate, sub-workflows, starting/resuming runs,
6
+ and deciding whether something should be a workflow at all. TRIGGER when: code uses
7
+ pikkuWorkflowFunc or pikkuWorkflowGraph, or asked about workflows, multi-step processes, durable
8
+ execution, suspend/resume, approval gates, sagas or DAG orchestration. DO NOT TRIGGER for a single
9
+ background job or cron task (use pikku-wiring's queue/scheduler references) or n8n exports (use
10
+ pikku-n8n-import).
12
11
  installGroups: [core]
13
12
  ---
14
13