@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
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-scenario
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
via `pikku dev --coverage`. TRIGGER when: user asks about scenarios, testing a Pikku function,
|
|
13
|
-
coverage, e2e flows, browser/UI e2e, health checks, virtual users, or adversarial
|
|
14
|
-
runs against a stage. DO NOT TRIGGER when: user asks about running an existing suite (use
|
|
15
|
-
Bash) or CI config.
|
|
4
|
+
Writing and running Pikku scenarios (pikkuScenario): virtual users drive the app over the real
|
|
5
|
+
transport, so a flow doubles as an e2e test and a stage/production health check. Covers
|
|
6
|
+
scenario.do/expectEventually/expectError/expectService/expectScore, pikkuScenarioStep (Playwright
|
|
7
|
+
browser steps), virtual users and environments in pikku.config.json, VIRTUAL_USER_SIGNING_KEY,
|
|
8
|
+
`pikku scenario`/`pikku virtual-user`, coverage via `pikku dev --coverage`. TRIGGER when: asked
|
|
9
|
+
about scenarios, testing a Pikku function, coverage, e2e or browser flows, health checks, virtual
|
|
10
|
+
users or adversarial stage runs. DO NOT TRIGGER for running an existing suite (use Bash) or CI
|
|
11
|
+
config.
|
|
16
12
|
installGroups: [core]
|
|
17
13
|
---
|
|
18
14
|
|
|
@@ -31,13 +27,13 @@ installGroups: [core]
|
|
|
31
27
|
This skill covers writing and running scenarios end to end. Five topics are one level down, and
|
|
32
28
|
each says when to open it:
|
|
33
29
|
|
|
34
|
-
| Read | For
|
|
35
|
-
| -------------------------------- |
|
|
36
|
-
| `references/steps.md` | Authoring a `pikkuScenarioStep` — intent, witnesses, what a step is given
|
|
37
|
-
| `references/browser.md` | Browser bindings, and locating by message key in a translated app
|
|
30
|
+
| Read | For |
|
|
31
|
+
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| `references/steps.md` | Authoring a `pikkuScenarioStep` — intent, witnesses, what a step is given |
|
|
33
|
+
| `references/browser.md` | Browser bindings, and locating by message key in a translated app |
|
|
38
34
|
| `references/virtual-users.md` | Virtual users vs virtual users, `defineVirtualUsers`, upstream credentials for virtual users, and human sign-in as a virtual user |
|
|
39
|
-
| `references/coverage.md` | Live coverage, filling it, and unit tests for pure logic
|
|
40
|
-
| `references/virtual-user-run.md` | Running a model-driven virtual user against a stage
|
|
35
|
+
| `references/coverage.md` | Live coverage, filling it, and unit tests for pure logic |
|
|
36
|
+
| `references/virtual-user-run.md` | Running a model-driven virtual user against a stage |
|
|
41
37
|
|
|
42
38
|
## What a scenario is
|
|
43
39
|
|
|
@@ -321,6 +317,7 @@ pikku scenario run local --spawn --exclude-tags ai-live
|
|
|
321
317
|
| `--api-url` / `--app-url` | Override the environment's URLs — for a target that only exists at run time |
|
|
322
318
|
| `--trace` | Keep every stack frame on failure (default shows only the project's own) |
|
|
323
319
|
| `--coverage` | Reset/snapshot server coverage per scenario |
|
|
320
|
+
| `--run-id <id>` | Record the run under this id rather than a fresh one, so a caller can follow it |
|
|
324
321
|
|
|
325
322
|
Output is `PASS <name> (<ms>) → <output>` / `FAIL <name> (<ms>): <error>`, then `N/M scenarios passed against '<env>'`. A scenario inside a feature is named `<Feature> › <scenario> <data>`.
|
|
326
323
|
|
|
@@ -358,10 +358,9 @@ is the app's language, not the team's. Ask, or leave it `en`.
|
|
|
358
358
|
|
|
359
359
|
**Where a non-`en` `metaLocale` still shows English, today.** The reporter composes a
|
|
360
360
|
sentence as `<Keyword> <virtual user> <template>` (`composeStepProse`), and the keyword is
|
|
361
|
-
an English literal. The
|
|
362
|
-
UI language; the CLI reporter does not, so `metaLocale: "de"` gives you German step
|
|
361
|
+
an English literal. The CLI reporter does not translate the Given/When/Then keywords, so `metaLocale: "de"` gives you German step
|
|
363
362
|
prose inside an English frame — `Given shopper kauft 1 Äpfel`. Write templates that read
|
|
364
363
|
acceptably in that frame rather than trying to defeat it. A second gap: where a
|
|
365
|
-
function or scenario declares no `title`, the
|
|
366
|
-
**identifier** into
|
|
364
|
+
function or scenario declares no `title`, the label falls back to splitting the
|
|
365
|
+
**identifier** into English words (`toEnglishName`), so under a
|
|
367
366
|
non-`en` `metaLocale` meta is worth authoring rather than leaving to the fallback.
|
|
@@ -47,7 +47,7 @@ harmless but worth knowing before you align it to the surrounding code.
|
|
|
47
47
|
One more thing worth knowing before writing a rich virtual user: **`virtualUser.converse`
|
|
48
48
|
builds its prompt from `name`, `jobTitle`, `personality` and the scenario's
|
|
49
49
|
`task` only.** Fields like `disposition`, `goals` and `roles` are read and
|
|
50
|
-
stored,
|
|
50
|
+
stored, but they do not reach the conversing
|
|
51
51
|
virtual user's instructions. Anything that must shape how someone talks belongs in
|
|
52
52
|
`personality` or in the task.
|
|
53
53
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-seo
|
|
3
3
|
description: >-
|
|
4
|
-
On-page SEO
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
On-page SEO for the app's PUBLIC pages: per-route head() titles and meta descriptions, Open Graph,
|
|
5
|
+
crawlable links/URLs, landing-page JSON-LD, noindex for the logged-in area. TRIGGER when: building
|
|
6
|
+
or reworking a public page (landing, pricing, about, blog), writing page titles or meta tags, or
|
|
7
|
+
asked about SEO, Google, discoverability or social share previews. DO NOT TRIGGER for
|
|
8
|
+
accessibility incl. heading order, landmarks, alt text (use pikku-a11y), logged-in /app screens
|
|
9
|
+
(noindexed; only the one robots rule applies), backend, database or deployment.
|
|
7
10
|
installGroups: [client]
|
|
8
11
|
---
|
|
9
12
|
|
|
@@ -31,7 +34,10 @@ export const Route = createFileRoute('/pricing')({
|
|
|
31
34
|
'Simple per-seat pricing for Acme Scheduling. Start free, upgrade when your team grows — no setup fees, cancel anytime.',
|
|
32
35
|
},
|
|
33
36
|
{ property: 'og:title', content: 'Pricing — Acme Scheduling' },
|
|
34
|
-
{
|
|
37
|
+
{
|
|
38
|
+
property: 'og:description',
|
|
39
|
+
content: 'Simple per-seat pricing. Start free.',
|
|
40
|
+
},
|
|
35
41
|
],
|
|
36
42
|
}),
|
|
37
43
|
component: PricingPage,
|
|
@@ -90,16 +96,18 @@ or offers (fake schema is a Google penalty, not a boost):
|
|
|
90
96
|
|
|
91
97
|
```tsx
|
|
92
98
|
head: () => ({
|
|
93
|
-
meta: [
|
|
94
|
-
/* title + description as above */
|
|
95
|
-
],
|
|
99
|
+
meta: [/* title + description as above */],
|
|
96
100
|
scripts: [
|
|
97
101
|
{
|
|
98
102
|
type: 'application/ld+json',
|
|
99
103
|
children: JSON.stringify({
|
|
100
104
|
'@context': 'https://schema.org',
|
|
101
105
|
'@graph': [
|
|
102
|
-
{
|
|
106
|
+
{
|
|
107
|
+
'@type': 'Organization',
|
|
108
|
+
name: 'Acme Scheduling',
|
|
109
|
+
description: '…',
|
|
110
|
+
},
|
|
103
111
|
{ '@type': 'WebSite', name: 'Acme Scheduling' },
|
|
104
112
|
],
|
|
105
113
|
}),
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-service-backends
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
(use pikku-services), SQL via Kysely (use pikku-kysely), or the Lambda/Cloudflare runtimes
|
|
13
|
-
themselves (use pikku-deploy).
|
|
4
|
+
Picking or wiring a backend for Pikku's core service interfaces: ContentService (S3, B2),
|
|
5
|
+
QueueService (SQS), SecretService (AWS Secrets Manager, Redis, MongoDB), SchemaService (AJV,
|
|
6
|
+
cfworker), ChannelStore, EventHubStore, WorkflowService, SessionStore, AgentRunService (Redis,
|
|
7
|
+
MongoDB). Covers which to choose, silent differences, swallowed failures. TRIGGER when: code uses
|
|
8
|
+
S3Content, B2Content, SQSQueueService, AWSSecrets, Redis*/MongoDB* stores, PikkuMongoDB,
|
|
9
|
+
AjvSchemaService or CFWorkerSchemaService, or asked how to store files, secrets, channel state or
|
|
10
|
+
sessions. DO NOT TRIGGER for defining service factories (use pikku-services), Kysely SQL (use
|
|
11
|
+
pikku-kysely) or the Lambda/Cloudflare runtimes (use pikku-deploy).
|
|
14
12
|
installGroups: [core]
|
|
15
13
|
---
|
|
16
14
|
|
|
@@ -26,17 +24,17 @@ behind the interface.
|
|
|
26
24
|
|
|
27
25
|
## Pick a backend
|
|
28
26
|
|
|
29
|
-
| Interface
|
|
30
|
-
|
|
|
31
|
-
| `ContentService`
|
|
32
|
-
| `QueueService`
|
|
33
|
-
| `SecretService`
|
|
34
|
-
| `SchemaService`
|
|
35
|
-
| `ChannelStore`, `EventHubStore`
|
|
36
|
-
| `PikkuWorkflowService`, `WorkflowRunService`
|
|
37
|
-
| `SessionStore`, `AgentRunService`, `DeploymentService` | Redis, MongoDB
|
|
38
|
-
| `AgentStorageService`, `AgentRunStateService`
|
|
39
|
-
| `LeaseService`
|
|
27
|
+
| Interface | Backends | Package |
|
|
28
|
+
| ------------------------------------------------------ | ---------------------------------------------------------- | ------------------------------------------------------- |
|
|
29
|
+
| `ContentService` | `S3Content`, `B2Content` | `@pikku/aws-services`, `@pikku/backblaze` |
|
|
30
|
+
| `QueueService` | `SQSQueueService` | `@pikku/aws-services` |
|
|
31
|
+
| `SecretService` | `AWSSecrets`, `RedisSecretService`, `MongoDBSecretService` | `@pikku/aws-services`, `@pikku/redis`, `@pikku/mongodb` |
|
|
32
|
+
| `SchemaService` | `AjvSchemaService`, `CFWorkerSchemaService` | `@pikku/schema-ajv`, `@pikku/schema-cfworker` |
|
|
33
|
+
| `ChannelStore`, `EventHubStore` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
|
|
34
|
+
| `PikkuWorkflowService`, `WorkflowRunService` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
|
|
35
|
+
| `SessionStore`, `AgentRunService`, `DeploymentService` | Redis, MongoDB | `@pikku/redis`, `@pikku/mongodb` |
|
|
36
|
+
| `AgentStorageService`, `AgentRunStateService` | MongoDB **only** | `@pikku/mongodb` |
|
|
37
|
+
| `LeaseService` | `RedisLeaseService` | `@pikku/redis` |
|
|
40
38
|
|
|
41
39
|
SQL is the third option for every store interface in that table —
|
|
42
40
|
`KyselyChannelStore`, `KyselyWorkflowService`, `KyselySecretService` and friends
|
|
@@ -79,12 +77,12 @@ wrapper has to be constructed and initialised before any of them.
|
|
|
79
77
|
|
|
80
78
|
Where they diverge:
|
|
81
79
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
| Signing failure
|
|
85
|
-
| `writeFile` memory
|
|
86
|
-
| Client-side upload integrity | Presigned, expires at a fixed 3600s
|
|
87
|
-
| Credential rotation
|
|
80
|
+
| | `S3Content` | `B2Content` |
|
|
81
|
+
| ---------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
82
|
+
| Signing failure | **Fails open** — logs and returns the _unsigned_ URL | Throws |
|
|
83
|
+
| `writeFile` memory | Streams | **Buffers the whole stream** to compute a SHA-1 |
|
|
84
|
+
| Client-side upload integrity | Presigned, expires at a fixed 3600s | `X-Bz-Content-Sha1: do_not_verify` — unverified |
|
|
85
|
+
| Credential rotation | Picked up by the SDK provider chain | Auth is cached for the instance's lifetime — construct a new `B2Content` |
|
|
88
86
|
|
|
89
87
|
The S3 fail-open is the one to design around: on a private CloudFront
|
|
90
88
|
distribution the client gets a 403, and on a public one you have just handed out
|
|
@@ -119,7 +117,7 @@ Swapping them changes behaviour without changing types:
|
|
|
119
117
|
becomes `1` in the wiring layer, not here.
|
|
120
118
|
|
|
121
119
|
Both throw `UnprocessableContentError` (422) on a failed validation, and both
|
|
122
|
-
throw a **bare string** — `Missing validator for <name>` — for a
|
|
120
|
+
throw a **bare string** — `Missing validator for <name>` — for a _missing_
|
|
123
121
|
schema. It is not an `Error`, so `catch (e) { e.message }` reads `undefined`.
|
|
124
122
|
That almost always means codegen did not run.
|
|
125
123
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-services
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
MongoDB (use pikku-service-backends).
|
|
4
|
+
The Pikku service layer: dependency injection with pikkuServices/pikkuWireServices,
|
|
5
|
+
startup/shutdown via pikkuServerLifecycle, config through the secrets, variables and credentials
|
|
6
|
+
services, audit sink and buffer, structured logging. TRIGGER when: code uses
|
|
7
|
+
pikkuServices/pikkuWireServices/pikkuServerLifecycle/defineSecret/defineVariable/defineCredential,
|
|
8
|
+
or asked about services.ts, lifecycle.ts, DI, env vars, secrets, API credentials, audit logging,
|
|
9
|
+
PinoLogger or a built-in service. DO NOT TRIGGER for middleware (use pikku-middleware),
|
|
10
|
+
authentication or permissions (use pikku-auth), or third-party backends such as Redis, S3, MongoDB
|
|
11
|
+
(use pikku-service-backends).
|
|
13
12
|
installGroups: [core]
|
|
14
13
|
---
|
|
15
14
|
|
|
@@ -63,7 +63,7 @@ A service factory should **construct** services, not run startup side effects. S
|
|
|
63
63
|
|
|
64
64
|
```typescript
|
|
65
65
|
// src/lifecycle.ts
|
|
66
|
-
import { pikkuServerLifecycle } from '@pikku/core'
|
|
66
|
+
import { pikkuServerLifecycle } from '@pikku/core/lifecycle'
|
|
67
67
|
import type { SingletonServices } from '../types/application-types.js'
|
|
68
68
|
|
|
69
69
|
export const lifecycle = pikkuServerLifecycle<SingletonServices>({
|
|
@@ -38,7 +38,7 @@ Concept names are the stable IDs. On re-run after code changes, re-extract only
|
|
|
38
38
|
|
|
39
39
|
## How Pikku consumes the blueprint
|
|
40
40
|
|
|
41
|
-
Full mapping table in `references/pikku-mapping.md`. Summary: entities → Kysely migrations + Zod schemas; commands/queries → `pikkuFunc`s; api surfaces → `wireHTTP`; policies → shared permission functions (collapsing duplicated legacy checks); system workflows → `wireScheduler`/`wireQueueWorker`/`pikkuWorkflowFunc`; integrations → injected services with `defineSecret`/`defineCredential`; test-derived scenarios → `
|
|
41
|
+
Full mapping table in `references/pikku-mapping.md`. Summary: entities → Kysely migrations + Zod schemas; commands/queries → `pikkuFunc`s; api surfaces → `wireHTTP`; policies → shared permission functions (collapsing duplicated legacy checks); system workflows → `wireScheduler`/`wireQueueWorker`/`pikkuWorkflowFunc`; integrations → injected services with `defineSecret`/`defineCredential`; test-derived scenarios → `pikkuScenario` / `pikkuFeature` (see `references/tests-to-scenarios.md`). Humans resolve `migration.json.decisionsNeeded` before any generation starts.
|
|
42
42
|
|
|
43
43
|
## How uncertainty is represented
|
|
44
44
|
|
|
@@ -75,6 +75,8 @@ pikku-software-archaeology/
|
|
|
75
75
|
├── references/
|
|
76
76
|
│ ├── blueprint.schema.json # the output contract (JSON Schema)
|
|
77
77
|
│ ├── pikku-mapping.md # blueprint → Pikku primitives
|
|
78
|
+
│ ├── tests-to-scenarios.md # legacy test suite → blueprint scenarios → pikkuScenario
|
|
79
|
+
│ ├── sources/ # per-framework guides: rails, laravel, nestjs, express
|
|
78
80
|
│ ├── second-opinion.md # blueprint → owner-facing report: method, voice, red flags
|
|
79
81
|
│ └── second-opinion-report-template.md # the layered structure to fill in
|
|
80
82
|
├── example/
|
|
@@ -82,3 +84,12 @@ pikku-software-archaeology/
|
|
|
82
84
|
└── scripts/
|
|
83
85
|
└── validate.mjs # schema + cross-file validation (node, no deps)
|
|
84
86
|
```
|
|
87
|
+
|
|
88
|
+
## Backlog: real-app test runs
|
|
89
|
+
|
|
90
|
+
The framework guides in `references/sources/` are written from general knowledge of each framework and have not been run against a real codebase yet. Run the skill on these, scoped to one slice each (not the whole repo), clone into a scratch directory rather than a pikku repo, and fix whatever the guides miss:
|
|
91
|
+
|
|
92
|
+
- **Redmine** (Rails, issue tracker, ~2006): the hardest test of `sources/rails.md` — callbacks, workflow states, plugins. Scope to issues + workflow transitions.
|
|
93
|
+
- **Immich** (NestJS, photo backend): exercises `sources/nestjs.md` — guards, cron/queues, event handlers. Scope to one domain (e.g. albums or sharing).
|
|
94
|
+
|
|
95
|
+
Things to check on each run: implicit events found, validator passes, which grep patterns in the guide come up empty, and how `tests-to-scenarios.md` handles the repo's own suite.
|
|
@@ -75,7 +75,14 @@ Build an inventory before interpreting anything:
|
|
|
75
75
|
- TODO / FIXME / HACK / XXX / deprecated markers — each is a gaps.json candidate.
|
|
76
76
|
- **The test suite** — locate it now, excavate it in Phase 2.
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
**Framework guides.** Once you know the framework, read its guide before extracting. Each lists where intent hides, how its idioms map to blueprint concepts, the traps, and a grep checklist. Read only the ones that apply:
|
|
79
|
+
|
|
80
|
+
- Rails: `references/sources/rails.md`
|
|
81
|
+
- Laravel: `references/sources/laravel.md`
|
|
82
|
+
- NestJS: `references/sources/nestjs.md`
|
|
83
|
+
- Express (also the approach for Koa/Fastify/Hono): `references/sources/express.md`
|
|
84
|
+
|
|
85
|
+
Other ecosystems fall back to this table. Where intent hides, per ecosystem (read these first):
|
|
79
86
|
|
|
80
87
|
| Ecosystem | Highest-yield locations |
|
|
81
88
|
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -91,7 +98,7 @@ Where intent hides, per ecosystem (read these first):
|
|
|
91
98
|
Tests are the closest thing to an executable product spec. For every test file:
|
|
92
99
|
|
|
93
100
|
- `describe`/`context`/`it` names → **scenarios** (attach to the matching workflow in `workflows.json` under `scenarios[]`, with `fromTest` set).
|
|
94
|
-
-
|
|
101
|
+
- Journey harnesses (`pikkuScenario` flows, `.feature` files, Playwright/Capybara journeys, request/feature/e2e specs) are the highest-grade scenario source — they already ARE given/when/outcome sequences; extract them verbatim. Triage every test by kind first: see `references/tests-to-scenarios.md`.
|
|
95
102
|
- Assertions → confirmations of policies and invariants (upgrade their `confidence` to `high`, add the test as evidence).
|
|
96
103
|
- Fixtures/factories → entity attribute shapes and realistic example data.
|
|
97
104
|
- Edge-case tests → business rules that exist **nowhere else in the code** (e.g. "replayed webhook events are idempotent" may only be stated in a test).
|
|
@@ -14,7 +14,7 @@ The `.knowledge/` blueprint is designed so each concept maps onto exactly one Pi
|
|
|
14
14
|
| `workflows.json` kind=user | frontend flows + the commands they chain |
|
|
15
15
|
| `workflows.json` kind=system, with `schedule` | `wireScheduler` entries |
|
|
16
16
|
| `workflows.json` multi-step / checkpointing | `pikkuWorkflowFunc` with one `workflow.do(...)` step per blueprint step |
|
|
17
|
-
| `workflows.json` `scenarios[]` | **`
|
|
17
|
+
| `workflows.json` `scenarios[]` | **`pikkuScenario` + `pikkuFeature` — this is the canonical target** (`pikkuUserFlow` was renamed to scenario in #862). Each blueprint scenario's given/when/outcome maps onto `scenario.given/when/then` steps; group by workflow into one scenario per journey and by domain into a `pikkuFeature`. Pure system workflows (cron sweeps, webhook ingest) are still scenarios: fire them with `scenario.runScheduledTask` or the webhook route, then `expectEventually`. See `tests-to-scenarios.md` |
|
|
18
18
|
| `api.json` | `wireHTTP` routes: keep `path`+`method` for compatibility, point at the mapped command/query func; `auth: none`/capability-URL surfaces get `auth: false` |
|
|
19
19
|
| `api.json` kind=webhook-in | `wireHTTP` with `auth: false` + signature-verification middleware from the integration |
|
|
20
20
|
| `integrations.json` | services in `services.ts` (constructor-injected classes); `configVia` env vars become `defineSecret` / config; per-user credentials become `defineCredential` |
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Source guide: Express (Node.js)
|
|
2
|
+
|
|
3
|
+
Read this when the survey finds `express` in `package.json`. Express has no conventions, so every Express app is its own framework. The same product can be one 3,000-line `server.js` or a layered app that imitates Nest. Your job is to find which, then trace the request path by hand. The same approach works for Koa, Fastify and Hono; adjust the registration syntax.
|
|
4
|
+
|
|
5
|
+
## Detect
|
|
6
|
+
|
|
7
|
+
`express` in dependencies, `app.listen`, `express.Router()`. Note the module system (CJS vs ESM), TypeScript or not, the ORM or query layer (Prisma, Sequelize, Knex, Mongoose, raw `pg`/`mysql2`), the session/auth approach (Passport, JWT, express-session) and the entry point named in `package.json` `main`/`scripts.start`.
|
|
8
|
+
|
|
9
|
+
## Where intent hides
|
|
10
|
+
|
|
11
|
+
Start at the entry file and follow `app.use`. **Order is behavior**: middleware registered before a route applies to it, middleware registered after does not.
|
|
12
|
+
|
|
13
|
+
| Location | Blueprint concept |
|
|
14
|
+
| ------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
15
|
+
| entry file (`app.js`, `server.js`, `index.js`, `src/app.ts`): every `app.use` in order | global middleware chain, raw-body routes, CORS, mounts, architecture constraints |
|
|
16
|
+
| `routes/`, `routers/`, `api/`: `router.get/post/...`, `app.route()` | api surfaces; track the mount prefix (`app.use('/v1/invoices', r)`) |
|
|
17
|
+
| auth middleware (`requireAuth`, `isAdmin`, `passport.authenticate`, `jwt.verify`) | authorization policies; note which routes use it and which do not |
|
|
18
|
+
| handlers/controllers: inline `if (...) return res.status(4xx)` | preconditions and policies (every guard is one) |
|
|
19
|
+
| validation: `joi`, `zod`, `yup`, `express-validator`, `celebrate`, hand-rolled `if (!req.body.x)` | command `input[]` + validation policies |
|
|
20
|
+
| models: Mongoose schemas, Sequelize models, Prisma schema, SQL files, migrations (`knex`, `umzug`), or raw SQL strings | entities, constraints |
|
|
21
|
+
| `services/`, `lib/`, `utils/`, `helpers/` | business logic; the real commands usually live here |
|
|
22
|
+
| `jobs/`, `workers/`, `cron/`, `node-cron`, `agenda`, `bull`/`bullmq`, `setInterval` at module scope | system workflows |
|
|
23
|
+
| webhook routes (`/webhooks/stripe`, signature checks, `express.raw`) | integration ingress; command + signature policy |
|
|
24
|
+
| `socket.io` / `ws` setup | realtime interface |
|
|
25
|
+
| `process.env.*`, `config/`, `dotenv` | integrations, toggles, deployment constraints |
|
|
26
|
+
| `test/`, `__tests__/`, `*.test.js` (supertest/jest/mocha) | scenarios; edge cases not in the code |
|
|
27
|
+
|
|
28
|
+
## Idiom -> blueprint -> Pikku
|
|
29
|
+
|
|
30
|
+
| Express idiom | Blueprint concept | Pikku shortcut |
|
|
31
|
+
| ----------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------ |
|
|
32
|
+
| `router.post('/:id/send', auth, handler)` | command (`SendInvoice`), not "POST /:id/send" | `pikkuFunc` + `wireHTTP` keeping path for compatibility |
|
|
33
|
+
| `router.use(requireAuth)` before a set of routes | authorization policy `enforcedAt` each route in scope | auth by default; list public routes explicitly |
|
|
34
|
+
| `if (resource.userId !== req.user.id) return 403` | ownership policy, repeated per handler | one named permission function, wired everywhere |
|
|
35
|
+
| `req.body` hand-checks / Joi schema | `input[]` + validation policy | Zod schema |
|
|
36
|
+
| `res.status(409)` on duplicate / state conflict | precondition on entity state | guard clause inside the func |
|
|
37
|
+
| `await sendEmail(...)` at the end of a handler | **implicit event consumer** | emit event, consumer in `wireQueueWorker` |
|
|
38
|
+
| `setTimeout`/`setInterval`/`node-cron` in module scope | system workflow (often undocumented) | `wireScheduler` |
|
|
39
|
+
| `emitter.emit/on` (`EventEmitter`) | in-process event (not durable) | EventHub / queue; note the lost-on-crash gap |
|
|
40
|
+
| Mongoose/Sequelize hooks (`pre('save')`, `beforeCreate`) | implicit events / validation | explicit step in command |
|
|
41
|
+
| `express.raw()` / `bodyParser.raw` for one route | deployment constraint (signature verification) | raw-body config on that `wireHTTP` route |
|
|
42
|
+
| `app.use(express.static)` + SPA fallback | frontend hosting | separate deploy / TanStack Start |
|
|
43
|
+
| `res.render` (EJS/Pug/Handlebars) | server-rendered frontend (see SKILL.md note) | TanStack route + `usePikkuQuery` |
|
|
44
|
+
| `req.session` / `req.user` set by Passport | session model | Pikku session |
|
|
45
|
+
|
|
46
|
+
## Traps
|
|
47
|
+
|
|
48
|
+
- **Public by omission.** The unguarded route is the default. Build a table of every route vs which middleware applies and look for ones that have none; the legacy apps that "have auth" often have three unguarded admin endpoints. Each is a gaps.json security entry.
|
|
49
|
+
- **Middleware order bugs.** A route registered before `app.use(auth)` is public even though it sits in the same file. Read order top to bottom, with mounts.
|
|
50
|
+
- **`next(err)` and the error handler** define the real error contract (status codes, shapes). Read the final `app.use((err, req, res, next) => ...)` and record the mapping.
|
|
51
|
+
- **Business logic in handlers.** If no service layer exists, the handler IS the command: its guards, DB writes, side effects and response form one concept. Name it from the effect.
|
|
52
|
+
- **Duplicated handlers.** Copy-pasted routes (`/v1` and `/v2`, or admin and user versions) that drifted. List both, flag the difference as a gap.
|
|
53
|
+
- **Hidden writers.** Several routes and a cron job may all write one status column. Grep the column to find every transition, as with Rails.
|
|
54
|
+
- **Callbacks and fire-and-forget.** `doThing().catch(console.error)` without `await` means a side effect that can fail silently: an invariant with `enforcedBy: nothing`.
|
|
55
|
+
- **Tenancy by hand.** Whether `WHERE tenant_id = req.user.tenantId` appears on every query is the question. Missing on one is a gap.
|
|
56
|
+
- **Mongo and loose schemas.** With schemaless data, the entity shape is spread across writers. Reconstruct attributes from schemas, fixtures and serializers, and say which are optional in practice.
|
|
57
|
+
- **Env-driven branches** (`if (process.env.NODE_ENV === 'production')`, feature flags): record the flag and the behavior difference.
|
|
58
|
+
- **Monolith with many entry points**: `server.js`, `worker.js`, `cron.js`, `scripts/`. Each runnable is a component in `architecture.json`.
|
|
59
|
+
|
|
60
|
+
## Search checklist
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
grep -rnE "app\.use\(|router\.use\(" . # middleware + mounts, in file order
|
|
64
|
+
grep -rnE "(app|router)\.(get|post|put|patch|delete|all|route)\(" --include=*.{js,ts} .
|
|
65
|
+
grep -rnE "status\((401|403|404|409|422)\)" --include=*.{js,ts} . # guards = policies
|
|
66
|
+
grep -rnE "req\.user|req\.session|req\.auth" --include=*.{js,ts} .
|
|
67
|
+
grep -rnE "setInterval|setTimeout\(.*[0-9]{4,}|cron\.schedule|new CronJob|agenda\.define|\.process\(" .
|
|
68
|
+
grep -rnE "express\.raw|bodyParser\.raw|stripe\.webhooks|constructEvent|createHmac" .
|
|
69
|
+
grep -rnE "emit\(|\.on\('" --include=*.{js,ts} .
|
|
70
|
+
grep -rnE "process\.env\.[A-Z_]+" -o --include=*.{js,ts} . | sort | uniq -c | sort -rn
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Confidence notes
|
|
74
|
+
|
|
75
|
+
Behavior in a handler plus a supertest scenario is `high`. A rule that only appears in one route of several similar ones is `medium` (decide whether the others should have it, and record the decision needed). Because there is no declarative structure, expect more `medium` than in Nest, and prefer a short `decisionsNeeded` list over guessing.
|
|
@@ -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.
|