@pikku/skills 0.12.38 → 0.12.39

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.38",
3
+ "version": "0.12.39",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -63,86 +63,61 @@ Worth a scenario each, because they are two different claims: that a mechanic ca
63
63
  *see* the invoices nav item, and that their call to an invoices RPC is *refused*. The
64
64
  second is the one that catches a `permissions` field nobody wired.
65
65
 
66
- ## The clone
66
+ ## The second app
67
67
 
68
68
  ```bash
69
- cp -R apps/app apps/admin
70
- rm -rf apps/admin/node_modules apps/admin/src/paraglide
69
+ pikku new app admin --serves staff --personas manager,mechanic
71
70
  ```
72
71
 
73
- `src/paraglide` is compiled from `messages/` by the Vite plugin on first run;
74
- copying it forward ships one app's compiled strings inside another.
75
-
76
- Then, in order:
77
-
78
- ### 1. `apps/admin/package.json`
79
-
80
- - `name` → `@project/admin`
81
- - `dev` and `preview` ports → `7105`. Every frontend needs its own, or the second
82
- one fails to bind and the dev runner looks like it hung.
83
- - the `--tsBuildInfoFile` path inside the **`tsc` script** → `admin-tsc.tsbuildinfo`.
84
- In this template it is a CLI flag on that script (`tsc --noEmit --incremental
85
- --tsBuildInfoFile node_modules/.cache/app-tsc.tsbuildinfo`), not a
86
- `compilerOptions` entry — `tsconfig.json` needs no change. Left alone, the two
87
- apps fight over one incremental cache and you get type errors that vanish on a
88
- clean build: an hour of debugging for a one-word edit.
89
-
90
- ### 2. `pikkufabric.config.json`
91
-
92
- This file is the source of truth for what apps exist, and it is read whether or
93
- not you ever deploy to Fabric.
94
-
95
- ```json
96
- {
97
- "projectId": "__PROJECT_ID__",
98
- "frontends": {
99
- "app": {
100
- "cwd": "apps/app", "primary": true, "deploy": true, "kind": "ssr",
101
- "dev": { "command": ["bun", "run", "dev"], "port": 7104, "healthPath": "/" },
102
- "serves": "tenant",
103
- "personas": ["visitor", "chidi"]
104
- },
105
- "admin": {
106
- "cwd": "apps/admin", "primary": false, "deploy": true, "kind": "ssr",
107
- "dev": { "command": ["bun", "run", "dev"], "port": 7105, "healthPath": "/" },
108
- "serves": "owner",
109
- "personas": ["amina", "bilal"]
110
- }
111
- }
112
- }
113
- ```
114
-
115
- Two things to fix while you are in here, not just add:
116
-
117
- - **The shipped `app` entry may say `["yarn", "dev"]`** while the rest of the
118
- project is driven with bun. Correct it. A frontend that starts under a package
119
- manager the project does not use is a failure that only appears on someone
120
- else's machine.
121
- - **`serves` and `personas` name real personas.** Every persona should appear
122
- under exactly one frontend. A persona listed nowhere is a person with no way
123
- in, and that is a design bug worth seeing now rather than at review.
124
-
125
- ### 3. The dev runner
126
-
127
- `dev.mjs`, under the project's scripts directory, spawns `@project/app` **by
128
- name** and will silently never start your second app — the frontend simply is not
129
- there, with no error to explain it.
130
-
131
- Make it read the `frontends` map and spawn one child per entry, rather than
132
- adding a second hardcoded line. Two sources of truth for "which apps exist" is
133
- the drift this whole file is trying to avoid.
134
-
135
- ### 4. `pikku.config.json` → `environments`
136
-
137
- `local.appUrl` points at one app. Add an environment per frontend (`local`,
138
- `local-admin`) so the browser scenario pass can drive either one. A browser
139
- scenario run against the wrong `appUrl` fails on a missing element and reads like
140
- a UI bug rather than a config one.
141
-
142
- ### 5. Re-run `bun install`
143
-
144
- `apps/*` is already globbed in the root workspaces, so this just links the new
145
- one.
72
+ One command does every step this section used to list by hand: it fetches
73
+ `pikkujs/starter-template`'s `apps/app`, re-points its `package.json` at the
74
+ new name, its own dev/preview port and its own `--tsBuildInfoFile`, stamps
75
+ `app: '<slug>'` onto each named persona in `definePersonas({…})`, adds the
76
+ `frontends` entry, and re-runs `bun install`.
77
+
78
+ **It scaffolds from the starter template, not from the app you already have.**
79
+ Copying the working app drags its screens, routes and nav into an audience that
80
+ never asked for them, and the first hour in the new app goes on deleting
81
+ someone else's product.
82
+
83
+ `--template <source>` scaffolds from something else — any giget source, or a
84
+ path inside the repo for an offline or vendored copy. `--install false` skips
85
+ the install when you are batching several.
86
+
87
+ **The `--tsBuildInfoFile` edit is the one that used to bite.** Two apps sharing
88
+ one incremental cache produce type errors that vanish on a clean build: an hour
89
+ of debugging for a one-word edit. It is handled now, but it is why you should
90
+ not copy by hand.
91
+
92
+ ### What it refuses, and why that is the valuable part
93
+
94
+ The scaffolding is five file edits. Getting the audience wrong is a whole
95
+ second app nobody needed, so the command will not create one when:
96
+
97
+ - **`--serves` names a surface.** `dashboard`, `portal`, `admin`, `console`,
98
+ `ui` and friends say nothing — every frontend is an app. Name the people in
99
+ their own word: staff, customer, supplier, patient.
100
+ - **An existing app already serves that audience.** People sharing an audience
101
+ share ONE app and differ by nav and permitted actions. A new app is for a
102
+ group the first app is not for.
103
+ - **A named persona already signs into another app.** A person signs into one
104
+ app; move them out first if they really belong here.
105
+ - **The slug is what the plan calls an app that already exists.** The plan's
106
+ FIRST app is the one the project starts with — only the apps after it get
107
+ created.
108
+ - **A persona is not in `definePersonas({…})`.** The app is built around who
109
+ signs into it, so it is not created for people who do not exist yet.
110
+
111
+ It also repairs its own half-states: a run that died between writing the
112
+ directory and writing the config entry leaves one without the other, and
113
+ neither survives alone, so the next run clears the remains and carries on
114
+ rather than sending you in to do the surgery by hand.
115
+
116
+ ### What it does NOT do
117
+
118
+ It stops after `bun install`. Serving the new app — a reverse proxy, a
119
+ supervisor, a dev runner, a deploy target — belongs to whatever is hosting it.
120
+ On a plain checkout, `bun --filter @project/<slug> dev` is enough.
146
121
 
