@pikku/skills 0.12.38 → 0.12.40
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 +1 -1
- package/package.json +1 -1
- package/skills/pikku-agent/references/agents.md +7 -0
- package/skills/pikku-build/SKILL.md +8 -12
- package/skills/pikku-build/references/app.md +4 -2
- package/skills/pikku-build/references/feature.md +4 -4
- package/skills/pikku-build/references/multi-app.md +51 -76
- package/skills/pikku-build/references/quick.md +1 -1
- package/skills/pikku-knowledge/SKILL.md +21 -22
- package/skills/pikku-report/SKILL.md +39 -20
- package/skills/pikku-workflow/SKILL.md +54 -1
package/package.json
CHANGED
|
@@ -102,6 +102,13 @@ await rpc.agent.resume(runId, { toolCallId, approved })
|
|
|
102
102
|
await rpc.agent.interrupt(runId, 'user' | 'speech' | 'timeout')
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
`rpc.agent` is built from a factory that `@pikku/core/agent` registers when it
|
|
106
|
+
is imported, so the agent runtime lands only in a deployment unit that actually
|
|
107
|
+
holds an agent. A unit that declares one imports it through its own agent file
|
|
108
|
+
and needs nothing extra; a unit that reaches `rpc.agent` while holding no agent
|
|
109
|
+
throws `Agent runtime not available` rather than silently paying ~52 KB for a
|
|
110
|
+
runtime it never uses.
|
|
111
|
+
|
|
105
112
|
`context` is a string injected into the system prompt for this request only —
|
|
106
113
|
use it for upfront state (current org, project, deployment) so the agent stops
|
|
107
114
|
asking the user for identifiers it could have been handed.
|
|
@@ -115,21 +115,17 @@ generated functions through `ref()`.
|
|
|
115
115
|
|
|
116
116
|
A person who has to go hunting for the port assumes the app did not start.
|
|
117
117
|
|
|
118
|
-
##
|
|
118
|
+
## Report what pikku got wrong, as it happens
|
|
119
119
|
|
|
120
120
|
Whenever pikku or a skill costs you time — a command that failed on a fresh
|
|
121
121
|
tree, a skill that described a flag the CLI does not have, generated code you
|
|
122
|
-
had to fix by hand —
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
`"skill"` and `"passage"` when a skill misled you). The `pikku-report` skill
|
|
130
|
-
has the fields. When the CLI is not signed in to Fabric, the report is queued
|
|
131
|
-
locally rather than sent: say so, and that `pikku fabric findings flush` sends
|
|
132
|
-
the queue once they sign in. Do not retry or file it twice.
|
|
122
|
+
had to fix by hand — file it then with `pikku fabric report --stdin`. The
|
|
123
|
+
`pikku-report` skill has the fields. Leave out secrets, tokens and customer data.
|
|
124
|
+
|
|
125
|
+
Findings are held until hand-over. The last thing in the hand-over, after the
|
|
126
|
+
app runs (and is deployed, if they chose to), is `pikku fabric report`: if it
|
|
127
|
+
lists findings and asks, ask the user — Yes, No, Always or Never — and run it
|
|
128
|
+
again with `--consent <answer>`. If it says reporting is off, do not ask.
|
|
133
129
|
|
|
134
130
|
## Who you are talking to
|
|
135
131
|
|
|
@@ -515,9 +515,11 @@ Rules that are not optional:
|
|
|
515
515
|
- Surface errors. No empty catch, no swallowed promise. If a mutation can fail,
|
|
516
516
|
render the failure inline next to the control that triggered it — not a toast.
|
|
517
517
|
- An exposed function with no session and no permission is reachable by anyone
|
|
518
|
-
over `POST /rpc/:rpcName` (PKU574). Either gate it
|
|
518
|
+
over `POST /rpc/:rpcName` (PKU574). Either gate it, drop `expose: true`, or —
|
|
519
|
+
when public is the point — write `auth: false` on it to say so.
|
|
519
520
|
- A public, signed-out read (a homepage's programme, a price list) is a
|
|
520
|
-
`pikkuSessionlessFunc
|
|
521
|
+
`pikkuSessionlessFunc` with `auth: false` written out, which is what keeps
|
|
522
|
+
PKU574 quiet for it. `pikkuFunc` with `auth: false` still answers
|
|
521
523
|
`MissingSessionError` over `/rpc` to a caller with no session.
|
|
522
524
|
- Better Auth already owns the `user`, `session`, `account` and `verification`
|
|
523
525
|
tables. A domain table with one of those names — a class _session_, a drop-in
|
|
@@ -259,14 +259,14 @@ Do not push without explicit confirmation. Do not merge.
|
|
|
259
259
|
When pikku itself is what cost you time — a wrong generated type, a check that
|
|
260
260
|
passed when it should not, a skill that misled you — file it with `pikku fabric
|
|
261
261
|
report`. The `pikku-report` skill owns the ladder, the two kinds, the JSON-on-
|
|
262
|
-
stdin form and the
|
|
262
|
+
stdin form and asking the user at hand-over; read it before filing.
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
network call a build may make
|
|
264
|
+
Filing is permitted here (see **Hard constraints**), and sending what was filed
|
|
265
|
+
is the one network call a build may make, once the user agrees. Nothing is
|
|
266
|
+
written to the repo. Never patch pikku
|
|
266
267
|
itself — not `node_modules`, not a linked checkout — work around it in the app,
|
|
267
268
|
report it, and let the fix happen once.
|
|
268
269
|
|
|
269
|
-
|
|
270
270
|
## Hard constraints
|
|
271
271
|
|
|
272
272
|
The skill's `allowed-tools` does **not** permit:
|
|
@@ -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
|
|
66
|
+
## The second app
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
|
@@ -135,7 +135,7 @@ than they save:
|
|
|
135
135
|
type params, never an inline return type. The schema is the type.
|
|
136
136
|
- Permission checks go in the `permissions` field, never the function body. An
|
|
137
137
|
exposed function with no session and no permission is reachable by anyone over
|
|
138
|
-
`POST /rpc/:rpcName` (PKU574).
|
|
138
|
+
`POST /rpc/:rpcName` (PKU574). If that is the point, write `auth: false` on it.
|
|
139
139
|
- No `process.env` inside a function — use the injected `variables` / `secrets`
|
|
140
140
|
services.
|
|
141
141
|
- A `z.date()` **input** arrives over RPC as an ISO string, not a `Date`.
|
|
@@ -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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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.**
|
|
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
|
-
|
|
94
|
+
milestones/
|
|
96
95
|
index.md
|
|
97
|
-
01-the-daily-entry.md # type:
|
|
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
|
-
| `
|
|
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
|
-
##
|
|
127
|
+
## Milestones
|
|
129
128
|
|
|
130
|
-
A
|
|
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:
|
|
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 `
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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.
|
|
@@ -3,12 +3,11 @@ name: pikku-report
|
|
|
3
3
|
description: >-
|
|
4
4
|
Use when pikku itself cost you time — wrong generated types, a check that passes when it
|
|
5
5
|
should not, output that is quietly wrong, a skill that misled you — or when the user asks you
|
|
6
|
-
to report a framework bug
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
to report a framework bug or file a finding. Owns `pikku fabric report` (a finding is about
|
|
7
|
+
pikku, not the app), the product-vs-harness kinds, the workaround-first ladder, and asking the
|
|
8
|
+
user at hand-over whether to send what was filed. TRIGGER when: the framework fought you,
|
|
9
9
|
codegen produced something broken, a skill told you to run something that does not exist, the
|
|
10
|
-
user says "report this to pikku" / "file a finding"
|
|
11
|
-
was queued and never sent. DO NOT TRIGGER when: the bug is in the app you are building (fix it
|
|
10
|
+
user says "report this to pikku" / "file a finding", or you are handing over a build. DO NOT TRIGGER when: the bug is in the app you are building (fix it
|
|
12
11
|
there), or you are tempted to patch pikku's source (never do that from an app).
|
|
13
12
|
installGroups: [core]
|
|
14
13
|
---
|
|
@@ -19,9 +18,9 @@ A finding is about **pikku**, not about the app you are building. It is how the
|
|
|
19
18
|
framework learns what cost its users time — the bug, the misleading skill, the
|
|
20
19
|
silence where a check should have complained.
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
no top-level report command.
|
|
21
|
+
File each one the moment it happens. Nothing leaves the machine until the user
|
|
22
|
+
says so at hand-over, and nothing is written to the repository. The command is
|
|
23
|
+
spelled `pikku fabric report` — there is no top-level report command.
|
|
25
24
|
|
|
26
25
|
## Report at the moment it happens
|
|
27
26
|
|
|
@@ -62,7 +61,7 @@ baseline noise that was already failing before you started.
|
|
|
62
61
|
- `--kind harness` — a skill misled you: it told you to run something that does
|
|
63
62
|
not exist, described a flag that is spelled differently, or contradicted what
|
|
64
63
|
the CLI actually did. Pass `--skill <name>` and `--passage "<the line or
|
|
65
|
-
|
|
64
|
+
section>"`. This is the most useful kind to file, because it is fixable
|
|
66
65
|
immediately — so file it even when the cost was small.
|
|
67
66
|
|
|
68
67
|
## The command
|
|
@@ -102,24 +101,44 @@ before anything is sent:
|
|
|
102
101
|
Add whichever of these you actually have: `error` (the error's message line,
|
|
103
102
|
verbatim), `repro` (the shortest way to reach it again), `proposal`, `area`,
|
|
104
103
|
`surface`, `cost` (measured if you measured it — "98s vs 20s steady" ranks;
|
|
105
|
-
"slow" does not), `
|
|
106
|
-
`deployTarget`.
|
|
104
|
+
"slow" does not), `deployTarget`.
|
|
107
105
|
|
|
108
106
|
Versions, platform and package manager are read off the installed tree for you.
|
|
109
107
|
Do not pass them and do not ask the user for them.
|
|
110
108
|
|
|
111
|
-
##
|
|
109
|
+
## At hand-over
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
that succeeds, so nothing you file is lost:
|
|
111
|
+
Filing holds the finding on the machine, tied to this build by a run id the CLI
|
|
112
|
+
makes for the checkout. The terminal says `held until hand-over`; carry on.
|
|
116
113
|
|
|
117
|
-
-
|
|
118
|
-
|
|
119
|
-
- `pikku fabric findings clear` discards it.
|
|
114
|
+
The last thing in the hand-over — after the app runs, and is deployed if they
|
|
115
|
+
chose to — is:
|
|
120
116
|
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
```bash
|
|
118
|
+
pikku fabric report
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
With no finding, it lists what this build filed. What happens next depends on
|
|
122
|
+
what the user said before, which the CLI keeps on their machine:
|
|
123
|
+
|
|
124
|
+
- **Always** — every finding was sent the moment you filed it. Tell the user in
|
|
125
|
+
one line.
|
|
126
|
+
- **Never** — nothing was kept. Do not ask and do not mention it.
|
|
127
|
+
- **Nothing saved** — show the user the titles and ask once: _"Send these to the
|
|
128
|
+
Pikku team so they can fix them?"_ — **Yes**, **No**, **Always** or
|
|
129
|
+
**Never**. Then run it again with their answer:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pikku fabric report --consent yes|no|always|never
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Yes sends and No discards what is held now; Always and Never are saved and the
|
|
136
|
+
question is not asked again. If nobody answers, leave them held and say that
|
|
137
|
+
`pikku fabric report` sends them later.
|
|
138
|
+
|
|
139
|
+
Findings are anonymous: no account, no project. The receipt printed when you
|
|
140
|
+
filed each one is exactly what leaves the machine. A send that fails keeps what
|
|
141
|
+
was not sent; do not retry or file it twice.
|
|
123
142
|
|
|
124
143
|
## Never fix pikku itself
|
|
125
144
|
|
|
@@ -159,6 +159,9 @@ export const processOrder = pikkuWorkflowFunc({
|
|
|
159
159
|
// RPC step — run a registered Pikku function as a step (opts: retries, retryDelay, description)
|
|
160
160
|
const result = await workflow.do('Step name', 'rpcFunctionName', { ...data }, { retries: 3, retryDelay: '1s' })
|
|
161
161
|
|
|
162
|
+
// Sub-workflow step — name another workflow instead of an RPC (see "Sub-workflows")
|
|
163
|
+
const onboarded = await workflow.do('Onboard', 'onboardUserWorkflow', { userId })
|
|
164
|
+
|
|
162
165
|
// Inline closure step — immediate execution, cached for replay
|
|
163
166
|
const msg = await workflow.do('Generate', async () => `Welcome, ${data.email}!`)
|
|
164
167
|
|
|
@@ -277,9 +280,59 @@ const users = await Promise.all(
|
|
|
277
280
|
)
|
|
278
281
|
```
|
|
279
282
|
|
|
283
|
+
### Sub-workflows
|
|
284
|
+
|
|
285
|
+
A step whose second argument names a **workflow** rather than an RPC starts that
|
|
286
|
+
workflow as a child run and resolves to its output. There is no separate API —
|
|
287
|
+
it is the same `workflow.do`, and the generated `TypedWorkflow` has an overload
|
|
288
|
+
keyed on `FlattenedWorkflowMap`, so the child's input and output are type-checked
|
|
289
|
+
exactly like an RPC step's.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
export const signupWorkflow = pikkuWorkflowFunc<
|
|
293
|
+
{ email: string },
|
|
294
|
+
{ userId: string }
|
|
295
|
+
>({
|
|
296
|
+
func: async (_services, data, { workflow }) => {
|
|
297
|
+
const user = await workflow.do('Create user', 'createUser', data)
|
|
298
|
+
// runs onboardUserWorkflow as a child run; awaits its output
|
|
299
|
+
await workflow.do('Onboard', 'onboardUserWorkflow', { userId: user.id })
|
|
300
|
+
await workflow.do('Send welcome', 'sendWelcomeEmail', { userId: user.id })
|
|
301
|
+
return { userId: user.id }
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
How the child runs:
|
|
307
|
+
|
|
308
|
+
- **It is its own run.** It gets its own `runId`, its own steps and its own
|
|
309
|
+
history; the parent step records it as `childRunId` and the child's wire
|
|
310
|
+
carries `parentRunId`/`parentStepId`. Inspect the child's steps on the child
|
|
311
|
+
run, not the parent.
|
|
312
|
+
- **Identity is inherited.** The child's wire copies the parent's
|
|
313
|
+
`pikkuUserId`, so the child runs as whoever started the parent.
|
|
314
|
+
- **Inline vs queued follows the deployment.** Without a `queueService` the
|
|
315
|
+
child runs inline and the parent step returns its output directly. With one,
|
|
316
|
+
the child is queued, the parent parks on that step, and the child's
|
|
317
|
+
completion writes the parent step's result and resumes the parent.
|
|
318
|
+
- **Failure propagates.** A child that fails or is cancelled fails the parent
|
|
319
|
+
step (`'Sub-workflow failed'` / `'Sub-workflow was cancelled'` when the child
|
|
320
|
+
left no message). Inline, the step's `retries` start a fresh child run per
|
|
321
|
+
attempt; queued, the child's failure lands on the parent step once and is
|
|
322
|
+
not retried — put retries on the child's own steps instead.
|
|
323
|
+
|
|
324
|
+
Use a sub-workflow when the child is a real orchestration you also start on
|
|
325
|
+
its own, or reuse from several parents. A child that would be a single
|
|
326
|
+
`workflow.do` is a function — call the RPC directly (see the single-RPC rule
|
|
327
|
+
above).
|
|
328
|
+
|
|
329
|
+
To start a workflow **without waiting** for it, that is not a sub-workflow:
|
|
330
|
+
have an RPC step call `rpc.startWorkflow(name, input)`, which returns
|
|
331
|
+
`{ runId }` immediately, and the new run has no parent link.
|
|
332
|
+
|
|
280
333
|
### Graph workflow (DAG)
|
|
281
334
|
|
|
282
|
-
`pikkuWorkflowGraph` derives types from the RPC map — no explicit `input`/`output`. Nodes map `nodeName → Pikku function name
|
|
335
|
+
`pikkuWorkflowGraph` derives types from the RPC map — no explicit `input`/`output`. Nodes map `nodeName → Pikku function name` — or a workflow name, which runs that workflow as a sub-workflow node; `config.<node>.next` lists nodes to run after it (in parallel); `config.<node>.input: (ref) => ...` transforms input using refs to prior node outputs.
|
|
283
336
|
|
|
284
337
|
```typescript
|
|
285
338
|
import { pikkuWorkflowGraph } from '#pikku/workflow/pikku-workflow-types.gen.js'
|