@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
@@ -0,0 +1,77 @@
1
+ # Source guide: Laravel (PHP)
2
+
3
+ Read this when the survey finds `composer.json` with `laravel/framework` and an `artisan` file. Laravel is conventional like Rails, but a lot of behavior is wired by service providers and the event system, so read those before the controllers.
4
+
5
+ ## Detect
6
+
7
+ `artisan`, `bootstrap/app.php`, `routes/`, `app/Http`, `database/migrations`. Check the Laravel version in `composer.lock`: before 11, middleware lives in `app/Http/Kernel.php`; from 11, in `bootstrap/app.php`. Also check for Livewire, Inertia, Filament or Nova, which change what the frontend and admin are.
8
+
9
+ ## Where intent hides
10
+
11
+ | Location | Blueprint concept |
12
+ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
13
+ | `database/migrations/*` (replay in order), `database/schema/*.sql` | entities, attributes, DB-enforced invariants |
14
+ | `app/Models/*`: `$fillable`/`$guarded`, `$casts`, relations, scopes, `booted()` | entities, relationships, tenancy scopes |
15
+ | `app/Observers/*`, model `$dispatchesEvents`, `boot()` hooks | **implicit events** (`created`, `updated`, `deleted`) |
16
+ | `app/Events` + `app/Listeners` + `EventServiceProvider` | explicit events and their consumers (`explicit: true`) |
17
+ | `routes/web.php`, `api.php`, `channels.php`, `console.php` | api surfaces, realtime, CLI |
18
+ | `app/Http/Controllers/**` | commands/queries; invokable controllers are one command each |
19
+ | `app/Http/Requests/*` (FormRequest): `rules()`, `authorize()` | input shape + validation and authorization policies |
20
+ | `app/Policies/*`, `Gate::define` in `AuthServiceProvider` | policies (authorization) |
21
+ | `app/Http/Middleware/*` + middleware groups | cross-cutting policies (auth, tenancy, throttling, locale) |
22
+ | `app/Jobs/*`, `config/queue.php`, `app/Console/Kernel.php` / `routes/console.php` `schedule()` | system workflows, async effects |
23
+ | `app/Console/Commands/*` | operator workflows and CLI interface |
24
+ | `app/Notifications/*`, `app/Mail/*` | event consumers + integrations (channels: mail, Slack, SMS) |
25
+ | `app/Providers/*` | bindings, global scopes, gates, macros: read for hidden wiring |
26
+ | `config/*.php` + `.env.example` | integrations, feature toggles, tenancy mode |
27
+ | `tests/Feature`, `tests/Unit` (Pest/PHPUnit) | scenarios; rules that exist only in tests |
28
+ | `app/Filament/**`, `app/Nova/**`, Livewire `app/Livewire/**` | admin workflows; Livewire components are UI **and** commands |
29
+
30
+ ## Idiom -> blueprint -> Pikku
31
+
32
+ | Laravel idiom | Blueprint concept | Pikku shortcut |
33
+ | ------------------------------------------------------ | -------------------------------------------------- | -------------------------------------------------------- |
34
+ | `Model::created` observer / `$dispatchesEvents` | implicit event, consumers = observer body | emit event; consumer = `wireQueueWorker` |
35
+ | `event(new X)` + `Listener` (`ShouldQueue`) | explicit event + async consumer | EventHub topic + queue worker |
36
+ | `FormRequest::rules()` | validation policy + command `input[]` | Zod schema on `input` |
37
+ | `FormRequest::authorize()`, `$this->authorize()`, `Gate::allows`, `->can()` | authorization policy | one named permission function |
38
+ | `Route::middleware('auth:sanctum', 'can:...')` | auth policy `enforcedAt` the route group | auth middleware + permission |
39
+ | `Policy` class methods (`view`, `update`, `delete`) | per-entity authorization policies | permission functions per action |
40
+ | `dispatch(new Job)` / `Bus::chain` / `Bus::batch` | command effect: async step / multi-step workflow | `wireQueueWorker` / `pikkuWorkflowFunc` |
41
+ | `$schedule->command(...)->dailyAt(...)` | system workflow with `schedule` | `wireScheduler` |
42
+ | `Notification::send` (mail/database/slack) | event consumer + integration | service call in consumer |
43
+ | `Gate::before`, super-admin bypass | invariant exception: **record it** | explicit permission, never a global bypass |
44
+ | global scope (`addGlobalScope`, `SoftDeletes`, tenant trait) | tenancy scoping / soft-delete state | session filter on reads; `archived` state |
45
+ | Eloquent accessors/mutators, `$casts` | derived/normalized attributes | Zod transforms or query mapping |
46
+ | Sanctum / Passport / Fortify / Breeze | `LogInUser` etc. + token issuance | auth middleware / `better-auth` |
47
+ | Livewire component (`wire:click`, public props) | UI route + commands in one class: split them | TanStack route + funcs |
48
+ | Blade + Alpine/Inertia pages | frontend (see SKILL.md server-rendered note) | TanStack route; Alpine behaviors = `custom-logic` candidates |
49
+
50
+ ## Traps
51
+
52
+ - **Observers and model `boot()` are invisible from controllers.** A controller that only calls `$order->save()` may trigger emails, stock updates and audit logs. Read the model's observers before naming the command's effects.
53
+ - **Mass assignment shapes the API.** `$fillable` is the real allowed-input list; `$guarded = []` means any column is writable (a gap if exposed to users).
54
+ - **Service providers run before everything.** Macros, global scopes, gates and route model bindings registered there change behavior across the app.
55
+ - **Route model binding** (`Route::get('/invoices/{invoice}')`) silently adds a lookup + 404 + possibly a scope. Count it as a precondition, and check `Route::bind` customisations.
56
+ - **Queued vs sync.** `ShouldQueue` on a listener/notification changes consistency semantics (eventual vs immediate). Record which.
57
+ - **Duplicated rules between FormRequest, controller and model** are common; list every location in `enforcedAt`, and flag drift as a gap.
58
+ - **Config in `.env` drives behavior** (`FEATURE_X`, `TENANCY_MODE`). Collect every `env(` / `config(` that gates a branch.
59
+ - **Third-party packages carry product behavior** (Spatie `laravel-permission` = roles/permissions, `laravel-medialibrary`, Cashier = billing, `laravel-activitylog` = audit events). Read the package config, not the vendor code, and map to policies/integrations.
60
+ - **Raw `DB::` queries and `DB::statement`** often hold reporting logic and tenancy bypasses. Grep and review each.
61
+
62
+ ## Search checklist
63
+
64
+ ```
65
+ grep -rnE "Observer|\$dispatchesEvents|static::(created|updated|deleted|saving)" app
66
+ grep -rnE "event\(|Event::dispatch|::dispatch\(|dispatch\(new" app
67
+ grep -rnE "Gate::|->authorize\(|->can\(|@can|middleware\('can" app routes
68
+ grep -rnE "addGlobalScope|withoutGlobalScope|SoftDeletes|withTrashed" app
69
+ grep -rnE "DB::(raw|select|statement|table)" app
70
+ grep -rnE "env\(|config\('.*feature" app config
71
+ grep -rn "schedule->" app routes
72
+ cat composer.json | grep -E "spatie|laravel/(cashier|sanctum|passport|fortify|nova)|livewire|filament"
73
+ ```
74
+
75
+ ## Confidence notes
76
+
77
+ A validation in a FormRequest backed by a Feature test is `high`. Behavior reconstructed only from an observer with no test is `medium`. Anything resting on a Livewire component's public properties alone is `low`; confirm through the Blade template and its tests.
@@ -0,0 +1,73 @@
1
+ # Source guide: NestJS
2
+
3
+ Read this when the survey finds `@nestjs/core` in `package.json` and `nest-cli.json`. NestJS is the closest source to Pikku: modules, controllers, guards and providers map almost one-to-one. The risk is not finding things, it is over-mirroring the structure instead of recovering the product.
4
+
5
+ ## Detect
6
+
7
+ `nest-cli.json`, `src/main.ts` (`NestFactory.create`), `*.module.ts`. Check the platform adapter (Express or Fastify), transports (`@nestjs/microservices`, GraphQL, WebSockets) and monorepo mode (`apps/`, `libs/`): each app is a component, each lib may be a shared domain.
8
+
9
+ ## Where intent hides
10
+
11
+ | Location | Blueprint concept |
12
+ | --------------------------------------------------------------------------- | -------------------------------------------------------------------- |
13
+ | `*.module.ts` (imports/providers/exports) | domain candidates; the dependency graph shows ownership |
14
+ | `*.controller.ts`: `@Get/@Post/...`, `@UseGuards`, `@Roles`, `@HttpCode` | api surfaces, command/query split, authorization |
15
+ | `*.service.ts` | the actual command/query logic (controllers are usually thin) |
16
+ | `dto/*.ts` with `class-validator` (`@IsString`, `@Min`), `class-transformer` | command `input[]` + validation policies |
17
+ | `*.entity.ts` (TypeORM), `schema.prisma`, `*.schema.ts` (Mongoose), `*.model.ts` (Sequelize/MikroORM) | entities, relations, constraints |
18
+ | `*.guard.ts`, `@Roles()`/`@Public()` decorators, `APP_GUARD` providers | policies (authorization), including global defaults |
19
+ | `*.pipe.ts`, global `ValidationPipe` options | validation policies (check `whitelist`, `forbidNonWhitelisted`) |
20
+ | `*.interceptor.ts`, `*.filter.ts`, `*.middleware.ts` | cross-cutting behavior: audit, caching, error mapping, tenancy |
21
+ | `@nestjs/schedule` (`@Cron`, `@Interval`), `@nestjs/bull`/`bullmq` (`@Processor`, `@Process`) | system workflows, queue consumers |
22
+ | `@nestjs/event-emitter` (`@OnEvent`, `emitter.emit`), `@nestjs/cqrs` | explicit events and consumers; CQRS gives commands/queries for free |
23
+ | `@nestjs/microservices` (`@MessagePattern`, `@EventPattern`) | event/command ingress from other services |
24
+ | `*.gateway.ts` (`@WebSocketGateway`, `@SubscribeMessage`) | realtime interface |
25
+ | `*.resolver.ts` (GraphQL) | queries/commands via `@Query`/`@Mutation` |
26
+ | `@nestjs/config` + `.env.example`, `config/*.ts` | integrations, toggles |
27
+ | `*.spec.ts` (unit), `test/*.e2e-spec.ts` (supertest) | scenarios; e2e specs are the best source |
28
+
29
+ ## Idiom -> blueprint -> Pikku
30
+
31
+ | NestJS idiom | Blueprint concept | Pikku shortcut |
32
+ | ---------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------- |
33
+ | `@Controller` method + service method | one command or query | one `pikkuFunc`, wired with `wireHTTP` |
34
+ | DTO class + `class-validator` | `input[]` + validation policy | Zod schema |
35
+ | `@UseGuards(AuthGuard, RolesGuard)` / `@Roles` | authorization policy | permission functions / middleware |
36
+ | `APP_GUARD` global guard + `@Public()` opt-out | default-deny policy + exceptions list | auth by default, `auth: false` for the listed routes |
37
+ | `@Injectable` provider | service or domain logic | class in `services.ts` |
38
+ | `@Cron`, `@Interval`, `@Timeout` | system workflow with `schedule` | `wireScheduler` |
39
+ | `@Processor` / `@Process` | queue consumer | `wireQueueWorker` |
40
+ | `emitter.emit('x')` + `@OnEvent('x')` | explicit event + consumer | EventHub topic / queue |
41
+ | CQRS `CommandHandler`, `QueryHandler`, `EventsHandler`, sagas | commands, queries, events, workflow | direct 1:1; sagas become `pikkuWorkflowFunc` |
42
+ | `@Interceptor` (logging/caching/transform) | cross-cutting concern | middleware, only if behavior matters to the product |
43
+ | Exception filter mapping errors to status codes | error contract of the API | keep status/code mapping explicit in the func |
44
+ | Request-scoped provider (`Scope.REQUEST`), `AsyncLocalStorage`, `nestjs-cls` | session / tenant context | Pikku session / services |
45
+ | `@nestjs/swagger` decorators | documented contract (check for drift) | generated from `wireHTTP` + schemas |
46
+
47
+ ## Traps
48
+
49
+ - **Structure is not intent.** Nest makes it easy to produce a service-per-table with CRUD controllers. Do not turn `UsersController.update` into `UpdateUser` unless it is a real business action; look at what the service does (status flips, notifications) and name the command from that.
50
+ - **Services calling services.** Cross-module calls (`this.billing.charge`) are the real dependency graph. Derive domains from entity ownership, not from module folders.
51
+ - **Guards can be bypassed by omission.** Check every controller for a missing `@UseGuards` when the rest have one, and read `APP_GUARD` registrations to see whether the default is secure. Missing coverage is a gaps.json security entry.
52
+ - **Global setup in `main.ts`**: `app.useGlobalPipes`, `useGlobalGuards`, `enableCors`, `setGlobalPrefix`, `enableVersioning`, raw-body config for webhooks. These change every route's behavior and belong in `architecture.json`.
53
+ - **Decorators carry semantics.** `@Transform`, `@Exclude`, `@Expose`, `@SerializeOptions` shape responses; `@Exclude` on a password field is an invariant about never exposing it.
54
+ - **Dynamic modules** (`forRoot`, `forRootAsync`) hide which features are on. Read the module's config call and the env it reads.
55
+ - **ORM hooks** (`@BeforeInsert`, `@AfterUpdate`, TypeORM subscribers, Prisma middleware/extensions) are implicit events like Rails callbacks.
56
+ - **Event emitter is in-process.** `@OnEvent` handlers run in the same process and are lost on crash, unlike real queues. Record that delivery guarantee as an invariant (`enforcedBy: nothing`).
57
+ - **Hybrid apps** (HTTP + microservice + WebSocket in one `main.ts`): each transport is an `interfaces.json` entry.
58
+
59
+ ## Search checklist
60
+
61
+ ```
62
+ grep -rnE "@(Get|Post|Put|Patch|Delete|All)\(" src apps libs
63
+ grep -rnE "@UseGuards|@Roles|@Public|APP_GUARD|@SetMetadata" src apps libs
64
+ grep -rnE "useGlobal(Pipes|Guards|Interceptors|Filters)|enableCors|setGlobalPrefix|rawBody" src apps
65
+ grep -rnE "@(Cron|Interval|Timeout|Process|Processor|OnEvent|EventPattern|MessagePattern|SubscribeMessage)" src apps libs
66
+ grep -rnE "@(BeforeInsert|AfterInsert|BeforeUpdate|AfterUpdate|EventSubscriber)|\$use\(" src apps libs
67
+ grep -rnE "forRootAsync?\(|ConfigService|process\.env" src apps libs
68
+ ls test *.e2e-spec.ts 2>/dev/null
69
+ ```
70
+
71
+ ## Confidence notes
72
+
73
+ A DTO validation exercised by an e2e spec is `high`. A guard-based rule with no test is `medium`. Because Nest's structure is declarative, most extraction is `high`; keep `medium`/`low` for logic buried in service methods that mix concerns and for anything decided by dynamic module config.
@@ -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