147
122
  ## Sessions across two origins
148
123
 
@@ -3,16 +3,15 @@ name: pikku-knowledge
3
3
  description: >-
4
4
  Use when writing, reading, reorganising or validating a project's knowledge/ directory — the
5
5
  notes that say what the app is, in the language its users use. Covers the Open Knowledge Format
6
- note (path-as-identity markdown, YAML frontmatter, only `type` required), the sections of the
7
- app-project profile (slices, entities, decisions, questions, wishlist) and the one question each
8
- answers, slice status/entities/gherkin rules, the `resource:` URI scheme tying a note to the
9
- code it is about, the shapes that are NOT a knowledge base, and the `pikku knowledge
10
- validate|index` commands. TRIGGER when: user asks to write down a decision, requirement, entity
11
- or open question; asks what the app does or is; asks about knowledge/, notes, slices,
12
- an index.md, or a diagram, callout or decision block; or hands over a product
13
- brief to record. DO NOT TRIGGER when: user asks what
14
- functions, routes, tables or permissions exist (that is `pikku meta` / `pikku info`, never a
15
- note), or to write a scenario test (use pikku-scenario).
6
+ note (path-as-identity markdown, YAML frontmatter, only `type` required), the app-project
7
+ profile's sections (milestones, entities, decisions, questions, wishlist) and what each answers,
8
+ milestone status/entities/gherkin rules, the `resource:` URI scheme tying a note to its code,
9
+ what is NOT a knowledge base, and `pikku knowledge validate|index`. TRIGGER when: user asks to
10
+ write down a decision, requirement, entity or open question; asks what the app does or is; asks
11
+ about knowledge/, notes, milestones, an index.md, or a diagram, callout or decision block; or
12
+ hands over a product brief to record. DO NOT TRIGGER when: user asks what functions, routes,
13
+ tables or permissions exist (that is `pikku meta` / `pikku info`, never a note), or to write a
14
+ scenario test (use pikku-scenario).
16
15
  installGroups: [core]
