packages/cli/skills/pikku-feature/SKILL.md
Drive create-a-feature work for a Pikku project: discover project context, work on a feature branch, implement + verify + commit, and ask the user to review via the diff. TRIGGER when: the user asks to "create a feature", "build a todo app", "add X to my Pikku project", "wire up a new endpoint", or anything that implies turning a natural-language request into Pikku functions/wirings/migrations. DO NOT TRIGGER when: the user asks for a one-off code edit in an existing function, or asks about Pikku concepts (use pikku-concepts).
npx skillsauth add pikkujs/pikku pikku-featureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill as an execution checklist, not reference material.
pikku-meta when available; otherwise run the relevant pikku meta ... --json command and inspect only the focused output you need..pikku, node_modules, vendored packages, or broad build artifacts.pikku-verify or pikku all when functions, wirings, schemas, or generated clients may have changed.End-to-end flow: discover → state intent → branch → implement → verify → commit → hand to reviewer.
There is no plan JSON. The branch + diff IS the contract. The reviewer
sees real, compiled, working code. Apply = merge. Reject = git branch -D.
Run once at the start of every feature request:
yarn pikku meta context --json
This single call returns functions, wires, middleware, permissions, workflows,
capabilities (which wire types are in use), and layout (where new files
should land).
Only fall back to targeted commands when you need full input/output JSON
schemas (yarn pikku meta functions get <id>) or workflow steps
(yarn pikku meta workflows get <id>).
Capability rule: do not introduce new wires of a type whose
capabilities.<type> is false unless the user explicitly asked for it.
Before touching any files, give the user one paragraph stating exactly what you'll do. This is the lightweight "plan" — it is chat, not JSON.
I'll add a
todostable via a new migration insql/, and twopikkuSessionlessFuncs (createTodo,listTodoswithreadonly: true) inpackages/functions/src/functions/. Bothexpose: true, so they'll be reachable via the auto-generated RPC client and React Query hooks — no HTTP wiring needed. No new dependencies. OK to proceed?
Wait for the user to confirm or redirect. They can ask for changes ("use the existing tasks table" / "make it a queue not http") in normal chat — no schema, no JSON, no ceremony.
Non-interactive runs (auto mode, CI, batch jobs): state intent in one paragraph and proceed without waiting. Surface course corrections promptly in the post-implementation report.
After confirmation, ensure the working tree is clean and create a feature
branch off the current default branch (whatever git branch --show-current
returns at the start — main, master, develop, all fine):
git status
git switch -c feature/<short-slug>
If the working tree is dirty, stop and ask — never stash silently or overwrite uncommitted work.
Write the code as a normal human contributor would. Use the project's
existing conventions (look at neighbour files in srcDirectories[0]/functions/
and .../wirings/ for style).
Just write the function with expose: true — that's enough to make it
callable. Pikku auto-generates an RPC client (and React Query hooks if the
project's clientFiles.reactQueryFile is set) from every exposed function.
You do not need an HTTP wiring for callers to reach the function.
Default flow for a feature:
expose: true (and readonly: true for
reads).pikku all — RPC map, fetch client, and React Query hooks are
regenerated. Frontends call useListTodos() / mutation.mutate(...)
without you wiring anything.Add an HTTP wiring only when the feature genuinely needs a specific REST shape (third-party callers, webhooks, REST-conventional URLs). Most in-app features don't.
expose: true for any function called from a frontend or another
service. Without it the RPC client won't generate hooks for it.readonly: true for queries. Mark read functions as readonly: true
on the function config. The runner uses this to enforce read-only sessions
(a write func called under a readonly session is rejected). The RPC layer
also uses it to pick useQuery (cacheable) vs useMutation for client
hooks. Mutations leave readonly unset (or false).kind ⇔ auth coupling for HTTP wirings (when you have one). If the
function is pikkuFunc (session-aware), the HTTP wiring needs
auth: true. pikkuSessionlessFunc ⇒ auth: false. Mismatching is a
hard error (PKU573).GET. Writes →
POST/PUT/PATCH/DELETE per REST conventions.pikkuWorkflowGraph (DSL) over
pikkuWorkflowComplexFunc. mode: 'inline' is sync; 'distributed' is
queue-dispatched.permissions field with a pikkuPermission factory.@pikku/core/errors — NotFoundError,
ConflictError, BadRequestError. Never bare Error.sql/). Use a numbered prefix matching existing files.process.env. Declare them with
wireSecret (sensitive) or wireVariable (non-sensitive) — both with a
zod schema for type-safe access. Read with
services.secrets.getSecretJSON('NAME') or services.variables.get('NAME').
See the pikku-config skill for the full pattern (including
OAuth2 credentials). This applies even in config.ts and singleton
service factories.Some patterns vary by project; read a neighbour file before writing:
consts (CreateTodoInput,
CreateTodoOutput) passed to input/output on the func config — vs
generic-typed config. Schema name must match codegen expectations (the
exported const name = the schema name in generated .gen.json).'#pikku' for pikkuFunc / pikkuSessionlessFunc
etc. Copy what neighbours do.kysely, redis. Look at how an existing function
destructures services from its first arg. Check application-types.d.ts
to see whether services like kysely are typed (Kysely<DB>) or untyped
(Kysely<any>) — that drives whether you can lean on generated DB types
or have to coerce manually.CREATE SCHEMA
(e.g. app.todos). Read the first migration in sql/ to see the
convention; reuse helper functions/triggers (e.g. update_last_updated_at)
rather than redefining them.wireHTTP({ method, route, func, auth }).const routes = defineHTTPRoutes({ auth: false, routes: { fooName: { method: 'post', route: '/foo', func: fooFunc } }}); wireHTTPRoutes(routes).For shared wiring files (e.g. todos.http.ts holding both create and list):
create the file with imports if it doesn't exist; append wire calls and
add missing imports if it does.
Both must complete cleanly for your changes before committing:
yarn pikku all
# Type-check the workspaces you touched:
cd packages/functions && npx tsc --noEmit
Notes on running tsc:
yarn tsc may be a no-op in monorepos that don't define a
tsc script in each workspace. Don't trust an exit-zero from the root if
no actual checking happened — verify by running npx tsc --noEmit in the
package(s) you touched.Trust the exit code, not the stderr noise. yarn pikku all may print
warnings, [PKUxxx] messages, even level: critical log lines, while
still exiting 0 — those are pre-existing project state, not your
problem. Same for meta context --json: it streams logs to stderr that
look scary on a clean baseline. The exit code is the source of truth.
If a command exits non-zero, that's a real failure — fix or stop.
Many real-world projects ship with pre-existing warnings or errors (legacy types, version drift, gen-layer messages). Those are not your problem; do not "fix" them.
To distinguish your errors from baseline:
yarn pikku all 2>&1 | tee /tmp/pikku-before.log
yarn pikku all 2>&1 | tee /tmp/pikku-after.log
diff /tmp/pikku-before.log /tmp/pikku-after.log
A clean diff means your changes introduced no new issues — even if the underlying logs both show pre-existing warnings.
If something genuinely failed because of YOUR change, fix the actual issue.
Do not mask errors with as any, @ts-ignore, or --no-verify. If
you're stuck, surface the failure to the user — don't hand them a broken
branch.
git add -A
git commit -m "feat: <short title>"
Tell the user the branch name and how to review. Two options:
main with pikku-aware structure (added functions,
new wires, migrations).git push -u origin feature/<slug> and surface the PR-create URL.Do not push without explicit confirmation. Do not merge.
The skill's allowed-tools does not permit:
yarn add / npm install / dependency changes (ask the user first)yarn dbmigrate (never run migrations against the real DB during planning)pikku deploy apply (never deploy)If the feature genuinely needs any of these, stop and ask with a clear explanation of why and what would change.
feature/todos created, implementing..."
→ "pikku all clean, tsc clean, committed. Review via console or run
git diff main."documentation
Deprecated — use pikku-middleware instead. Tag middleware (addTagMiddleware) is now documented as a section within the pikku-middleware skill, alongside global HTTP middleware, execution order, and the service-to-service bearer auth pattern.
testing
Use when adding authorization checks to Pikku functions or routes — pikkuPermission, pikkuAuth, per-function permissions, pattern-based permissions, or understanding OR/AND permission logic. TRIGGER when: user wants to restrict who can call a function, check resource ownership, add role-based access, or understand where permission checks belong. DO NOT TRIGGER when: user asks about middleware or request interception (use pikku-middleware), authentication strategies (use pikku-security), or session management.
testing
Use when adding any middleware to a Pikku app — global HTTP middleware, tag-scoped middleware (including service-to-service bearer auth), per-route middleware, session-setting middleware, or understanding middleware execution order and priority. TRIGGER when: user wants middleware on some or all routes, machine-to-machine auth, tag-scoped cross-cutting concerns, global interceptors, or middleware priority/order questions. DO NOT TRIGGER when: user asks about permissions/authorization checks (use pikku-permissions), auth strategies like authBearer/authCookie (use pikku-security), or deployment.
documentation
Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `pikku create`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.