@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.
- package/dist/skills.gen.js +2 -2
- package/package.json +10 -7
- package/skills/pikku-a11y/SKILL.md +5 -3
- package/skills/pikku-addon/SKILL.md +8 -9
- package/skills/pikku-agent/SKILL.md +16 -13
- package/skills/pikku-agent/references/runner-vercel.md +1 -1
- package/skills/pikku-architect/SKILL.md +7 -10
- package/skills/pikku-auth/SKILL.md +16 -20
- package/skills/pikku-auth/references/better-auth.md +74 -37
- package/skills/pikku-auth/references/sessions.md +8 -0
- package/skills/pikku-build/SKILL.md +36 -68
- package/skills/pikku-build/references/app.md +27 -828
- package/skills/pikku-build/references/feature.md +3 -5
- package/skills/pikku-build/references/phase-1-what-it-is.md +167 -0
- package/skills/pikku-build/references/phase-2-people-and-apps.md +133 -0
- package/skills/pikku-build/references/phase-3-plan.md +62 -0
- package/skills/pikku-build/references/phase-4-build.md +249 -0
- package/skills/pikku-build/references/phase-5-prove-and-ship.md +148 -0
- package/skills/pikku-build/references/platform.md +5 -6
- package/skills/pikku-build/references/quick.md +2 -2
- package/skills/pikku-build/references/ship.md +4 -5
- package/skills/pikku-changes/SKILL.md +13 -7
- package/skills/pikku-concepts/SKILL.md +19 -258
- package/skills/pikku-concepts/references/cli.md +63 -0
- package/skills/pikku-concepts/references/functions.md +117 -0
- package/skills/pikku-concepts/references/language.md +5 -5
- package/skills/pikku-concepts/references/packages.md +6 -4
- package/skills/pikku-concepts/references/testing.md +14 -0
- package/skills/pikku-convert-screen/SKILL.md +7 -1
- package/skills/pikku-deploy/SKILL.md +29 -30
- package/skills/pikku-deploy/references/ws.md +1 -1
- package/skills/pikku-emails/SKILL.md +7 -3
- package/skills/pikku-guide/SKILL.md +7 -10
- package/skills/pikku-i18n/SKILL.md +18 -19
- package/skills/pikku-i18n/references/messages.md +6 -8
- package/skills/pikku-knowledge/SKILL.md +42 -45
- package/skills/pikku-kysely/SKILL.md +50 -13
- package/skills/pikku-list-query/SKILL.md +27 -14
- package/skills/pikku-meta/SKILL.md +13 -16
- package/skills/pikku-meta/references/audit.md +1 -45
- package/skills/pikku-meta/references/versioning.md +1 -1
- package/skills/pikku-middleware/SKILL.md +23 -13
- package/skills/pikku-middleware/references/middleware-patterns.md +2 -2
- package/skills/pikku-n8n-import/SKILL.md +7 -8
- package/skills/pikku-permissions/SKILL.md +14 -8
- package/skills/pikku-react/SKILL.md +7 -6
- package/skills/pikku-realtime/SKILL.md +16 -11
- package/skills/pikku-report/SKILL.md +8 -10
- package/skills/pikku-scenario/SKILL.md +15 -18
- package/skills/pikku-scenario/references/steps.md +3 -4
- package/skills/pikku-scenario/references/virtual-users.md +1 -1
- package/skills/pikku-seo/SKILL.md +16 -8
- package/skills/pikku-service-backends/SKILL.md +26 -28
- package/skills/pikku-services/SKILL.md +8 -9
- package/skills/pikku-services/references/services.md +1 -1
- package/skills/pikku-software-archaeology/README.md +12 -1
- package/skills/pikku-software-archaeology/SKILL.md +9 -2
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +1 -1
- package/skills/pikku-software-archaeology/references/sources/express.md +75 -0
- package/skills/pikku-software-archaeology/references/sources/laravel.md +77 -0
- package/skills/pikku-software-archaeology/references/sources/nestjs.md +73 -0
- package/skills/pikku-software-archaeology/references/sources/rails.md +72 -0
- package/skills/pikku-software-archaeology/references/tests-to-scenarios.md +72 -0
- package/skills/pikku-tailwind/SKILL.md +15 -16
- package/skills/pikku-theme/SKILL.md +15 -17
- package/skills/pikku-ui/SKILL.md +4 -3
- package/skills/pikku-webhook/SKILL.md +10 -11
- package/skills/pikku-wiring/SKILL.md +45 -47
- package/skills/pikku-wiring/references/channel.md +1 -1
- package/skills/pikku-wiring/references/scheduler.md +1 -1
- package/skills/pikku-wiring/references/trigger.md +24 -1
- 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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
87
|
-
|
|
|
88
|
-
| `ml-*`, `mr-*`
|
|
89
|
-
| `pl-*`, `pr-*`
|
|
90
|
-
| `left-*`, `right-*`
|
|
91
|
-
| `text-left`, `text-right
|
|
92
|
-
| `border-l`, `border-r`
|
|
93
|
-
| `rounded-l`, `rounded-r`
|
|
94
|
-
| `flex-row-reverse`
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
90
|
-
|
|
|
91
|
-
| `--preset <id>`
|
|
92
|
-
| `--structure <id>`
|
|
93
|
-
| `--primary`, `--secondary`, `--accent` | brand colours, hex
|
|
94
|
-
| `--font-heading`, `--font-body`
|
|
95
|
-
| `--page`
|
|
96
|
-
| `--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
|
package/skills/pikku-ui/SKILL.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-ui
|
|
3
3
|
description: >-
|
|
4
|
-
How a screen gets made:
|
|
5
|
-
TRIGGER when:
|
|
6
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
74
|
-
|
|
|
75
|
-
| `wireHTTP`, `defineHTTPRoutes`, `wireHTTPRoutes`
|
|
76
|
-
| `wireChannel`, `defineChannelRoutes`
|
|
77
|
-
| `wireQueueWorker`
|
|
78
|
-
| `wireScheduler`
|
|
79
|
-
| `wireTrigger`, `wireTriggerSource`, `pikkuTriggerFunc`
|
|
80
|
-
| `wireCLI`, `pikkuCLICommand`, `pikkuCLIRender`
|
|
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
|
|
86
|
-
|
|
|
87
|
-
| **HTTP**
|
|
88
|
-
| **Channel**
|
|
89
|
-
| **Realtime**
|
|
90
|
-
| **RPC**
|
|
91
|
-
| **Queue**
|
|
92
|
-
| **Scheduler** | Recurring work on a cron expression
|
|
93
|
-
| **Trigger**
|
|
94
|
-
| **MCP**
|
|
95
|
-
| **CLI**
|
|
96
|
-
| **Gateway**
|
|
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
|
|
108
|
-
|
|
|
109
|
-
| HTTP, Channel, Queue, CLI, MCP | `pikkuFunc` / `pikkuSessionlessFunc` | Ordinary request/response
|
|
110
|
-
| Scheduler
|
|
111
|
-
| Trigger
|
|
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
|
|
149
|
-
|
|
|
150
|
-
| Trigger
|
|
151
|
-
| Queue
|
|
152
|
-
| Scheduler | Depends on the runtime — see below
|
|
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
|
|
164
|
-
|
|
|
165
|
-
| `pikku enable rpc`
|
|
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`
|
|
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 `
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
and
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|