17
16
  agent:
18
17
  tools: read, write, edit, bash, grep
@@ -76,7 +75,7 @@ Frontmatter fields:
76
75
 
77
76
  | Field | Meaning |
78
77
  | ------------- | ----------------------------------------------------------------------------------------------------------------------- |
79
- | `type` | **The only required field.** `slice` — or `milestone`, when the project's own `knowledge/index.md` names the section that way; `validate` accepts both, so follow the scaffold rather than this list. Then `entity`, `decision`, `note`, `overview`. Lowercase — gates compare it literally. |
78
+ | `type` | **The only required field.** One of `milestone`, `entity`, `decision`, `note`, `overview`. Lowercase — every gate compares it literally, and `milestone` is the exact string `readMilestones` filters on, so a near-miss is a note no command can see. |
80
79
  | `title` | What to call the note in a listing. Falls back to the first heading, then the filename. |
81
80
  | `description` | One line, used as the note's subtitle in a section index. |
82
81
  | `resource` | Comma-separated `<kind>:<id>` URIs — the code this note is about. See below. |
@@ -92,9 +91,9 @@ Plain markdown links between notes — `[revocation](../decisions/revocation-end
92
91
  ```
93
92
  knowledge/
94
93
  index.md # type: overview — the map
95
- slices/
94
+ milestones/
96
95
  index.md
97
- 01-the-daily-entry.md # type: slice
96
+ 01-the-daily-entry.md # type: milestone
98
97
  entities/
99
98
  index.md
100
99
  entry.md # type: entity
@@ -116,7 +115,7 @@ Each section answers exactly one question, which is what lets a reader find a no
116
115
 
117
116
  | Section | The question it answers |
118
117
  | --------------------- | ------------------------------------------------------------------ |
119
- | `slices/` | What is one buildable piece of this app, and what proves it works? |
118
+ | `milestones/` | What is one buildable piece of this app, and what proves it works? |
120
119
  | `entities/` | What is this thing, in the words users use for it? |
121
120
  | `decisions/` | What was chosen, and what does that rule out? |
122
121
  | `decisions/security/` | Who may do what? |
@@ -125,13 +124,13 @@ Each section answers exactly one question, which is what lets a reader find a no
125
124
 
126
125
  **Create a section the turn you have a note for it** — never a scaffold of empty directories, and never a section without its own `index.md`. A section index says in one line what belongs in it; that sentence is the reason the file exists, so `pikku knowledge index` writes only the note listing and leaves your prose alone.
127
126
 
128
- ## Slices
127
+ ## Milestones
129
128
 
130
- A slice is the one note type that is a piece of _work_ rather than a fact, so it alone carries state and size:
129
+ A milestone is the one note type that is a piece of _work_ rather than a fact, so it alone carries state and size. It lives in `knowledge/milestones/` and nowhere else: `readMilestones` matches on that directory literally, so the same note under another section is invisible to every gate and command below.
131
130
 
132
131
  ````markdown
133
132
  ---
134
- type: slice
133
+ type: milestone
135
134
  title: The daily entry
136
135
  description: An owner writes one entry per day, and sees it on the day.
137
136
  status: proposed
