@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
|
@@ -65,7 +65,7 @@ schemas (`yarn pikku meta functions get <id>`) or workflow steps
|
|
|
65
65
|
|
|
66
66
|
**Language rule:** read `metaLocale` in `pikku.config.json` (default `en`). It is
|
|
67
67
|
the language of every `description`, `title` and step `template` you author —
|
|
68
|
-
the meta
|
|
68
|
+
the meta that is read back to the team. It renames nothing:
|
|
69
69
|
functions, components, types, files, tables and columns are English in every
|
|
70
70
|
project, and what the app says to its users is the message catalogue. See
|
|
71
71
|
`pikku-concepts` → _What Language You Write In_.
|
|
@@ -260,9 +260,7 @@ your branch and silently breaks theirs.
|
|
|
260
260
|
|
|
261
261
|
Tell the user the branch name and how to review. Two options:
|
|
262
262
|
|
|
263
|
-
- **Local review:**
|
|
264
|
-
current branch against `main` with pikku-aware structure (added functions,
|
|
265
|
-
new wires, migrations).
|
|
263
|
+
- **Local review:** `git diff main` on the branch.
|
|
266
264
|
- **PR review:** ask before pushing. Once they confirm, `git push -u origin
|
|
267
265
|
feature/<slug>` and surface the PR-create URL.
|
|
268
266
|
|
|
@@ -300,7 +298,7 @@ explanation of why and what would change.
|
|
|
300
298
|
- Stage 2 (intent statement) is plain English, one paragraph.
|
|
301
299
|
- Between stages, give one-line updates: "Discovered 30 functions, http+queue
|
|
302
300
|
in use. Drafting intent..." → "Branch `feature/todos` created, implementing..."
|
|
303
|
-
→ "`pikku all` clean, `tsc` clean, committed. Review
|
|
301
|
+
→ "`pikku all` clean, `tsc` clean, committed. Review with
|
|
304
302
|
`git diff main`."
|
|
305
303
|
- Don't narrate file-by-file. Only surface what's interesting (new patterns,
|
|
306
304
|
judgment calls, things you suppressed).
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Phase 1 — What the app is (§0, §1, §1a, §2)
|
|
2
|
+
|
|
3
|
+
Part of the app workflow (`app.md` is the index and the rules that hold throughout). Work through §0 to §2 in order, before any code.
|
|
4
|
+
|
|
5
|
+
## 0. Bootstrap, before anything else
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bunx --bun pikku bootstrap
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
One command, run once, now — not later when you start building. It wires the
|
|
12
|
+
`#pikku` import alias the generated code depends on. On a fresh scaffold `.pikku/`
|
|
13
|
+
is empty, so **every command that touches codegen fails until this has run** —
|
|
14
|
+
including ones you would reasonably reach for while still planning, like
|
|
15
|
+
`pikku virtual-user list`. Those failures look alarming (`Cannot find module
|
|
16
|
+
'#pikku/workflow/pikku-workflow-types.gen.js'`, `Schema generation failed for 16
|
|
17
|
+
schemas`) and they are nothing but this missing step.
|
|
18
|
+
|
|
19
|
+
`pikku knowledge index` and `knowledge validate` work without it — which is why
|
|
20
|
+
the planning phases below are safe either way.
|
|
21
|
+
|
|
22
|
+
## 1. One more round of questions — then stop asking
|
|
23
|
+
|
|
24
|
+
Ask **three to five** questions that actually change the schema or the screens,
|
|
25
|
+
in one message. Then stop; do not interview the user.
|
|
26
|
+
|
|
27
|
+
- **Who uses it — who are the distinct kinds of people?** Ask this however small
|
|
28
|
+
the app is. The answer becomes §3's virtual users and roles, and you build only the
|
|
29
|
+
roles they name.
|
|
30
|
+
- **What are the two or three core objects?**
|
|
31
|
+
- **What is the main thing someone does on their first visit?** This answer
|
|
32
|
+
becomes the second changeset, not the tenth.
|
|
33
|
+
- **One app or several?** Separate apps on separate hosts, or one app with paths.
|
|
34
|
+
Cheap to answer now, expensive after the routes exist.
|
|
35
|
+
- **What should it look like?** The template ships one theme — "Neutral", a
|
|
36
|
+
deliberately unopinionated monochrome scaffold — and **nothing in the
|
|
37
|
+
open-source toolchain will ever replace it for you.** Accept any of: keep
|
|
38
|
+
Neutral (fine for an internal tool, but say so out loud); a direction in words;
|
|
39
|
+
a reference (brand guide, screenshots, a site whose register they want); or
|
|
40
|
+
their own design agent/prompt, whose output you take as the direction.
|
|
41
|
+
- **Do they want to see the screens before you build them?** Offer it here, in
|
|
42
|
+
this same round, with yes marked recommended, in the words of
|
|
43
|
+
the design reference (see SKILL.md) — "a picture of the main screens so you can say 'yes,
|
|
44
|
+
like that' or 'no, move this'", never "mock" or "wireframe". Behind it is one
|
|
45
|
+
HTML page mocking the main screens, a few minutes of work. On a yes, or no
|
|
46
|
+
answer at all, the design reference (see SKILL.md) owns what to make and what it then binds — the
|
|
47
|
+
approved page becomes source of truth for the screens, and the theme is written
|
|
48
|
+
before it so what they approve is what ships. Only an explicit no skips it.
|
|
49
|
+
- **May I write test records into the system it talks to?** Ask only when the
|
|
50
|
+
app reads a live system through an addon (an ERP, a CRM) and a changeset needs
|
|
51
|
+
data that isn't there yet: an unpaid invoice, a closed ticket. Say what you
|
|
52
|
+
would create and that it will be marked "Test". A no means building those
|
|
53
|
+
screens against their empty states.
|
|
54
|
+
- **What language should the app speak, and what language does the team work
|
|
55
|
+
in?** Two answers, not one — see §1a, which is where they go. Ask only if the
|
|
56
|
+
request is not obviously English; a brief written in English about an English
|
|
57
|
+
product answers both.
|
|
58
|
+
|
|
59
|
+
Skip anything you can decide yourself. If nobody answers, assume one app with
|
|
60
|
+
paths, the roles implied by the request, Neutral, English — and say so.
|
|
61
|
+
|
|
62
|
+
## 1a. Three languages, and you must not collapse them
|
|
63
|
+
|
|
64
|
+
A brief saying "the entire UI is German" is about **one** of these. Getting this
|
|
65
|
+
wrong has already shipped a project that can never add a second language, so
|
|
66
|
+
settle all three explicitly before you write code.
|
|
67
|
+
|
|
68
|
+
| Axis | What it covers | Where it goes |
|
|
69
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
70
|
+
| **Identifiers** | Function, component, type, variable and file names. Database tables and columns. Commit messages. | Nowhere — **always English**, no setting, not negotiable |
|
|
71
|
+
| **Meta** | `description` on functions and steps, `name`/`title` on features and scenarios, step `template`, role and virtual user descriptions | `metaLocale` in `pikku.config.json`, default `en` |
|
|
72
|
+
| **Product UI** | Every string the app shows a user | `messages/<locale>.json`, and `defaultLocale` for what a first-time visitor opens in |
|
|
73
|
+
|
|
74
|
+
**Identifiers are English.** The product's market does not change this and
|
|
75
|
+
neither does `metaLocale`. Identifiers are the surface the generated `#pikku/*`
|
|
76
|
+
clients, `pikku info`, the typed RPC map and the Kysely types all bind to, and
|
|
77
|
+
unlike a string an identifier cannot be translated later — renaming one is a
|
|
78
|
+
migration. A German practice management tool gets `getWorklist`, `case`,
|
|
79
|
+
`event`, not `getUebersicht`, `vorgang`, `ereignis`.
|
|
80
|
+
|
|
81
|
+
**Meta follows `metaLocale`.** Write the team's answer into `pikku.config.json` in
|
|
82
|
+
this phase, before there is any meta to be wrong:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{ "metaLocale": "de" }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Meta is the one part of a project that is read back by a human, so a team
|
|
89
|
+
working in German reads their own functions, features and scenario reports in
|
|
90
|
+
German. Default `en` and do not ask when the
|
|
91
|
+
project is obviously English. **On every later run, read this field first and
|
|
92
|
+
author descriptions, titles and step templates in it** — a project whose
|
|
93
|
+
`metaLocale` you ignored reports half in one language and half in another.
|
|
94
|
+
|
|
95
|
+
**Product UI is the message catalogue.** `messages/<locale>.json` via
|
|
96
|
+
`pikku-i18n`, with `defaultLocale` deciding what a visitor opens in.
|
|
97
|
+
`baseLocale` in `project.inlang/settings.json` **stays `en`**: it names the
|
|
98
|
+
message source, the catalogue every other language is cloned from, so a project
|
|
99
|
+
that repoints it has nothing to translate from and `--add-locale` is broken
|
|
100
|
+
forever.
|
|
101
|
+
|
|
102
|
+
A German medical portal, correctly:
|
|
103
|
+
|
|
104
|
+
```jsonc
|
|
105
|
+
// project.inlang/settings.json
|
|
106
|
+
{ "baseLocale": "en", "locales": ["en", "de"] }
|
|
107
|
+
// apps/app/src/i18n/active.json
|
|
108
|
+
{ "defaultLocale": "de" }
|
|
109
|
+
// pikku.config.json
|
|
110
|
+
{ "metaLocale": "de" }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Record the two non-obvious answers as a `decisions/` note in §2 — neither is
|
|
114
|
+
discoverable from code, and the next agent will otherwise re-derive them wrong.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 2. Write the knowledge graph — before any code
|
|
119
|
+
|
|
120
|
+
`knowledge/` is not documentation you write at the end. It is the record of what
|
|
121
|
+
the app IS, in the words its users use, and it is the one part of the project
|
|
122
|
+
another agent picks up and continues from. **Nothing gets built until there is a
|
|
123
|
+
note saying what it is.**
|
|
124
|
+
|
|
125
|
+
Read `knowledge/index.md` and the `pikku-knowledge` skill, then write the notes
|
|
126
|
+
for what the user just told you. A project whose `knowledge/` is still only the
|
|
127
|
+
shipped index is a project nobody can resume.
|
|
128
|
+
|
|
129
|
+
Four sections, each answering exactly one question:
|
|
130
|
+
|
|
131
|
+
- `entities/` — what a thing IS, in the words users use for it
|
|
132
|
+
- `decisions/` — what was chosen and what that rules out.
|
|
133
|
+
`decisions/security/` for who may reach what, `decisions/design/` for how it
|
|
134
|
+
looks and behaves
|
|
135
|
+
- `questions/` — what you asked and never got an answer to
|
|
136
|
+
- `wishlist/` — what someone wants that nobody has asked you to build
|
|
137
|
+
|
|
138
|
+
Rules that make it a graph rather than a pile of files:
|
|
139
|
+
|
|
140
|
+
- **A note's path is its identity.** Markdown, YAML frontmatter, `type` required.
|
|
141
|
+
Cross-link notes with plain markdown links — that is what makes it a graph.
|
|
142
|
+
- **Create a section the turn you have a note for it**, with its own `index.md`
|
|
143
|
+
written in the same turn. Never scaffold empty directories, and never leave
|
|
144
|
+
notes flat at the root: a `product.md` and a `glossary.md` at `knowledge/` is
|
|
145
|
+
not a knowledge base, and it leaves the project unbuildable.
|
|
146
|
+
- **A note says what the app is, never what is left to do.** No status, no
|
|
147
|
+
todo list: the work is filed as changes in §5, and `pikku knowledge gaps`
|
|
148
|
+
says which notes no change builds yet.
|
|
149
|
+
- **Record only what pikku cannot tell you.** Tables, columns, function
|
|
150
|
+
signatures, routes, wirings, permissions and roles are all discoverable with
|
|
151
|
+
`pikku info` / `pikku meta`. Copying them into a note gives you a second copy
|
|
152
|
+
that goes stale. Knowledge is the why: decisions, constraints, what a thing
|
|
153
|
+
means.
|
|
154
|
+
|
|
155
|
+
Three decisions belong here on day one, because every later choice leans on them
|
|
156
|
+
and none is discoverable from code:
|
|
157
|
+
|
|
158
|
+
- **How the product is split into apps**, and why (§4) — `decisions/`
|
|
159
|
+
- **What each kind of person may reach**, in domain language — `decisions/security/`
|
|
160
|
+
- **What the app should look like** — the direction from §1 (§8) — `decisions/design/`
|
|
161
|
+
|
|
162
|
+
Then keep it honest — both must pass, and `validate` is a real gate:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
bunx --bun pikku knowledge index
|
|
166
|
+
bunx --bun pikku knowledge validate
|
|
167
|
+
```
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Phase 2 — Who it is for, and what it is made of (§3, §4)
|
|
2
|
+
|
|
3
|
+
Part of the app workflow (`app.md` is the index and the rules that hold throughout). Declare the people, then decide the apps.
|
|
4
|
+
|
|
5
|
+
## 3. Declare the people — virtual users and roles
|
|
6
|
+
|
|
7
|
+
The answer to "who uses it" becomes code, in one file:
|
|
8
|
+
`packages/functions/src/virtual-users.ts`. It ships with a single `visitor`; add the
|
|
9
|
+
people the user named, and the roles they imply.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { defineVirtualUsers } from '#pikku/scopes/pikku-virtual-users.gen.js'
|
|
13
|
+
import { defineSystemRole } from '#pikku/scopes'
|
|
14
|
+
|
|
15
|
+
defineSystemRole({
|
|
16
|
+
owner: {
|
|
17
|
+
displayName: 'Owner',
|
|
18
|
+
description: 'Runs their own properties — sees only what they own',
|
|
19
|
+
scopes: [],
|
|
20
|
+
},
|
|
21
|
+
tenant: {
|
|
22
|
+
displayName: 'Tenant',
|
|
23
|
+
description: 'Lives in one unit — sees only their own tenancy',
|
|
24
|
+
scopes: [],
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
defineVirtualUsers({
|
|
29
|
+
visitor: {
|
|
30
|
+
name: 'Visitor',
|
|
31
|
+
jobTitle: 'Synthetic health-check user',
|
|
32
|
+
account: {},
|
|
33
|
+
},
|
|
34
|
+
amina: {
|
|
35
|
+
name: 'Amina',
|
|
36
|
+
jobTitle: 'Property owner',
|
|
37
|
+
personality: 'Checks arrears first, every single time',
|
|
38
|
+
roles: ['owner'],
|
|
39
|
+
account: {},
|
|
40
|
+
},
|
|
41
|
+
bilal: {
|
|
42
|
+
name: 'Bilal',
|
|
43
|
+
jobTitle: 'Property owner',
|
|
44
|
+
personality:
|
|
45
|
+
'A second owner — exists so "you see yours, not theirs" is testable',
|
|
46
|
+
roles: ['owner'],
|
|
47
|
+
account: {},
|
|
48
|
+
},
|
|
49
|
+
chidi: {
|
|
50
|
+
name: 'Chidi',
|
|
51
|
+
jobTitle: 'Tenant',
|
|
52
|
+
personality: 'Reports the boiler, wants to know it was seen',
|
|
53
|
+
roles: ['tenant'],
|
|
54
|
+
account: {},
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- **Keep `visitor`.** The shipped scenarios name `virtualUsers.visitor`, and PKU677
|
|
60
|
+
requires a browser step's virtual user to be a literal `virtualUsers.<name>`. Removing it
|
|
61
|
+
stops `virtualUsers.visitor` type-checking, which fails `pikku all`, and nothing you
|
|
62
|
+
write after that registers.
|
|
63
|
+
- **One `defineVirtualUsers` call for the whole project.** Codegen builds the
|
|
64
|
+
`VirtualUserId` union from it, materialises one scenario virtual user per person, and
|
|
65
|
+
seeds a user row each. A second call site is a second answer to "who uses this
|
|
66
|
+
app".
|
|
67
|
+
- **`roles` is typechecked against `defineSystemRole`.** An undeclared role is a
|
|
68
|
+
build error rather than a runtime surprise.
|
|
69
|
+
- **Never write an email address.** Each is derived from the virtual user id and
|
|
70
|
+
`scenarios.emailDomain` in `pikku.config.json` — `visitor@virtualuser.local`.
|
|
71
|
+
Hand-writing one is how a run signs in as somebody who was never created.
|
|
72
|
+
- **Declare a second person of the same kind** whenever the rule is ownership
|
|
73
|
+
(`bilal` above). "You see yours, not theirs" is not testable with one owner,
|
|
74
|
+
and §7 is where it gets caught.
|
|
75
|
+
- **Build the roles the user's answer produces, no more.** An invented role
|
|
76
|
+
becomes invented screens and invented rules, and it is the user who has to
|
|
77
|
+
live with them.
|
|
78
|
+
- **Roles are what a permission check reads, not where it lives.** The check goes
|
|
79
|
+
in the function's `permissions` field (§6), never in the body. Read the
|
|
80
|
+
`pikku-auth` skill.
|
|
81
|
+
|
|
82
|
+
`pikku virtual-user list` shows who is declared and `pikku roles audit` reports roles
|
|
83
|
+
the database still holds that code no longer declares — both need §0's bootstrap
|
|
84
|
+
to have run, and both are worth a look once it has.
|
|
85
|
+
|
|
86
|
+
**One warning about the scaffold's own notes:** `knowledge/index.md` may claim
|
|
87
|
+
the people live in `pikku.config.json`, put there by a virtual user command.
|
|
88
|
+
That is stale. In this template they live in `virtual-users.ts` as above, and
|
|
89
|
+
`pikku.config.json` carries no virtual users key at all — only `scenarios.emailDomain`.
|
|
90
|
+
Trust the file you can read over the note describing it.
|
|
91
|
+
|
|
92
|
+
## 4. Declare the apps — one API, several frontends
|
|
93
|
+
|
|
94
|
+
### The rule that makes it cheap
|
|
95
|
+
|
|
96
|
+
**One backend, many frontends. Never fork `packages/functions`.**
|
|
97
|
+
|
|
98
|
+
Every app imports the same generated SDK (`@project/functions-sdk`) and calls the
|
|
99
|
+
same RPCs. What differs is which screens exist and what the shell looks like.
|
|
100
|
+
What must NOT differ is the data layer: two copies of a `listProperties` function
|
|
101
|
+
is two places for the permission check to be wrong.
|
|
102
|
+
|
|
103
|
+
**The app split is presentation, not security.** A tenant app that simply never
|
|
104
|
+
renders the arrears screen is not access control — it is a hidden button.
|
|
105
|
+
Security is the `permissions` field on the function, and it holds whether the
|
|
106
|
+
caller arrived from the admin origin, the tenant origin, curl, or the generated
|
|
107
|
+
client. Build the split for clarity, and prove the boundary with a refusal
|
|
108
|
+
scenario in §7.
|
|
109
|
+
|
|
110
|
+
### Choosing the split
|
|
111
|
+
|
|
112
|
+
- **Separate apps on separate hosts** (`admin.example.com`, `portal.example.com`)
|
|
113
|
+
— when the two audiences share almost no screens, when they should not see each
|
|
114
|
+
other's brand register, or when one may later ship independently.
|
|
115
|
+
- **One app with paths** (`/app/admin/*`, `/app/portal/*`) — when they share most
|
|
116
|
+
of the shell and the difference is a handful of screens. Cheaper, and honest:
|
|
117
|
+
two nav trees in one app is still two apps to a user.
|
|
118
|
+
|
|
119
|
+
Either way, write the choice and its reason into `knowledge/decisions/`.
|
|
120
|
+
|
|
121
|
+
### Adding a second frontend — later, not now
|
|
122
|
+
|
|
123
|
+
Recording the decision is Phase 2 work. **Creating the directory is not.**
|
|
124
|
+
Cloning `apps/app` materialises a folder of copied screens, so it belongs to the
|
|
125
|
+
changeset that first needs the second app, not to planning.
|
|
126
|
+
|
|
127
|
+
When you get there, read the multi-app reference (see SKILL.md). It carries the clone, the
|
|
128
|
+
`package.json` edits, the `frontends` map in `pikku.config.json`, the dev-runner
|
|
129
|
+
change that otherwise silently never starts your second app, the per-frontend
|
|
130
|
+
scenario environments, and how sessions behave across two origins.
|
|
131
|
+
|
|
132
|
+
What Phase 2 owes you now is only this: the split, its reason, and who each app
|
|
133
|
+
serves, written into `knowledge/decisions/`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Phase 3 — The plan (§5, §5a)
|
|
2
|
+
|
|
3
|
+
Part of the app workflow (`app.md` is the index and the rules that hold throughout). File the work, and plan each changeset that needs it.
|
|
4
|
+
|
|
5
|
+
## 5. File the work as changes
|
|
6
|
+
|
|
7
|
+
Turn the app into changes on the project's queue — the pikku-changes skill.
|
|
8
|
+
`pikku knowledge gaps` lists every note no change builds yet; file each as one
|
|
9
|
+
or more changes with `pikku changes file`, one commit's worth each, and
|
|
10
|
+
end every body with the gap's `Knowledge:` line so it is not filed twice.
|
|
11
|
+
|
|
12
|
+
What a change is:
|
|
13
|
+
|
|
14
|
+
- **One commit's worth.** "The owner sees this month's arrears" is a change,
|
|
15
|
+
or a few. "Add the database schema" is not; it is a step inside one.
|
|
16
|
+
- **It ends in something a person can do**, in a browser, signed in as a named
|
|
17
|
+
virtual user. If you cannot say what they see when it is done, that is a
|
|
18
|
+
`questions/` note, not a change.
|
|
19
|
+
|
|
20
|
+
If §1's screen mock was made — approved, or drawn because nobody answered —
|
|
21
|
+
every screen on that page belongs to some change, and a screen no change builds
|
|
22
|
+
is a hole in this plan.
|
|
23
|
+
|
|
24
|
+
How they group into changesets, in order:
|
|
25
|
+
|
|
26
|
+
1. **The spine first.** The one object everything else hangs off, and the screen
|
|
27
|
+
that proves the app exists at all.
|
|
28
|
+
2. **Then the loop the user named as "the main thing someone does on their first
|
|
29
|
+
visit."** That answer from §1 is the second changeset, not the tenth.
|
|
30
|
+
3. **Then each audience's own surface**, one at a time. With two apps, finish one
|
|
31
|
+
app's spine before starting the other's — a half-built app in each is worse
|
|
32
|
+
than one working app.
|
|
33
|
+
4. **Refusals ride along with the changeset that creates the thing being
|
|
34
|
+
refused**, never as a "permissions" changeset at the end.
|
|
35
|
+
|
|
36
|
+
**One approval, then build to the end.** Show the picture of the screens and
|
|
37
|
+
the changes together, in one message, as the plan. That is the only approval you
|
|
38
|
+
ask for. Once they approve it, or don't answer, run `pikku changes next --exec <harness>
|
|
39
|
+
--loop`: it hands each changeset to a fresh agent, merges it, and moves on. Post
|
|
40
|
+
one line as each changeset merges, and carry on. Stop only for what is theirs to
|
|
41
|
+
decide: a credential you don't have, spending money, posting in public, deleting
|
|
42
|
+
or overwriting their data, or a finding that changes the plan.
|
|
43
|
+
|
|
44
|
+
## 5a. The technical plan — per changeset, before you build it
|
|
45
|
+
|
|
46
|
+
`pikku changes claim` decides whether a changeset needs a **plan**: one
|
|
47
|
+
that creates or alters a table always does, and the judge decides the rest. The
|
|
48
|
+
plan says what has to exist: the tables, functions, wires, roles, scopes,
|
|
49
|
+
screens and scenarios, split into passes. It is JSON at
|
|
50
|
+
`knowledge/plans/<changeset>.plan.json`, committed on the changeset's branch,
|
|
51
|
+
and `pikku knowledge plan progress` measures the finished build against it.
|
|
52
|
+
|
|
53
|
+
**Read `pikku-architect` and follow it.** The plan is written in its own turn,
|
|
54
|
+
before any of the code it measures exists, and is never edited afterwards to
|
|
55
|
+
match what you ended up building. An item that will not land is deferred with
|
|
56
|
+
its reason — `plan defer` — not quietly rewritten:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pikku knowledge plan schema # the only spec there is
|
|
60
|
+
pikku knowledge plan set <changeset> /tmp/plan.json
|
|
61
|
+
pikku knowledge plan show <changeset> --for-build # what you then build
|
|
62
|
+
```
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Phase 4 — Build (§6, §6a, §6b)
|
|
2
|
+
|
|
3
|
+
Part of the app workflow (`app.md` is the index and the rules that hold throughout). Read this for each changeset. Proving and shipping it are §7-§9, in `phase-5-prove-and-ship.md`.
|
|
4
|
+
|
|
5
|
+
## 6. Build each changeset
|
|
6
|
+
|
|
7
|
+
**Per changeset** — claim it, plan it if the claim says so (§5a), do the six
|
|
8
|
+
steps one commit per change, close it out (§6a), mark its changes done. Do not
|
|
9
|
+
mark the last one done until §6a passes, §7 is green for it _and §7a shows its
|
|
10
|
+
functions covered_; `changes done` refuses a planned changeset's last change
|
|
11
|
+
while the plan is short.
|
|
12
|
+
|
|
13
|
+
1. **Migration.** SQL in `db/sqlite/` at the project root (`db/postgres/` or
|
|
14
|
+
`db/mysql/` when `createConfig` sets `postgresUrl` or `mysqlUrl`), numbered on from the
|
|
15
|
+
ones already there. Apply with `bunx --bun pikku db migrate`, which also
|
|
16
|
+
regenerates the Kysely types your functions import. **Neither `pikku all` nor
|
|
17
|
+
restarting `pikku dev` applies a migration** — so a new column reads as
|
|
18
|
+
`TS2353 … does not exist in type 'InsertExpression<DB, "…">'` on the function
|
|
19
|
+
that writes it. An unapplied migration is the usual cause and the cheapest to
|
|
20
|
+
rule out, so run `db migrate` first — but the same error is what a misspelt
|
|
21
|
+
column or a stale generated type looks like, so if it survives the migration,
|
|
22
|
+
go and read the SQL.
|
|
23
|
+
2. **Seed.** Demo rows in `db/sqlite-dev-seed.sql`. **There is no seed command** —
|
|
24
|
+
`bunx --bun pikku db reset` is the only thing that applies the file, and it
|
|
25
|
+
wipes, migrates and seeds in one go (`--no-seed` stops after the migration,
|
|
26
|
+
for working on an empty state the test data would hide). Because reset always
|
|
27
|
+
arrives at a database it just wiped, **the seed file is plain `INSERT`s** — no
|
|
28
|
+
`ON CONFLICT DO NOTHING`, no `INSERT OR IGNORE`. Nothing ever applies it
|
|
29
|
+
twice, so it never has to defend itself. It is also **local only** — no deploy
|
|
30
|
+
applies it, so anything the app would be broken without in production is
|
|
31
|
+
configuration and belongs in a migration, not here.
|
|
32
|
+
Do this generously and do it now: an empty app demos badly and critiques
|
|
33
|
+
badly, and you cannot judge a screen's hierarchy, overflow, or truncation
|
|
34
|
+
against zero rows. Seed rows each virtual user sees differently — with an ownership
|
|
35
|
+
rule that means seeding rows for the _second_ owner too.
|
|
36
|
+
3. **Functions.** One `pikkuFunc` per `*.function.ts`. Mark it `expose: true` and
|
|
37
|
+
Pikku generates the typed RPC client and the React Query hooks the UI calls;
|
|
38
|
+
you do NOT write an HTTP route for it. Add `wireHTTP` only for a real REST
|
|
39
|
+
shape (a third-party webhook).
|
|
40
|
+
4. **Regenerate:** `bunx --bun pikku all`
|
|
41
|
+
5. **UI.** Pages in `<app>/src/pages/`, one route file each in `<app>/src/routes/`,
|
|
42
|
+
calling functions through the generated `usePikkuQuery` / `usePikkuMutation`
|
|
43
|
+
hooks from `@project/functions-sdk/pikku/api.gen`. One component per `.tsx`
|
|
44
|
+
file. Compose the kit from `@/components/<Name>` rather than hand-rolling
|
|
45
|
+
controls, and add to it where the design reference (see SKILL.md), "The kit is a floor", says to.
|
|
46
|
+
Register the screen in `useNavItems()` — that one file feeds both the
|
|
47
|
+
desktop sidebar and the phone navigation.
|
|
48
|
+
**Read the design reference (see SKILL.md) before you write the first screen.** You commit
|
|
49
|
+
to a design direction there and are then accountable to it — it hands you no
|
|
50
|
+
layouts, because the design is yours to make. What you build here is then
|
|
51
|
+
judged at step 7, at the end of this changeset rather than once at §8, where
|
|
52
|
+
the only affordable fix is a repaint of eight screens.
|
|
53
|
+
6. **Scenario** (§7).
|
|
54
|
+
7. **Look at it.** Screenshot every screen this changeset touched, at both
|
|
55
|
+
widths, with the seed in place, and look at the images. This is a gate, the
|
|
56
|
+
same as the scenario: a changeset whose screens nobody has seen is not built,
|
|
57
|
+
it is unproven at the one layer scenarios cannot reach. the design reference
|
|
58
|
+
carries how to take the shot when no browser tool is wired up, and what to
|
|
59
|
+
look for. Then close it against its plan (§6a) — `pikku knowledge plan
|
|
60
|
+
progress` has to exit zero before the last change is done — and only then
|
|
61
|
+
mark it done, saying in the `--note` what you looked at and what it made you
|
|
62
|
+
change.
|
|
63
|
+
|
|
64
|
+
Rules that are not optional:
|
|
65
|
+
|
|
66
|
+
- A function's input and output types come from its `input:`/`output:` zod
|
|
67
|
+
schemas. Never pass generic type params, never annotate the return type inline.
|
|
68
|
+
The schema is the type. (Generics XOR schemas — never both.)
|
|
69
|
+
- Auth and permission checks go in the `permissions` field, never in the function
|
|
70
|
+
body. This is what makes §4's app split safe.
|
|
71
|
+
- No `process.env` inside a function. Read config through the injected
|
|
72
|
+
`variables` / `secrets` services; `process.env` belongs only in bootstrap. Every
|
|
73
|
+
secret a function reads needs a matching `defineSecret`, or `pikku all` reports
|
|
74
|
+
PKU951 and nobody knows what to provision at deploy.
|
|
75
|
+
- Let the database type your values, via `db/annotations.ts`. A `BOOLEAN` column
|
|
76
|
+
is derived for you. On SQLite the other two are **not** — add them by hand,
|
|
77
|
+
once, and they are typed AND coerced end-to-end:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
export const classifications = {
|
|
81
|
+
payment: {
|
|
82
|
+
paid_at: { kind: 'date' }, // -> Date, not an ISO string
|
|
83
|
+
metadata: { kind: 'json', tsType: 'PaymentMeta' }, // -> parsed object, not unknown
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
A `TIMESTAMP`/`DATETIME`/`DATE` column with no entry types as `string`, and a
|
|
89
|
+
`JSON` column with no entry types as `unknown` (the CLI warns PKU481). Add the
|
|
90
|
+
annotation rather than casting around the generated type. Once the file carries
|
|
91
|
+
manual fields, `db migrate` stops overwriting it.
|
|
92
|
+
|
|
93
|
+
- A `z.date()` on a function's **input** arrives over RPC as an ISO string, not a
|
|
94
|
+
`Date`. Normalise before calling date methods on it (`new Date(value)`), or it
|
|
95
|
+
throws `.getTime is not a function` at runtime — schema validation accepts the
|
|
96
|
+
string without converting it.
|
|
97
|
+
- Every user-facing string is a translation key. Never a hardcoded literal. With
|
|
98
|
+
two apps that means two `messages/` directories; a string used by both belongs
|
|
99
|
+
to whichever app renders it, and duplication beats a shared bundle that couples
|
|
100
|
+
the apps together.
|
|
101
|
+
- Surface errors. No empty catch, no swallowed promise. If a mutation can fail,
|
|
102
|
+
render the failure inline next to the control that triggered it — not a toast.
|
|
103
|
+
- An exposed function with no session and no permission is reachable by anyone
|
|
104
|
+
over `POST /rpc/:rpcName` (PKU574). Either gate it, drop `expose: true`, or —
|
|
105
|
+
when public is the point — write `auth: false` on it to say so.
|
|
106
|
+
- A public, signed-out read (a homepage's programme, a price list) is a
|
|
107
|
+
`pikkuSessionlessFunc` with `auth: false` written out, which is what keeps
|
|
108
|
+
PKU574 quiet for it. `pikkuFunc` with `auth: false` still answers
|
|
109
|
+
`MissingSessionError` over `/rpc` to a caller with no session.
|
|
110
|
+
- Better Auth already owns the `user`, `session`, `account` and `verification`
|
|
111
|
+
tables. A domain table with one of those names — a class _session_, a drop-in
|
|
112
|
+
_session_ — collides in the migration. Name it for the domain instead
|
|
113
|
+
(`evening`, `class_meeting`) and keep the word in the UI copy.
|
|
114
|
+
- The template's `/` redirects to `/app`, so the login screen is what a
|
|
115
|
+
signed-out visitor sees first. Replace `/` with a public homepage and a
|
|
116
|
+
reviewer lands on a site with no way in: link it to the login screen.
|
|
117
|
+
|
|
118
|
+
Before the first run, make sure `.env` at the project root holds the secret
|
|
119
|
+
the local stack needs. `bun run dev` appends it if missing, but check anyway —
|
|
120
|
+
a project scaffolded from an older template, or a `.env` copied in from
|
|
121
|
+
elsewhere, can lack it, and the failure does not name the variable:
|
|
122
|
+
|
|
123
|
+
- `BETTER_AUTH_SECRET` — without it the first sign-up is a 500.
|
|
124
|
+
|
|
125
|
+
Virtual user sign-in needs nothing in `.env`: `pikku dev` creates its own key
|
|
126
|
+
pair and hands the app the public half.
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
grep -q '^BETTER_AUTH_SECRET=' .env 2>/dev/null || echo "BETTER_AUTH_SECRET=$(openssl rand -base64 32)" >> .env
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`.env` is gitignored and local only — never commit it. A deployed stage gets its
|
|
133
|
+
secrets from the hosting platform, not from this file.
|
|
134
|
+
|
|
135
|
+
Then run it:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
bun run prebuild && bun run dev
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
That starts the API on :3000 and every frontend in `frontends`. A
|
|
142
|
+
frontend running against a dead API looks exactly like an app bug, so if every
|
|
143
|
+
request fails, check that both halves came up.
|
|
144
|
+
|
|
145
|
+
**Start the stack through `bun run dev`, not by launching `vite` or `pikku dev`
|
|
146
|
+
yourself.** The frontend needs to reach _your_ API. If you start the
|
|
147
|
+
frontend on its own (say :3000 is taken by another project), point
|
|
148
|
+
`VITE_API_PROXY` at your API: the dev proxy defaults to `http://localhost:3000`,
|
|
149
|
+
so beside another project's server every request goes to the wrong API.
|
|
150
|
+
|
|
151
|
+
The `--bun` in `bunx --bun pikku …` is load-bearing — keep it. Without it the
|
|
152
|
+
CLI's `#!/usr/bin/env node` shebang hands the process to whatever Node is on
|
|
153
|
+
PATH, which fails below Node 24 with `ERR_UNKNOWN_BUILTIN_MODULE: No such
|
|
154
|
+
built-in module: node:sqlite`.
|
|
155
|
+
|
|
156
|
+
Open it, sign up, and click through what you built. **HTTP 200 is not evidence.**
|
|
157
|
+
The pages are client-rendered: the server returns 200 with an empty shell, so a
|
|
158
|
+
page whose component throws still looks fine to `curl`.
|
|
159
|
+
|
|
160
|
+
That click-through is a smoke check, and it is the only thing it is. **Do not
|
|
161
|
+
hand-drive a browser tool in place of a test.** Steering Playwright yourself
|
|
162
|
+
proves a page rendered once, on your machine, in an order only you remember —
|
|
163
|
+
nothing about it re-runs, so the next agent inherits a claim rather than a test,
|
|
164
|
+
and a regression lands silently. When a journey is worth driving through the UI,
|
|
165
|
+
it is worth writing as a browser step on §7's scenario and running
|
|
166
|
+
`pikku scenario run local --spawn --run browser`: same clicks, same assertions,
|
|
167
|
+
in the repo, green or red on every future run.
|
|
168
|
+
|
|
169
|
+
**A host can inject platform services into `pikku dev`** the way a deploy does, with
|
|
170
|
+
`dev.contributors` in `pikku.config.json`:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"dev": {
|
|
175
|
+
"contributors": [
|
|
176
|
+
{ "module": "./dev/email-contributor.ts", "export": "email" }
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The module exports a `PlatformServiceContributor` (`{ name, imports?, emit }`, the
|
|
183
|
+
type `@pikku/deploy` takes). `pikku all` writes `.pikku/pikku-dev-platform-services.gen.ts`
|
|
184
|
+
from them, and `pikku dev` merges what it returns over the local services before
|
|
185
|
+
`createSingletonServices` runs, so a contributed `emailService` beats
|
|
186
|
+
`LocalEmailService` (and the test-mode spy). Nothing is written without
|
|
187
|
+
contributors, `pikku serve` never loads it, and `NODE_ENV=production` ignores it. An import a contributor names by `file://` URL or absolute path is written as the package export that points at it, or as a path relative to `.pikku/`, so the file typechecks under the project's tsconfig as long as the lines a contributor emits are typed.
|
|
188
|
+
|
|
189
|
+
## 6a. Close the changeset against its plan, not against your memory
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
pikku knowledge plan progress <changeset>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
It reads §5a's plan and reconciles it against the generated meta under
|
|
196
|
+
`.pikku/` — the function exists or it does not, the route is wired or it is not,
|
|
197
|
+
the `pikkuScenario` export is there or it is not. Nothing it reports comes from
|
|
198
|
+
what anyone claimed. It exits non-zero while anything in the first pass is
|
|
199
|
+
missing, and `changes done` runs the same check on the last change.
|
|
200
|
+
|
|
201
|
+
Three things it says, and what each one asks of you:
|
|
202
|
+
|
|
203
|
+
- **MISSING** — the first pass owes it and the meta cannot see it. Either build
|
|
204
|
+
it, or, if it genuinely belongs to later work, move it out with a reason on
|
|
205
|
+
the record:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
pikku knowledge plan defer <changeset> function:sendReminder \
|
|
209
|
+
-r "The email service it needs is the next changeset."
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**A deferral is capped at two per plan.** Past that, the plan was wrong and the
|
|
213
|
+
changeset is two changesets — say so rather than deferring again. What you may
|
|
214
|
+
never do is drop the item silently. A merged changeset with deferrals leaves its
|
|
215
|
+
notes `partial`, and `pikku knowledge gaps` files what it left behind.
|
|
216
|
+
|
|
217
|
+
- **PROBLEMS** — something exists but does not do what was planned. A function
|
|
218
|
+
planned as restricted whose meta says `auth: false`; a `cascade` no migration
|
|
219
|
+
declares; a browser scenario that opens a page and asserts it is still on it.
|
|
220
|
+
These are never deferred. Fix the app.
|
|
221
|
+
- **DEFERRED to a later pass** — already accounted for. Reported so it is
|
|
222
|
+
visible, never blocking.
|
|
223
|
+
|
|
224
|
+
Do not edit the plan to match what you built — the plan was written before the
|
|
225
|
+
code on purpose, and rewriting your own denominator afterwards is exactly what
|
|
226
|
+
that order exists to stop.
|
|
227
|
+
|
|
228
|
+
### 6b. Feed the changeset back into the seats
|
|
229
|
+
|
|
230
|
+
Before starting the next changeset, answer two questions out loud:
|
|
231
|
+
|
|
232
|
+
- **What did the plan fail to say?** A field nothing wrote, a promise no function
|
|
233
|
+
could keep, a pass 1 that turned out to be two. That is a `pikku-architect`
|
|
234
|
+
lesson.
|
|
235
|
+
- **What did the build learn the hard way?** Anything that cost a wasted run —
|
|
236
|
+
a stale process, a scenario that only passes once, a diagnostic that turned
|
|
237
|
+
out to be an echo of an earlier one. That is a `pikku-build` lesson.
|
|
238
|
+
|
|
239
|
+
Then edit the skill — **at most one change to each per changeset**, and only for
|
|
240
|
+
something that actually went wrong here. A rule with no incident behind it is a
|
|
241
|
+
guess, and these files are read in full every time: they earn their length by
|
|
242
|
+
naming failures a reader would otherwise repeat. Prefer sharpening an existing
|
|
243
|
+
line to appending a new one, and delete a rule the last few changesets have
|
|
244
|
+
shown to be noise.
|
|
245
|
+
|
|
246
|
+
The gates are the compounding part. A lesson written into a scenario the suite
|
|
247
|
+
runs, or into a check `plan progress` can see, is enforced; the same lesson
|
|
248
|
+
written as prose is a thing the next reader has to remember. Reach for prose
|
|
249
|
+
only when there is nothing to hang a check on.
|