@pikku/cli 0.12.91 → 0.12.93
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/LICENSE +106 -0
- package/console-app/assets/{index-D0HG8q0B.css → index-BYj7ZbUU.css} +1 -1
- package/console-app/assets/{index-CpkvmAGb.js → index-CmAplHjZ.js} +151 -149
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +16 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +15 -0
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +48 -0
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +73 -40
- package/dist/.pikku/function/pikku-functions.gen.js +1 -3
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +2 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +2 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +3 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.d.ts +4 -0
- package/dist/.pikku/scenarios/schemas/register.gen.js +4 -0
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +13 -3
- package/dist/.pikku/schemas/schemas/FabricSecretsListOutput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsSetOutput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/KnowledgeIndexInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeIndexOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeValidateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeValidateOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +2 -8
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +29 -0
- package/dist/src/deploy/analyzer/analyzer.js +22 -5
- package/dist/src/deploy/build-pipeline.js +5 -1
- package/dist/src/fabric/fabric-commands.d.ts +37 -9
- package/dist/src/fabric/fabric-commands.js +12 -0
- package/dist/src/fabric/functions/domains-add.function.d.ts +4 -4
- package/dist/src/fabric/functions/secrets-list.function.d.ts +16 -4
- package/dist/src/fabric/functions/secrets-list.function.js +12 -10
- package/dist/src/fabric/functions/secrets-rotate.function.d.ts +24 -0
- package/dist/src/fabric/functions/secrets-rotate.function.js +36 -0
- package/dist/src/fabric/functions/secrets-set.function.d.ts +8 -4
- package/dist/src/fabric/functions/secrets-set.function.js +18 -7
- package/dist/src/fabric/functions/validate.function.js +16 -7
- package/dist/src/fabric/lib/http.d.ts +7 -1
- package/dist/src/fabric/lib/sealed-box.d.ts +16 -0
- package/dist/src/fabric/lib/sealed-box.js +72 -0
- package/dist/src/functions/commands/dev.js +9 -0
- package/dist/src/functions/commands/knowledge-index.d.ts +30 -0
- package/dist/src/functions/commands/knowledge-index.js +11 -0
- package/dist/src/functions/commands/knowledge-validate.d.ts +33 -0
- package/dist/src/functions/commands/knowledge-validate.js +11 -0
- package/dist/src/functions/commands/new-addon.d.ts +18 -0
- package/dist/src/functions/commands/new-addon.js +40 -24
- package/dist/src/functions/commands/pikku-command-bootstrap.js +1 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +17 -2
- package/dist/src/functions/commands/scenario-browser.js +16 -8
- package/dist/src/functions/commands/scenario-environment.d.ts +32 -0
- package/dist/src/functions/commands/scenario-environment.js +61 -0
- package/dist/src/functions/commands/scenario.d.ts +6 -0
- package/dist/src/functions/commands/scenario.js +14 -9
- package/dist/src/functions/commands/skills.js +27 -61
- package/dist/src/functions/db/db-codegen.d.ts +6 -0
- package/dist/src/functions/db/db-codegen.js +9 -0
- package/dist/src/functions/db/db-migrator.js +14 -0
- package/dist/src/functions/db/local-db.d.ts +8 -0
- package/dist/src/functions/db/local-db.js +24 -2
- package/dist/src/functions/db/migration-identifiers.d.ts +58 -0
- package/dist/src/functions/db/migration-identifiers.js +262 -0
- package/dist/src/functions/db/migration-provenance.d.ts +35 -0
- package/dist/src/functions/db/migration-provenance.js +80 -0
- package/dist/src/functions/db/schema-sql.d.ts +43 -0
- package/dist/src/functions/db/schema-sql.js +135 -0
- package/dist/src/functions/db/sqlite/sqlite-kysely.js +22 -2
- package/dist/src/functions/knowledge/render.d.ts +3 -0
- package/dist/src/functions/knowledge/render.js +66 -0
- package/dist/src/functions/knowledge/schemas.d.ts +51 -0
- package/dist/src/functions/knowledge/schemas.js +25 -0
- package/dist/src/functions/wirings/credentials/serialize-credentials-types.js +8 -1
- package/dist/src/functions/wirings/functions/schemas.js +13 -1
- package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +6 -1
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +10 -0
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +102 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +13 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.js +15 -2
- package/dist/src/functions/wirings/scenarios/scenario-schema-partition.d.ts +33 -0
- package/dist/src/functions/wirings/scenarios/scenario-schema-partition.js +49 -0
- package/dist/src/functions/wirings/secrets/serialize-secrets-types.js +10 -3
- package/dist/src/functions/wirings/variables/serialize-variables-types.js +8 -1
- package/dist/src/functions/workflows/all.workflow.js +2 -7
- package/dist/src/utils/file-writer.js +14 -2
- package/dist/src/utils/pikku-cli-config.d.ts +14 -0
- package/dist/src/utils/pikku-cli-config.js +34 -0
- package/dist/src/utils/remove-legacy-scaffold-file.d.ts +14 -0
- package/dist/src/utils/remove-legacy-scaffold-file.js +23 -0
- package/dist/src/utils/serialize-schemas.d.ts +3 -1
- package/dist/src/utils/serialize-schemas.js +39 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -9
- package/dist/.pikku/schemas/schemas/PikkuScenarioFunctionsOutput.schema.json +0 -1
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.d.ts +0 -1
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.js +0 -31
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.d.ts +0 -10
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.js +0 -104
- package/skills/pikku-addon/SKILL.md +0 -243
- package/skills/pikku-addon/references/addon-package-manifest.md +0 -63
- package/skills/pikku-ai-agent/SKILL.md +0 -231
- package/skills/pikku-ai-vercel/SKILL.md +0 -82
- package/skills/pikku-ai-voice/SKILL.md +0 -88
- package/skills/pikku-audit/SKILL.md +0 -175
- package/skills/pikku-aws/SKILL.md +0 -111
- package/skills/pikku-backblaze/SKILL.md +0 -71
- package/skills/pikku-better-auth/SKILL.md +0 -298
- package/skills/pikku-cli/SKILL.md +0 -198
- package/skills/pikku-cli/references/complete-example.md +0 -82
- package/skills/pikku-concepts/SKILL.md +0 -250
- package/skills/pikku-concepts/references/concept-mapping.md +0 -556
- package/skills/pikku-concepts/references/packages.md +0 -29
- package/skills/pikku-config/SKILL.md +0 -212
- package/skills/pikku-cron/SKILL.md +0 -214
- package/skills/pikku-deploy-azure/SKILL.md +0 -71
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -82
- package/skills/pikku-deploy-express/SKILL.md +0 -86
- package/skills/pikku-deploy-fastify/SKILL.md +0 -73
- package/skills/pikku-deploy-lambda/SKILL.md +0 -110
- package/skills/pikku-deploy-nextjs/SKILL.md +0 -78
- package/skills/pikku-deploy-uws/SKILL.md +0 -88
- package/skills/pikku-deps/SKILL.md +0 -98
- package/skills/pikku-emails/SKILL.md +0 -163
- package/skills/pikku-fabric/SKILL.md +0 -318
- package/skills/pikku-fabric-debug/SKILL.md +0 -112
- package/skills/pikku-feature/SKILL.md +0 -258
- package/skills/pikku-gateway-slack/SKILL.md +0 -115
- package/skills/pikku-http/SKILL.md +0 -220
- package/skills/pikku-http/references/http-options.md +0 -55
- package/skills/pikku-i18n/SKILL.md +0 -137
- package/skills/pikku-info/SKILL.md +0 -100
- package/skills/pikku-jose/SKILL.md +0 -105
- package/skills/pikku-kysely/SKILL.md +0 -219
- package/skills/pikku-machine-auth/SKILL.md +0 -183
- package/skills/pikku-mcp/SKILL.md +0 -241
- package/skills/pikku-middleware/SKILL.md +0 -231
- package/skills/pikku-middleware/references/middleware-patterns.md +0 -61
- package/skills/pikku-mongodb/SKILL.md +0 -105
- package/skills/pikku-n8n-import/SKILL.md +0 -109
- package/skills/pikku-n8n-import/SPEC.md +0 -84
- package/skills/pikku-n8n-import/references/addon-mapping.md +0 -121
- package/skills/pikku-n8n-import/references/code-translation.md +0 -121
- package/skills/pikku-n8n-import/references/loops-and-control.md +0 -87
- package/skills/pikku-paraglide/SKILL.md +0 -117
- package/skills/pikku-permissions/SKILL.md +0 -192
- package/skills/pikku-pino/SKILL.md +0 -79
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-product-second-opinion/SKILL.md +0 -158
- package/skills/pikku-product-second-opinion/example/sample-report.md +0 -90
- package/skills/pikku-product-second-opinion/references/report-template.md +0 -73
- package/skills/pikku-queue/SKILL.md +0 -240
- package/skills/pikku-react/SKILL.md +0 -212
- package/skills/pikku-react-query/SKILL.md +0 -242
- package/skills/pikku-realtime/SKILL.md +0 -236
- package/skills/pikku-realtime/references/other-routes.md +0 -23
- package/skills/pikku-redis/SKILL.md +0 -90
- package/skills/pikku-rpc/SKILL.md +0 -171
- package/skills/pikku-rtl/SKILL.md +0 -219
- package/skills/pikku-scenario/SKILL.md +0 -382
- package/skills/pikku-schedule/SKILL.md +0 -57
- package/skills/pikku-schema-ajv/SKILL.md +0 -62
- package/skills/pikku-schema-cfworker/SKILL.md +0 -63
- package/skills/pikku-security/SKILL.md +0 -108
- package/skills/pikku-services/SKILL.md +0 -248
- package/skills/pikku-services/references/audit-wire-service.md +0 -34
- package/skills/pikku-software-archaeology/README.md +0 -70
- package/skills/pikku-software-archaeology/SKILL.md +0 -186
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +0 -625
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +0 -49
- package/skills/pikku-software-archaeology/scripts/validate.mjs +0 -173
- package/skills/pikku-tag-middleware/SKILL.md +0 -13
- package/skills/pikku-template-clone/SKILL.md +0 -40
- package/skills/pikku-trigger/SKILL.md +0 -181
- package/skills/pikku-versioning/SKILL.md +0 -173
- package/skills/pikku-websocket/SKILL.md +0 -243
- package/skills/pikku-workflow/SKILL.md +0 -172
- package/skills/pikku-workflow/references/workflow-reference.md +0 -63
- package/skills/pikku-workflows-client/SKILL.md +0 -150
- package/skills/pikku-ws/SKILL.md +0 -47
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# How Pikku Consumes a Product Blueprint
|
|
2
|
-
|
|
3
|
-
The `.knowledge/` blueprint is designed so each concept maps onto exactly one Pikku primitive. A generator (or an agent following `pikku-feature`) walks the JSON files in this order:
|
|
4
|
-
|
|
5
|
-
| Blueprint source | Pikku target |
|
|
6
|
-
|---|---|
|
|
7
|
-
| `entities.json` attributes + relationships + constraints | Kysely migrations + generated `DB` types; Zod schemas per entity |
|
|
8
|
-
| `entities.json` states/transitions | a `status` column + transition guards inside the owning commands (or a state-machine helper) |
|
|
9
|
-
| `commands.json` | `pikkuFunc` / `pikkuSessionlessFunc` with `input:` Zod schema built from `input[]`; `preconditions` become guard clauses; name is the camelCased command name (`SendInvoice` → `sendInvoice`) |
|
|
10
|
-
| `queries.json` | `pikkuFunc` reads; `scoping` becomes the mandatory `WHERE` / session filter |
|
|
11
|
-
| `events.json` | EventHub topics (realtime) or queue messages; `consumedBy` become `wireQueueWorker` handlers — implicit events (`explicit: false`) get promoted to real emissions |
|
|
12
|
-
| `policies.json` (authorization) | Pikku `permissions` / middleware; one policy = one named permission function, wired everywhere `enforcedAt` listed — this collapses duplicated legacy checks into a single definition |
|
|
13
|
-
| `policies.json` (validation) | Zod schema refinements on the command's `input` |
|
|
14
|
-
| `workflows.json` kind=user | frontend flows + the commands they chain |
|
|
15
|
-
| `workflows.json` kind=system, with `schedule` | `wireScheduler` entries |
|
|
16
|
-
| `workflows.json` multi-step / checkpointing | `pikkuWorkflowFunc` with one `workflow.do(...)` step per blueprint step |
|
|
17
|
-
| `workflows.json` `scenarios[]` | **`pikkuUserFlow` stories — this is the canonical target.** Each scenario's given/when/outcome maps 1:1 onto a user-flow step sequence; group scenarios by their workflow into one flow per journey. Only scenarios with no user-facing surface (pure system workflows: cron sweeps, webhook ingest) fall back to API/e2e tests |
|
|
18
|
-
| `api.json` | `wireHTTP` routes: keep `path`+`method` for compatibility, point at the mapped command/query func; `auth: none`/capability-URL surfaces get `auth: false` |
|
|
19
|
-
| `api.json` kind=webhook-in | `wireHTTP` with `auth: false` + signature-verification middleware from the integration |
|
|
20
|
-
| `integrations.json` | services in `services.ts` (constructor-injected classes); `configVia` env vars become `wireSecret` / config; per-user credentials become `wireCredential` |
|
|
21
|
-
| `architecture.json` notes | deployment config (ports, raw-body routes, proxy expectations) |
|
|
22
|
-
| `invariants.json` enforcedBy=db-constraint | migration constraints (UNIQUE, CHECK, FK) |
|
|
23
|
-
| `invariants.json` enforcedBy=code-guard/nothing | guard clauses + a test each; `atRiskBecause` entries get a hardening task |
|
|
24
|
-
| `gaps.json` | excluded from generation; `open-product-decision` + `migration.json.decisionsNeeded` go to a human BEFORE generation starts |
|
|
25
|
-
| `migration.json.mappings` | the work plan: one mapping = one migration slice |
|
|
26
|
-
| `interfaces.json` kind=cli | `wireCLI` entrypoints — the CLI commands are the same funcs the routes expose |
|
|
27
|
-
| `interfaces.json` kind=mcp | `wireMCP` — each MCP tool IS a `pikkuFunc` (reuse the command/query funcs; don't author tool duplicates) |
|
|
28
|
-
| `interfaces.json` kind=openapi-rest / sdk | generated, not hand-written: the OpenAPI spec + typed client SDK fall out of the `wireHTTP` routes + codegen |
|
|
29
|
-
| `interfaces.json` kind=websocket-realtime | `pikku-realtime` EventHub topics / channels |
|
|
30
|
-
| `frontend.json` | `apps/app` (TanStack Start) shell: router, `@pikku/mantine` theme, `pikku-react-query` data layer, `better-auth` client — the target stack the legacy UI is rebuilt onto |
|
|
31
|
-
| `frontend-routes.json` | TanStack Router routes under `apps/app/src/routes/**` (thin data containers calling `usePikkuQuery`); `dataFrom` names become the generated hooks; subpath routes for rich detail views |
|
|
32
|
-
| `frontend-components.json` rebuild=`mantine-standard`/`mantine-composition` | components in `packages/components` composed from `@pikku/mantine` — the trivial/straightforward bulk |
|
|
33
|
-
| `frontend-components.json` rebuild=`custom-logic` | the PORT list — each becomes a `packages/components` component that reimplements the bespoke behavior (chart/table/editor); its `dependencies` inform whether the lib is kept or replaced. These are the frontend's real work items |
|
|
34
|
-
| `frontend-components.json` rebuild=`custom-style` | normalize to Mantine/theme tokens; usually deleted-and-recomposed, not ported |
|
|
35
|
-
|
|
36
|
-
## Order of generation
|
|
37
|
-
|
|
38
|
-
1. Human resolves `decisionsNeeded`.
|
|
39
|
-
2. Entities → migrations + types.
|
|
40
|
-
3. Policies → permission functions (before commands, so commands can reference them).
|
|
41
|
-
4. Commands + queries → funcs; api.json → wirings.
|
|
42
|
-
5. Events → topics/queues; system workflows → schedulers/workers/workflows.
|
|
43
|
-
6. Scenarios → tests. Run them against the new implementation; they encode the legacy behavior worth preserving.
|
|
44
|
-
|
|
45
|
-
## Uncertainty handling
|
|
46
|
-
|
|
47
|
-
- `confidence: high` concepts generate directly.
|
|
48
|
-
- `confidence: medium` concepts generate, but are listed for review in the pre-generation report — id, evidence summary, and what is uncertain — rather than carrying a marker comment in the generated code. The report is the review surface; the generated code stays clean.
|
|
49
|
-
- `confidence: low` concepts are NOT generated automatically — they surface in the pre-generation review along with `decisionsNeeded`.
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Validates a .knowledge/ blueprint directory against references/blueprint.schema.json,
|
|
3
|
-
// then runs cross-file referential checks (does every command's domain exist, does every
|
|
4
|
-
// api surface map to a real command/query, ...). Exit 0 = valid, 1 = errors.
|
|
5
|
-
//
|
|
6
|
-
// Usage: node validate.mjs <path-to-.knowledge-dir>
|
|
7
|
-
|
|
8
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
9
|
-
import { join, dirname } from 'node:path';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
|
|
12
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const schemaDoc = JSON.parse(readFileSync(join(here, '..', 'references', 'blueprint.schema.json'), 'utf8'));
|
|
14
|
-
|
|
15
|
-
const dir = process.argv[2];
|
|
16
|
-
if (!dir) { console.error('usage: node validate.mjs <.knowledge dir>'); process.exit(2); }
|
|
17
|
-
|
|
18
|
-
const errors = [];
|
|
19
|
-
const warnings = [];
|
|
20
|
-
|
|
21
|
-
// --- minimal JSON-Schema-subset validator (type, required, properties, items, enum, minItems, pattern, $ref -> $defs) ---
|
|
22
|
-
function resolveRef(ref) {
|
|
23
|
-
const m = /^#\/\$defs\/(\w+)$/.exec(ref);
|
|
24
|
-
if (!m || !schemaDoc.$defs[m[1]]) throw new Error(`unresolvable $ref ${ref}`);
|
|
25
|
-
return schemaDoc.$defs[m[1]];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function check(value, schema, path) {
|
|
29
|
-
if (schema.$ref) schema = { ...resolveRef(schema.$ref), ...schema, $ref: undefined };
|
|
30
|
-
if (schema.enum && !schema.enum.includes(value)) {
|
|
31
|
-
errors.push(`${path}: expected one of [${schema.enum.join(', ')}], got ${JSON.stringify(value)}`);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const t = schema.type;
|
|
35
|
-
if (t === 'object') {
|
|
36
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
37
|
-
errors.push(`${path}: expected object`); return;
|
|
38
|
-
}
|
|
39
|
-
for (const req of schema.required || []) {
|
|
40
|
-
if (!(req in value)) errors.push(`${path}: missing required field "${req}"`);
|
|
41
|
-
}
|
|
42
|
-
for (const [k, v] of Object.entries(value)) {
|
|
43
|
-
if (schema.properties?.[k]) check(v, schema.properties[k], `${path}.${k}`);
|
|
44
|
-
}
|
|
45
|
-
} else if (t === 'array') {
|
|
46
|
-
if (!Array.isArray(value)) { errors.push(`${path}: expected array`); return; }
|
|
47
|
-
if (schema.minItems && value.length < schema.minItems) {
|
|
48
|
-
errors.push(`${path}: needs at least ${schema.minItems} item(s), has ${value.length}`);
|
|
49
|
-
}
|
|
50
|
-
if (schema.items) value.forEach((v, i) => check(v, schema.items, `${path}[${i}]`));
|
|
51
|
-
} else if (t === 'string') {
|
|
52
|
-
if (typeof value !== 'string') { errors.push(`${path}: expected string`); return; }
|
|
53
|
-
if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
|
|
54
|
-
errors.push(`${path}: "${value}" does not match ${schema.pattern}`);
|
|
55
|
-
}
|
|
56
|
-
} else if (t === 'boolean' && typeof value !== 'boolean') {
|
|
57
|
-
errors.push(`${path}: expected boolean`);
|
|
58
|
-
} else if (t === 'number' && typeof value !== 'number') {
|
|
59
|
-
errors.push(`${path}: expected number`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// --- load + per-file validation ---
|
|
64
|
-
// Files marked `x-optional` (the frontend layer) only validate when present, so a
|
|
65
|
-
// backend-only repo does not fail for lacking them.
|
|
66
|
-
const docs = {};
|
|
67
|
-
for (const [filename, fileSchema] of Object.entries(schemaDoc.files)) {
|
|
68
|
-
const p = join(dir, filename);
|
|
69
|
-
if (!existsSync(p)) {
|
|
70
|
-
if (!fileSchema['x-optional']) errors.push(`${filename}: missing`);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
docs[filename] = JSON.parse(readFileSync(p, 'utf8'));
|
|
75
|
-
} catch (e) {
|
|
76
|
-
errors.push(`${filename}: invalid JSON (${e.message})`); continue;
|
|
77
|
-
}
|
|
78
|
-
check(docs[filename], fileSchema, filename);
|
|
79
|
-
}
|
|
80
|
-
if (!existsSync(join(dir, 'blueprint.md'))) errors.push('blueprint.md: missing');
|
|
81
|
-
|
|
82
|
-
// --- cross-file referential checks ---
|
|
83
|
-
if (docs['domains.json'] && docs['commands.json']) {
|
|
84
|
-
const domains = new Set((docs['domains.json'].domains || []).map((d) => d.name));
|
|
85
|
-
const commandNames = new Set((docs['commands.json'].commands || []).map((c) => c.name));
|
|
86
|
-
const queryNames = new Set((docs['queries.json']?.queries || []).map((q) => q.name));
|
|
87
|
-
const eventNames = new Set((docs['events.json']?.events || []).map((e) => e.name));
|
|
88
|
-
|
|
89
|
-
const wantDomain = (owner, d) => {
|
|
90
|
-
if (d && !domains.has(d)) errors.push(`${owner}: domain "${d}" not defined in domains.json`);
|
|
91
|
-
};
|
|
92
|
-
for (const c of docs['commands.json'].commands || []) {
|
|
93
|
-
wantDomain(`commands.json:${c.name}`, c.domain);
|
|
94
|
-
for (const ev of c.eventsProduced || []) {
|
|
95
|
-
if (!eventNames.has(ev)) warnings.push(`commands.json:${c.name} produces "${ev}" which is not in events.json`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
for (const q of docs['queries.json']?.queries || []) wantDomain(`queries.json:${q.name}`, q.domain);
|
|
99
|
-
for (const e of docs['entities.json']?.entities || []) wantDomain(`entities.json:${e.name}`, e.domain);
|
|
100
|
-
for (const ev of docs['events.json']?.events || []) wantDomain(`events.json:${ev.name}`, ev.domain);
|
|
101
|
-
|
|
102
|
-
for (const s of docs['api.json']?.surfaces || []) {
|
|
103
|
-
const { type, name } = s.mapsTo || {};
|
|
104
|
-
if (type === 'command' && !commandNames.has(name)) errors.push(`api.json:${s.method || ''} ${s.path}: maps to unknown command "${name}"`);
|
|
105
|
-
if (type === 'query' && !queryNames.has(name)) errors.push(`api.json:${s.method || ''} ${s.path}: maps to unknown query "${name}"`);
|
|
106
|
-
if (type === 'event-ingress' && !eventNames.has(name)) errors.push(`api.json:${s.method || ''} ${s.path}: event-ingress maps to unknown event "${name}" (state-changing webhooks should map to a command instead)`);
|
|
107
|
-
}
|
|
108
|
-
// every domain's listed concepts should exist
|
|
109
|
-
for (const d of docs['domains.json'].domains || []) {
|
|
110
|
-
for (const c of d.commands || []) if (!commandNames.has(c)) warnings.push(`domains.json:${d.name}: lists command "${c}" not in commands.json`);
|
|
111
|
-
for (const q of d.queries || []) if (!queryNames.has(q)) warnings.push(`domains.json:${d.name}: lists query "${q}" not in queries.json`);
|
|
112
|
-
for (const e of d.events || []) if (!eventNames.has(e)) warnings.push(`domains.json:${d.name}: lists event "${e}" not in events.json`);
|
|
113
|
-
const policyNames = new Set((docs['policies.json']?.policies || []).map((p) => p.name));
|
|
114
|
-
for (const p of d.policies || []) if (!policyNames.has(p)) warnings.push(`domains.json:${d.name}: lists policy "${p}" not in policies.json`);
|
|
115
|
-
}
|
|
116
|
-
// commands with no policies and no preconditions are suspicious for mutating ops
|
|
117
|
-
for (const c of docs['commands.json'].commands || []) {
|
|
118
|
-
if (!(c.policies || []).length && !(c.preconditions || []).length) {
|
|
119
|
-
warnings.push(`commands.json:${c.name}: no policies or preconditions — really unguarded, or missed extraction?`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// --- frontend layer cross-checks (only when the optional frontend files exist) ---
|
|
125
|
-
if (docs['frontend-components.json']) {
|
|
126
|
-
const componentNames = new Set(
|
|
127
|
-
(docs['frontend-components.json'].components || []).map((c) => c.name),
|
|
128
|
-
);
|
|
129
|
-
// routes should reference components that were actually inventoried
|
|
130
|
-
for (const r of docs['frontend-routes.json']?.routes || []) {
|
|
131
|
-
for (const c of r.usesComponents || []) {
|
|
132
|
-
if (!componentNames.has(c)) {
|
|
133
|
-
warnings.push(`frontend-routes.json:${r.path}: uses component "${c}" not in frontend-components.json`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
// a component flagged as needing a port must say WHY (the custom logic), or the
|
|
138
|
-
// port-risk is unactionable
|
|
139
|
-
for (const c of docs['frontend-components.json'].components || []) {
|
|
140
|
-
if (c.rebuild === 'custom-logic' && !c.customLogic) {
|
|
141
|
-
warnings.push(`frontend-components.json:${c.name}: rebuild=custom-logic but no customLogic description — port risk is unactionable`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// data-fetching queries named on routes should resolve to a real query/command
|
|
145
|
-
if (docs['queries.json'] || docs['commands.json']) {
|
|
146
|
-
const known = new Set([
|
|
147
|
-
...(docs['queries.json']?.queries || []).map((q) => q.name),
|
|
148
|
-
...(docs['commands.json']?.commands || []).map((c) => c.name),
|
|
149
|
-
]);
|
|
150
|
-
for (const r of docs['frontend-routes.json']?.routes || []) {
|
|
151
|
-
for (const d of r.dataFrom || []) {
|
|
152
|
-
if (!known.has(d)) {
|
|
153
|
-
warnings.push(`frontend-routes.json:${r.path}: reads "${d}" which is not a known query/command`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// an inconsistent UI with no specific design findings = under-extraction
|
|
161
|
-
// (guarded on frontend.json alone — independent of the component inventory)
|
|
162
|
-
if (docs['frontend.json']) {
|
|
163
|
-
const consistency = docs['frontend.json'].designSystemConsistency;
|
|
164
|
-
const findingCount = (docs['frontend.json'].designFindings || []).length;
|
|
165
|
-
if ((consistency === 'mixed' || consistency === 'ad-hoc') && findingCount === 0) {
|
|
166
|
-
warnings.push(`frontend.json: designSystemConsistency="${consistency}" but designFindings is empty — name the specific broken patterns (interaction/theming/cross-page/…)`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
for (const w of warnings) console.log(`WARN ${w}`);
|
|
171
|
-
for (const e of errors) console.log(`ERROR ${e}`);
|
|
172
|
-
console.log(`\n${errors.length} error(s), ${warnings.length} warning(s) across ${Object.keys(docs).length} files`);
|
|
173
|
-
process.exit(errors.length ? 1 : 0);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-tag-middleware
|
|
3
|
-
description: '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.'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Deprecated: use `pikku-middleware`
|
|
7
|
-
|
|
8
|
-
Tag middleware is covered in the **`pikku-middleware`** skill, which also covers:
|
|
9
|
-
- `addHTTPMiddleware` (global / prefix-based)
|
|
10
|
-
- `addTagMiddleware` (tag-scoped)
|
|
11
|
-
- Middleware execution order and priority
|
|
12
|
-
- Service-to-service bearer auth pattern
|
|
13
|
-
- Session-setting middleware pattern
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-template-clone
|
|
3
|
-
description: '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 `npm create pikku`, `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.'
|
|
4
|
-
allowed-tools: Bash(git status *), Bash(git add *), Bash(git commit *), Bash(git rm *), Bash(git mv *), Bash(git log *)
|
|
5
|
-
installGroups: [core]
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Pikku Template Post-Clone Cleanup
|
|
9
|
-
|
|
10
|
-
## Agent Operating Procedure
|
|
11
|
-
|
|
12
|
-
Run this **once**, right after a template is cloned or scaffolded into a new
|
|
13
|
-
project. The goal is to turn template scaffolding into a real project. Make the
|
|
14
|
-
smallest changes and land them as one focused `chore: post-clone cleanup`
|
|
15
|
-
commit, separate from any feature work.
|
|
16
|
-
|
|
17
|
-
1. **Replace the template README.** The shipped `README.md` describes the
|
|
18
|
-
_template_, not the user's project — leaving it in place is misleading.
|
|
19
|
-
Either delete it (`git rm README.md`) or rewrite it with the new project's
|
|
20
|
-
name and purpose. Never ship a clone with the generic template README.
|
|
21
|
-
2. **Keep the lockfile committed.** Templates ship a committed `yarn.lock`; do
|
|
22
|
-
NOT re-add `yarn.lock` to `.gitignore`. A real project commits its lockfile
|
|
23
|
-
for reproducible installs. The correct pattern is `yarn.lock` followed by
|
|
24
|
-
`!/yarn.lock`, which commits the root lockfile while keeping generated
|
|
25
|
-
per-unit lockfiles under `.deploy/` (and `e2e/`) ignored.
|
|
26
|
-
3. **Rename template identifiers.** Update `name` in the root `package.json`
|
|
27
|
-
(and any `@project/*` or other placeholder names) to the real project.
|
|
28
|
-
4. **Drop template-only artifacts.** Remove any `TEMPLATE.md`, demo docs, or
|
|
29
|
-
placeholder content that only made sense for the template.
|
|
30
|
-
|
|
31
|
-
Do not touch generated files (`.pikku/`, `*.gen.*`) or run a full reinstall as
|
|
32
|
-
part of cleanup — this step is project hygiene, not a build.
|
|
33
|
-
|
|
34
|
-
## Why this exists
|
|
35
|
-
|
|
36
|
-
Templates are structure-only starting points. Without this pass, clones carry a
|
|
37
|
-
misleading README, a placeholder package name, and (historically) a gitignored
|
|
38
|
-
lockfile — all of which leak template assumptions into a real project. Running
|
|
39
|
-
it immediately after clone keeps every Pikku project, OSS or Fabric, starting
|
|
40
|
-
from a clean, honest baseline.
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-trigger
|
|
3
|
-
description: >-
|
|
4
|
-
Use when adding event-driven functions that respond to system events like Redis pub/sub,
|
|
5
|
-
PostgreSQL LISTEN/NOTIFY, or custom event sources. Covers wireTrigger, wireTriggerSource, and
|
|
6
|
-
pikkuTriggerFunc. TRIGGER when: code uses wireTrigger/wireTriggerSource/pikkuTriggerFunc, user
|
|
7
|
-
asks about event-driven functions, Redis pub/sub, PostgreSQL LISTEN/NOTIFY, or reacting to
|
|
8
|
-
external events. DO NOT TRIGGER when: user asks about scheduled tasks (use pikku-cron) or
|
|
9
|
-
background job queues (use pikku-queue).
|
|
10
|
-
installGroups: [core]
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Pikku Trigger Wiring
|
|
14
|
-
|
|
15
|
-
## Agent Operating Procedure
|
|
16
|
-
|
|
17
|
-
Use this skill as an execution checklist, not reference material.
|
|
18
|
-
|
|
19
|
-
1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
|
|
20
|
-
2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
|
|
21
|
-
3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
|
|
22
|
-
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
23
|
-
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
24
|
-
|
|
25
|
-
Wire Pikku functions to fire when external events occur. Triggers connect event sources (Redis pub/sub, PostgreSQL LISTEN/NOTIFY, polling, webhooks) to Pikku functions.
|
|
26
|
-
|
|
27
|
-
## Before You Start
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
pikku info functions --verbose # See existing functions and their types
|
|
31
|
-
pikku info tags --verbose # Understand project organization
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
See `pikku-concepts` for the core mental model.
|
|
35
|
-
|
|
36
|
-
## API Reference
|
|
37
|
-
|
|
38
|
-
### `wireTrigger(config)`
|
|
39
|
-
|
|
40
|
-
Define the target function that handles trigger events:
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
import { wireTrigger } from '@pikku/core/trigger'
|
|
44
|
-
|
|
45
|
-
wireTrigger({
|
|
46
|
-
name: string, // Trigger name (matches source)
|
|
47
|
-
func: PikkuFunc, // Function to call when event fires
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### `wireTriggerSource(config)`
|
|
52
|
-
|
|
53
|
-
Define the event source that fires triggers:
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
import { wireTriggerSource } from '@pikku/core/trigger'
|
|
57
|
-
|
|
58
|
-
wireTriggerSource({
|
|
59
|
-
name: string, // Must match wireTrigger name
|
|
60
|
-
func: PikkuTriggerFunc, // Source function (sets up listener)
|
|
61
|
-
input: object, // Configuration for the source
|
|
62
|
-
})
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### `pikkuTriggerFunc<TInput, TEvent>`
|
|
66
|
-
|
|
67
|
-
Define a trigger source function. Returns a cleanup function.
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
import { pikkuTriggerFunc } from '#pikku'
|
|
71
|
-
|
|
72
|
-
const source = pikkuTriggerFunc<
|
|
73
|
-
InputType, // Configuration input
|
|
74
|
-
EventType // Shape of events it emits
|
|
75
|
-
>(async (services, input, { trigger }) => {
|
|
76
|
-
// Set up listener...
|
|
77
|
-
trigger.invoke(eventData) // Fire the trigger
|
|
78
|
-
|
|
79
|
-
// Return cleanup function
|
|
80
|
-
return async () => {
|
|
81
|
-
/* teardown */
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Usage Patterns
|
|
87
|
-
|
|
88
|
-
### Redis Pub/Sub Source
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
const redisSubscribe = pikkuTriggerFunc<
|
|
92
|
-
{ channels: string[] },
|
|
93
|
-
{ channel: string; message: any }
|
|
94
|
-
>(async ({ redis }, { channels }, { trigger }) => {
|
|
95
|
-
const subscriber = redis.duplicate()
|
|
96
|
-
|
|
97
|
-
subscriber.on('message', (channel, message) => {
|
|
98
|
-
trigger.invoke({ channel, message: JSON.parse(message) })
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
await subscriber.subscribe(...channels)
|
|
102
|
-
|
|
103
|
-
return async () => {
|
|
104
|
-
await subscriber.unsubscribe()
|
|
105
|
-
await subscriber.quit()
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// Target function
|
|
110
|
-
const onOrderEvent = pikkuSessionlessFunc({
|
|
111
|
-
title: 'On Order Event',
|
|
112
|
-
func: async ({ db, logger }, { channel, message }) => {
|
|
113
|
-
logger.info(`Order event on ${channel}`, message)
|
|
114
|
-
await db.processOrderEvent(message)
|
|
115
|
-
},
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
// Wire them together
|
|
119
|
-
wireTrigger({
|
|
120
|
-
name: 'order-events',
|
|
121
|
-
func: onOrderEvent,
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
wireTriggerSource({
|
|
125
|
-
name: 'order-events',
|
|
126
|
-
func: redisSubscribe,
|
|
127
|
-
input: { channels: ['orders:created', 'orders:updated'] },
|
|
128
|
-
})
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Triggers vs Queues
|
|
132
|
-
|
|
133
|
-
| Feature | Trigger | Queue |
|
|
134
|
-
| ----------- | ---------------------------------- | ------------------------------ |
|
|
135
|
-
| Execution | Synchronous, in-process | Async, distributed |
|
|
136
|
-
| Reliability | At-most-once | At-least-once (with retries) |
|
|
137
|
-
| Use case | React to events immediately | Reliable background processing |
|
|
138
|
-
| Source | External systems (Redis, PG, etc.) | Enqueued programmatically |
|
|
139
|
-
|
|
140
|
-
Use triggers for real-time reactions. Use queues for reliable, retryable background work.
|
|
141
|
-
|
|
142
|
-
## Complete Example
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
// functions/triggers.functions.ts
|
|
146
|
-
const pgListen = pikkuTriggerFunc<{ channel: string }, { payload: any }>(
|
|
147
|
-
async ({ db }, { channel }, { trigger }) => {
|
|
148
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(channel)) {
|
|
149
|
-
throw new Error(`Invalid channel name: ${channel}`)
|
|
150
|
-
}
|
|
151
|
-
const client = await db.pool.connect()
|
|
152
|
-
|
|
153
|
-
client.on('notification', (msg) => {
|
|
154
|
-
trigger.invoke({ payload: JSON.parse(msg.payload) })
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
await client.query(`LISTEN ${channel}`)
|
|
158
|
-
|
|
159
|
-
return async () => {
|
|
160
|
-
await client.query(`UNLISTEN ${channel}`)
|
|
161
|
-
client.release()
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
const onUserCreated = pikkuSessionlessFunc({
|
|
167
|
-
title: 'On User Created',
|
|
168
|
-
func: async ({ emailService, logger }, { payload }) => {
|
|
169
|
-
logger.info('New user created', { userId: payload.id })
|
|
170
|
-
await emailService.sendWelcome(payload.email)
|
|
171
|
-
},
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
// wirings/triggers.wiring.ts
|
|
175
|
-
wireTrigger({ name: 'user-created', func: onUserCreated })
|
|
176
|
-
wireTriggerSource({
|
|
177
|
-
name: 'user-created',
|
|
178
|
-
func: pgListen,
|
|
179
|
-
input: { channel: 'user_created' },
|
|
180
|
-
})
|
|
181
|
-
```
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-versioning
|
|
3
|
-
description: >-
|
|
4
|
-
Use when versioning Pikku function contracts, detecting breaking changes, or managing API
|
|
5
|
-
backward compatibility. Covers the version property, versions.pikku.json manifest, contract
|
|
6
|
-
hashing, and CI integration. TRIGGER when: code uses version: on a pikkuFunc, user asks about
|
|
7
|
-
API versioning, breaking changes, contract hashes, backward compatibility, or "pikku versions"
|
|
8
|
-
CLI commands. DO NOT TRIGGER when: user asks about secrets/variables/OAuth2 (use pikku-config)
|
|
9
|
-
or general function definitions (use pikku-concepts).
|
|
10
|
-
installGroups: [core]
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Pikku Function Versioning
|
|
14
|
-
|
|
15
|
-
## Agent Operating Procedure
|
|
16
|
-
|
|
17
|
-
Use this skill as an execution checklist, not reference material.
|
|
18
|
-
|
|
19
|
-
1. Discover before editing. Prefer OpenCode tools such as `pikku-meta` when available; otherwise run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
|
|
20
|
-
2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
|
|
21
|
-
3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
|
|
22
|
-
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
23
|
-
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
24
|
-
|
|
25
|
-
Track and protect function contracts across releases. Pikku hashes each function's input/output schema into a manifest so you can detect breaking changes before they ship.
|
|
26
|
-
|
|
27
|
-
## Before You Start
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
pikku info functions --verbose # See existing functions and their versions
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
See `pikku-concepts` for the core mental model.
|
|
34
|
-
|
|
35
|
-
## Function Versioning
|
|
36
|
-
|
|
37
|
-
When you need to introduce a breaking change, keep the old function as a pinned version and let the new one become the latest.
|
|
38
|
-
|
|
39
|
-
**The pattern:**
|
|
40
|
-
|
|
41
|
-
1. Create a new file `my-function-v1.function.ts` — export a variable with the `V1` suffix
|
|
42
|
-
2. Set `override: 'myFunction'` — this is the contract key the manifest groups under
|
|
43
|
-
3. Set `version: 1` — pins this as version 1 of the contract
|
|
44
|
-
4. The existing `my-function.function.ts` (no `version:` field) automatically becomes the latest version
|
|
45
|
-
|
|
46
|
-
```typescript
|
|
47
|
-
// my-function-v1.function.ts — old contract, kept for running workflows/agents
|
|
48
|
-
export const getBookV1 = pikkuFunc({
|
|
49
|
-
override: 'getBook', // REQUIRED — links this to the 'getBook' contract family
|
|
50
|
-
version: 1,
|
|
51
|
-
input: z.object({ bookId: z.string() }),
|
|
52
|
-
output: z.object({ title: z.string() }),
|
|
53
|
-
func: async ({ db }, { bookId }) => {
|
|
54
|
-
return db.getBook(bookId)
|
|
55
|
-
},
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// my-function.function.ts — latest contract, no version: field
|
|
59
|
-
export const getBook = pikkuFunc({
|
|
60
|
-
input: z.object({
|
|
61
|
-
bookId: z.string(),
|
|
62
|
-
format: z.enum(['full', 'summary']),
|
|
63
|
-
}),
|
|
64
|
-
output: z.object({
|
|
65
|
-
title: z.string(),
|
|
66
|
-
author: z.string(),
|
|
67
|
-
isbn: z.string(),
|
|
68
|
-
}),
|
|
69
|
-
func: async ({ db }, { bookId, format }) => {
|
|
70
|
-
return db.getBook(bookId, format)
|
|
71
|
-
},
|
|
72
|
-
})
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**Why `override` is required:** The manifest groups functions by a shared contract key. Without `override: 'getBook'`, `getBookV1` is stored internally as `getBookV1@v1` (key: `getBookV1`), which is a different contract family from `getBook`. With `override: 'getBook'`, it becomes `getBook@v1` (key: `getBook`), which groups with the unversioned `getBook` — and the unversioned one is automatically promoted to `getBook@v2`.
|
|
76
|
-
|
|
77
|
-
## Version Manifest (`versions.pikku.json`)
|
|
78
|
-
|
|
79
|
-
Pikku tracks contract hashes to detect breaking changes:
|
|
80
|
-
|
|
81
|
-
```json
|
|
82
|
-
{
|
|
83
|
-
"manifestVersion": 1,
|
|
84
|
-
"contracts": {
|
|
85
|
-
"createTodo": {
|
|
86
|
-
"latest": 1,
|
|
87
|
-
"versions": {
|
|
88
|
-
"1": { "inputHash": "a1b2c3d4", "outputHash": "e5f6a7b8" }
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
"getTodos": {
|
|
92
|
-
"latest": 2,
|
|
93
|
-
"versions": {
|
|
94
|
-
"1": { "inputHash": "i9j0k1l2", "outputHash": "m3n4o5p6" },
|
|
95
|
-
"2": { "inputHash": "q7r8s9t0", "outputHash": "u1v2w3x4" }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Each hash is derived from the function's input and output schemas plus the contract key. If a schema changes without a version bump, `pikku versions check` will fail.
|
|
103
|
-
|
|
104
|
-
## CLI Commands
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
npx pikku versions init # Initialize versioning manifest (run once)
|
|
108
|
-
npx pikku versions check # Detect contract changes (use in CI)
|
|
109
|
-
npx pikku versions update # Update contract hashes after version bump
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**Workflow:**
|
|
113
|
-
|
|
114
|
-
1. `pikku versions init` — run once to create `versions.pikku.json`
|
|
115
|
-
2. Develop normally — add/modify functions
|
|
116
|
-
3. `pikku versions check` — CI catches unversioned breaking changes
|
|
117
|
-
4. If intentional: create `my-function-v1.function.ts` with `override` + `version: 1`, then `pikku versions update`
|
|
118
|
-
|
|
119
|
-
## CI Integration
|
|
120
|
-
|
|
121
|
-
```yaml
|
|
122
|
-
# .github/workflows/ci.yml
|
|
123
|
-
name: CI
|
|
124
|
-
on: [push, pull_request]
|
|
125
|
-
|
|
126
|
-
jobs:
|
|
127
|
-
check:
|
|
128
|
-
runs-on: ubuntu-latest
|
|
129
|
-
steps:
|
|
130
|
-
- uses: actions/checkout@v4
|
|
131
|
-
- run: npm ci
|
|
132
|
-
- run: npx pikku versions check
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Complete Example
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
// create-todo-v1.function.ts — v1 locked contract
|
|
139
|
-
export const createTodoV1 = pikkuSessionlessFunc({
|
|
140
|
-
override: 'createTodo', // groups under 'createTodo' contract family
|
|
141
|
-
version: 1,
|
|
142
|
-
input: z.object({ title: z.string() }),
|
|
143
|
-
output: z.object({ id: z.string(), title: z.string() }),
|
|
144
|
-
func: async ({ todoStore }, { title }) => todoStore.add(title),
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
// create-todo.function.ts — v2 (latest), called by default
|
|
148
|
-
export const createTodo = pikkuSessionlessFunc({
|
|
149
|
-
input: z.object({
|
|
150
|
-
title: z.string(),
|
|
151
|
-
priority: z.enum(['low', 'medium', 'high']),
|
|
152
|
-
}),
|
|
153
|
-
output: z.object({
|
|
154
|
-
id: z.string(),
|
|
155
|
-
title: z.string(),
|
|
156
|
-
priority: z.string(),
|
|
157
|
-
}),
|
|
158
|
-
func: async ({ todoStore }, { title, priority }) =>
|
|
159
|
-
todoStore.add(title, priority),
|
|
160
|
-
})
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Result in manifest:
|
|
164
|
-
|
|
165
|
-
```json
|
|
166
|
-
"createTodo": {
|
|
167
|
-
"latest": 2,
|
|
168
|
-
"versions": {
|
|
169
|
-
"1": { "inputHash": "...", "outputHash": "..." },
|
|
170
|
-
"2": { "inputHash": "...", "outputHash": "..." }
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|