@@ -151,9 +150,9 @@ And writing again replaces it rather than adding a second
151
150
  ```
152
151
  ````
153
152
 
154
- - **`status`** is `designing` → `proposed` → `dispatched` → `built`. Nothing else. Every gate compares it literally. `designing` sits BEFORE `proposed`: the slice is written down but must not be built yet, because whoever is being shown its looks has not picked one. Only `proposed` is dispatchable, so the two cannot be one status without a slice being built out from under the person still choosing.
153
+ - **`status`** is `proposed` → `dispatched` → `built`. Nothing else — `MILESTONE_STATUSES` is those three, and every gate compares them literally, so an invented status fails `validate` rather than degrading. Only `proposed` is dispatchable. A profile may add one of its own ahead of `proposed` for work that is written down but must not be built yet; that is the profile's to define and validate, not core's.
155
154
  - **`statusAt:` and `attempts:` are bookkeeping, not content — never hand-edit them.** A loop driving this base writes both. `statusAt:` is stamped by whatever moved the status, and is what makes "how long has this been building?" answerable; the file's mtime is not the transition time, because a note is edited after dispatch for all sorts of reasons. `attempts:` is `seat@hash` entries recording which seat has already tried to move this note forward, against the content it was trying to move — it is the loop's only brake, and clearing it by hand hands back a budget that exists to stop a note nothing can satisfy being rewritten forever. Rewriting the note's real content refunds that budget on its own, which is the point: an answer that changes the note is what unsticks it.
156
- - **`entities`** lists what the slice touches, **at most three**. Past three it is not one buildable piece — split it.
155
+ - **`entities`** lists what the milestone touches, **at most three**. Past three it is not one buildable piece — split it.
157
156
  - **The scenario is a fenced `gherkin` block, in the third person.** `Given 'owner' has no entry` — never `Given I have no entry`. A quoted word _means a persona_, which is what lets a reader (and a test) tell who is acting. First person hides that, so it is rejected. The console draws the keywords as a column and each quoted persona as a chip, so a first-person scenario is visibly a block with no personas in it.
158
157
 
159
158
  ## Showing it
@@ -231,7 +230,7 @@ These are all things that exist somewhere better, so a note is always the copy t
231
230
  | Do not write | Because it lives in |
232
231
  | ----------------------------------- | ---------------------------------------------------------- |
233
232
  | a `personas/` section | `definePersonas()` in the project's own code |
234
- | a `scenarios/` section | the gherkin block inside the slice it belongs to |
233
+ | a `scenarios/` section | the gherkin block inside the milestone it belongs to |
235
234
  | a `permissions/` section | a decision note under `decisions/security/` |
236
235
  | a list of tables, columns or routes | `pikku meta` — the generated schema _is_ the schema |
237
236
  | a changelog | `CHANGELOG.md` at the repo root |
@@ -250,7 +249,7 @@ pikku knowledge index # refresh every index.md
250
249
  pikku knowledge index --check # report stale indexes without writing (CI gate)
251
250
  ```
252
251
 
253
- `validate` reports: notes with no `type`, a missing `knowledge/index.md`, a section with no `index.md`, notes flat at the root, sections that duplicate what the project already declares, slices with a bad or missing `status`, slices over three entities, slices with no gherkin block or a first-person one, `decision` fences that state no `chosen:` or rule nothing out, and every `resource:` that no longer resolves. Errors fail the command; warnings do not.
252
+ `validate` reports: notes with no `type`, a missing `knowledge/index.md`, a section with no `index.md`, notes flat at the root, sections that duplicate what the project already declares, milestones with a bad or missing `status`, milestones over three entities, milestones with no gherkin block or a first-person one, `decision` fences that state no `chosen:` or rule nothing out, and every `resource:` that no longer resolves. Errors fail the command; warnings do not.
254
253
 
255
254
  `index` rewrites only the block between `<!-- pikku:knowledge-index -->` markers, creating a scaffolded `index.md` for a section that has none. It is idempotent — running it twice changes nothing.
256
255
 
@@ -312,4 +311,4 @@ The exception, and it is narrow: bookkeeping the loop owns. `statusAt:` and `att
312
311
 
313
312
  OKF permits frontmatter fields a reader does not know, and the parser ignores them rather than failing. That is the extension point: a tool layered on Pikku can add its own sections and fields on top of everything above without forking the format.
314
313
 
315
- Fabric is the one that exists. It adds `decisions/design/` — rules about how the app looks and behaves — and a `design:` field on a slice pointing at the design options it was built from. Both are Fabric's to validate; `pikku knowledge validate` passes them through untouched. Everything else in this skill is the same in both.
314
+ Fabric is the one that exists. It adds `decisions/design/` — rules about how the app looks and behaves — a `screens/` section, and a `design:` field on a milestone pointing at the design options it was built from. Both are Fabric's to validate; `pikku knowledge validate` passes them through untouched. Everything else in this skill is the same in both.