create-pracht 0.6.0 → 0.6.2

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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/skills/add-auth/SKILL.md +63 -143
  3. package/skills/add-capabilities/SKILL.md +409 -0
  4. package/skills/add-content/SKILL.md +242 -0
  5. package/skills/add-db/SKILL.md +93 -202
  6. package/skills/add-i18n/SKILL.md +178 -217
  7. package/skills/add-images/SKILL.md +203 -0
  8. package/skills/add-observability/SKILL.md +118 -15
  9. package/skills/add-openapi/SKILL.md +209 -0
  10. package/skills/audit-a11y/SKILL.md +8 -9
  11. package/skills/audit-agent-surface/SKILL.md +335 -0
  12. package/skills/audit-auth/SKILL.md +16 -11
  13. package/skills/audit-bundles/SKILL.md +56 -12
  14. package/skills/audit-csrf/SKILL.md +9 -10
  15. package/skills/audit-deps/SKILL.md +8 -8
  16. package/skills/audit-headers/SKILL.md +9 -10
  17. package/skills/audit-islands/SKILL.md +9 -10
  18. package/skills/audit-loaders/SKILL.md +23 -8
  19. package/skills/audit-redirects/SKILL.md +9 -10
  20. package/skills/audit-secrets/SKILL.md +6 -6
  21. package/skills/audit-seo/SKILL.md +8 -8
  22. package/skills/audit-shells/SKILL.md +8 -9
  23. package/skills/configure-isg/SKILL.md +9 -10
  24. package/skills/migrate-nextjs/SKILL.md +200 -415
  25. package/skills/pracht-debug/SKILL.md +165 -120
  26. package/skills/pracht-deploy/SKILL.md +248 -329
  27. package/skills/pracht-scaffold/SKILL.md +123 -146
  28. package/skills/pracht-test-api/SKILL.md +10 -10
  29. package/skills/pre-deploy/SKILL.md +166 -195
  30. package/skills/scaffold-e2e/SKILL.md +11 -12
  31. package/skills/scaffold-tests/SKILL.md +10 -12
  32. package/skills/tune-render-mode/SKILL.md +7 -8
  33. package/skills/typed-routes/SKILL.md +15 -11
  34. package/skills/upgrade-pracht/SKILL.md +12 -10
  35. package/src/index.js +43 -0
@@ -1,16 +1,14 @@
1
1
  ---
2
2
  name: pre-deploy
3
- version: 1.3.0
3
+ version: 1.4.0
4
4
  description: |
5
- Adapter-aware pre-deployment checklist for pracht apps targeting Node,
6
- Cloudflare Workers, Vercel, or a pure static export. Catches the issues that
7
- only surface in the production runtime: missing env vars, Node-only APIs in
8
- edge bundles, ISG manifest absence, oversized edge bundles, missing
9
- wrangler/vercel config, and static hosts missing clean-URL, 404, or security
10
- header configuration.
11
- Use when asked to "pre-deploy check", "ready to ship?", "deployment
12
- checklist", "is my build production-safe", or before running `wrangler
13
- deploy` / `vercel deploy`.
5
+ Adapter-aware pre-deployment checklist (Node, Cloudflare Workers, Vercel, static)
6
+ for the failures that only surface in production: missing env vars, Node-only
7
+ APIs in edge bundles, absent ISG manifest, oversized bundles, missing
8
+ wrangler/vercel config, and static hosts without clean URLs, 404, or security
9
+ headers.
10
+ Use for "pre-deploy check", "ready to ship?", "deployment checklist", "is my
11
+ build production-safe", before `wrangler deploy` or `vercel deploy`.
14
12
  allowed-tools:
15
13
  - Bash
16
14
  - Read
@@ -20,233 +18,206 @@ allowed-tools:
20
18
 
21
19
  # Pracht Pre-Deploy
22
20
 
23
- Run this before every production deploy. Each adapter has a different runtime
24
- contract; this skill enforces the contract that matches your build.
21
+ Run before every production deploy. Each adapter has a different runtime
22
+ contract; enforce the one that matches the build. Never deploy on the user's
23
+ behalf — end at the verdict.
25
24
 
26
- ## Step 1: Detect the adapter
27
-
28
- If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
29
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`) over
25
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
26
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify` tools over
30
27
  shelling out.
31
28
 
32
- Read `vite.config.ts` and look for `nodeAdapter()`, `cloudflareAdapter()`,
33
- `vercelAdapter()`, or `staticAdapter()`. Confirm with:
34
-
35
- ```bash
36
- pracht inspect build --json
37
- ```
29
+ ## Step 1: Build, then detect the adapter
38
30
 
39
- The `adapterTarget` field is authoritative. Prerequisites: `pracht inspect`
40
- needs a vite config with the pracht plugin, and `inspect build` reads
41
- artifacts from a prior build — if `pracht build` has not been run recently,
42
- run it first:
31
+ Always build first — never lint a stale `dist/`.
43
32
 
44
33
  ```bash
45
34
  pracht build
35
+ pracht inspect build --json
46
36
  ```
47
37
 
48
- ## Step 2: Run framework-wide checks
38
+ `adapterTarget` is authoritative; `vite.config.ts` (`nodeAdapter()`,
39
+ `cloudflareAdapter()`, `vercelAdapter()`, `staticAdapter()`) is the
40
+ cross-check. Never assume the adapter.
41
+
42
+ ## Step 2: Framework-wide checks
49
43
 
50
44
  ```bash
51
45
  pracht doctor --json
52
46
  pracht verify --json
47
+ pracht typegen --check # only if src/pracht-routes.ts or src/pracht.d.ts exists
53
48
  ```
54
49
 
55
- If the app uses generated typed routes (`src/pracht-routes.ts` or
56
- `src/pracht.d.ts` exists), also run:
57
-
58
- ```bash
59
- pracht typegen --check
60
- ```
61
-
62
- These catch app-graph wiring problems independent of the adapter — including
50
+ These catch app-graph wiring problems independent of the adapter, including
63
51
  `defineApp({ constraints })` violations and a stale `.pracht/app-graph.json`
64
- snapshot (fix the latter with `pracht plan --write`, then re-review the plan
65
- output). Resolve all `status: "error"` entries before continuing.
52
+ snapshot (fix that with `pracht plan --write`, then re-review the plan output).
53
+ Resolve every `status: "error"` before continuing — **if `pracht doctor`
54
+ reports errors, stop here**; the remaining checks will be noisy false
55
+ positives. Stale generated typed-route files block deployment; if the app does
56
+ not use them yet, note that `typegen --check` is optional.
66
57
 
67
- When the deploy corresponds to a PR, `pracht report --base origin/main` produces
68
- a markdown summary (graph diff + verify + budgets) worth attaching to it.
58
+ For a PR deploy, `pracht report --base origin/main` produces a markdown summary
59
+ (graph diff + verify + budgets) worth attaching.
69
60
 
70
- ## Step 3: Adapter-specific checklist
61
+ ## Step 3: Adapter checklist
71
62
 
72
63
  ### Node (`@pracht/adapter-node`)
73
64
 
74
- - `dist/server/server.js` exists.
75
- - `dist/client/.vite/manifest.json` exists.
76
- - `dist/server/isg-manifest.json` exists if any route has `render: "isg"`.
77
- - Smoke test: `pracht preview --skip-build` (or `node dist/server/server.js`) boots and `curl localhost:3000` returns 200.
78
- - Required env vars (grep `process.env.*` across `src/`) are set in the
79
- deployment environment. List them for the user.
80
- - If the app mounts `createImageHandler()` from `@pracht/image/node`, confirm
81
- `sharp` is installed and `localOrigin` is the same trusted public origin as
82
- `nodeAdapter({ canonicalOrigin })`. A relative image endpoint without both
83
- values is an error in every environment; loopback-looking request origins
84
- are intentionally not trusted.
85
- - Reverse-proxy / TLS termination configured (out of scope for this skill —
86
- flag for confirmation).
87
- - If the proxy strips Vite's deploy base, confirm
88
- `nodeAdapter({ basePathStripped: true })`; application code should still
89
- observe the public base in `request.url`, and the proxy must own the public
90
- bare-base redirect (`/app` to `/app/`).
65
+ - `dist/server/server.js`, `dist/client/.vite/manifest.json`, and — if any
66
+ route is `render: "isg"` — `dist/server/isg-manifest.json` all exist.
67
+ - `pracht preview --skip-build` (or `node dist/server/server.js`) boots and
68
+ `curl localhost:3000` returns 200.
69
+ - Every env var the app reads (grep `process.env.*` across `src/`) is set in
70
+ the deployment environment. List them for the user.
71
+ - If the app mounts `createImageHandler()` from `@pracht/image/node`, `sharp`
72
+ is installed and `localOrigin` is the same trusted public origin as
73
+ `nodeAdapter({ canonicalOrigin })`. A relative image endpoint without both is
74
+ an error in every environment; loopback-looking request origins are
75
+ intentionally not trusted.
76
+ - Reverse proxy / TLS termination configured — out of scope here, flag for
77
+ confirmation.
78
+ - If the proxy strips Vite's deploy base, `nodeAdapter({ basePathStripped:
79
+ true })` is set; application code should still observe the public base in
80
+ `request.url`, and the proxy must own the bare-base redirect (`/app` →
81
+ `/app/`).
91
82
 
92
83
  ### Cloudflare Workers (`@pracht/adapter-cloudflare`)
93
84
 
94
- - `wrangler.toml` (or `wrangler.jsonc`) present at repo root.
95
- - `main` points to `dist/server/worker.js` — the thin deploy wrapper that
96
- re-exports only the default handler and Cloudflare entrypoint classes.
97
- Pointing `main` at `dist/server/server.js` is an **error**: workerd
98
- validates every named export of the deploy entry and rejects the build
99
- metadata (`buildTarget`, manifests, `resolvedApp`, ...) that `server.js`
100
- exports for the prerender pass.
101
- - `assets.directory` points to `dist/client`.
102
- - `compatibility_date` is set, and is a date the installed workerd supports.
103
- It must not be *newer* than the runtime: workerd refuses to start with
104
- "This Worker requires compatibility date X, but the newest date supported
105
- by this server binary is Y". Never set it to today's date — that is by
106
- construction at or beyond the newest released workerd.
107
- - Bindings declared in wrangler config for every `context.env.*` access in
108
- loaders, middleware, and API routes (grep, then cross-check).
109
- - **No Node-only APIs in the server bundle.** Grep the server files for:
110
- `fs`, `path` (Node form), `process.cwd`, `Buffer`, `__dirname`,
111
- `__filename`, `crypto.createHash` (use `crypto.subtle` instead),
112
- `child_process`, `cluster`, `worker_threads`. Two nuances before flagging:
113
- - Consult `compatibility_flags` in the wrangler config first — with
114
- `nodejs_compat`, `Buffer` and several `node:` modules are legal in
115
- workerd. Only flag APIs the active flags don't cover.
116
- - Dev already runs inside workerd via `@cloudflare/vite-plugin`, so most
117
- incompatibilities surface in dev; this check is the backstop for code
118
- paths dev never hit.
119
- - An API route importing `@pracht/image/node` is an error on Workers because
120
- its optimizer requires `sharp`. Require `cloudflareLoader` (or
121
- `passthroughLoader`) instead.
122
- - ISG: worker-managed ISG via the per-colo Workers Cache API works out of the
123
- box. If time-revalidated routes should use the edge-tier Workers Caching
124
- upgrade instead, confirm both sides — `cloudflareAdapter({ cache: true })`
125
- in vite config and `"cache": { "enabled": true }` in wrangler config.
126
- - When Workers Caching is enabled, flag ISG routes reachable through unbounded
127
- query strings. Require a bounded allowlist/canonical redirect or an uncached
128
- gateway with a normalized `cf.cacheKey`; also check that markdown-capable
129
- routes normalize `Accept` at the gateway when variant fan-out matters.
130
- - Bundle size: measure what actually deploys — `dist/server/worker.js` plus
131
- its `dist/server/server.js` import (wrangler bundles the import graph of
132
- `main`; `worker.js` alone is a few lines). Workers limit is ~1 MB
133
- compressed for free tier, ~10 MB on paid. Warn at 80% of the active limit.
85
+ - `wrangler.toml`/`wrangler.jsonc` present at repo root, `assets.directory`
86
+ pointing at `dist/client`, and bindings declared for every `context.env.*`
87
+ access in loaders, middleware, and API routes (grep, then cross-check).
88
+ - `main` points at `dist/server/worker.js` — the thin deploy wrapper
89
+ re-exporting only the default handler and Cloudflare entrypoint classes.
90
+ Pointing it at `dist/server/server.js` is an **error**: workerd validates
91
+ every named export of the deploy entry and rejects the build metadata
92
+ (`buildTarget`, manifests, `resolvedApp`, …) that `server.js` exports for the
93
+ prerender pass.
94
+ - `no_bundle: true` plus an `ESModule` rule whose globs include `"**/*.js"`.
95
+ Pracht's Vite output is already bundled and may contain lazy server chunks;
96
+ these settings make Wrangler upload them as separate modules instead of
97
+ folding them into the entry.
98
+ - `compatibility_date` is set and is a date the installed workerd supports. It
99
+ must not be *newer* than the runtime, or workerd refuses to start ("This
100
+ Worker requires compatibility date X, but the newest date supported by this
101
+ server binary is Y"). Never set it to today's date — that is by construction
102
+ at or beyond the newest released workerd.
103
+ - **No Node-only APIs in the server bundle.** Grep the server files for `fs`,
104
+ `path` (Node form), `process.cwd`, `Buffer`, `__dirname`, `__filename`,
105
+ `crypto.createHash` (use `crypto.subtle`), `child_process`, `cluster`,
106
+ `worker_threads`. Check `compatibility_flags` first — with `nodejs_compat`,
107
+ `Buffer` and several `node:` modules are legal — and only flag what the
108
+ active flags do not cover. Dev already runs inside workerd via
109
+ `@cloudflare/vite-plugin`, so this is the backstop for code paths dev never
110
+ hits.
111
+ - An API route importing `@pracht/image/node` is an error on Workers (its
112
+ optimizer needs `sharp`). Require `cloudflareLoader` or `passthroughLoader`.
113
+ - ISG works out of the box via the per-colo Workers Cache API. If
114
+ time-revalidated routes should use the edge-tier Workers Caching upgrade,
115
+ confirm *both* sides: `cloudflareAdapter({ cache: true })` and
116
+ `"cache": { "enabled": true }` in wrangler config. With it enabled, flag ISG
117
+ routes reachable through unbounded query strings — require a bounded
118
+ allowlist or canonical redirect, or an uncached gateway with a normalized
119
+ `cf.cacheKey` — and check that markdown-capable routes normalize `Accept` at
120
+ the gateway when variant fan-out matters.
121
+ - Bundle size: measure what actually deploys — `dist/server/worker.js` plus its
122
+ `dist/server/server.js` import and lazy chunks (`worker.js` alone is a few
123
+ lines; `no_bundle` uploads the pre-built module graph). The Workers limit is
124
+ ~1 MB compressed on free, ~10 MB on paid. Warn at 80% of the active limit.
134
125
 
135
126
  ### Vercel (`@pracht/adapter-vercel`)
136
127
 
137
- - `.vercel/output/config.json` exists post-build.
128
+ - `.vercel/output/config.json` exists with `version: 3`, and
129
+ `.vercel/output/static/` is populated.
138
130
  - The render function exists at
139
131
  `.vercel/output/functions/<functionName>.func/server.js`. The name defaults
140
- to `render` but is configurable via `vercelAdapter({ functionName })` —
141
- read the configured name from `vite.config.ts` instead of hardcoding
142
- `render.func`.
143
- - `.vercel/output/static/` populated.
144
- - Required env vars are configured in the Vercel project (cannot verify from
145
- CLI without `vercel env pull` — run that and diff against `process.env.*`
146
- references).
147
- - Edge runtime constraints: the render function's `.vc-config.json` is
148
- **always** written with `runtime: "edge"`, so run the same Node-only API
149
- check as Cloudflare **unconditionally** for Vercel builds. Do not skip it
150
- based on a runtime probe — ISG routes run the same bundle on Node, but any
151
- Node-only API still breaks the edge function.
152
- - ISG functions: every `<route>.prerender-config.json` must sit next to a
153
- **Serverless** `<route>.func` (`.vc-config.json` with `launcherType:
154
- "Nodejs"`). Vercel rejects a prerender config paired with an edge function:
132
+ to `render` but is configurable via `vercelAdapter({ functionName })` — read
133
+ it from `vite.config.ts` rather than hardcoding `render.func`.
134
+ - Env vars are configured in the Vercel project. This cannot be verified from
135
+ the CLI alone: run `vercel env pull` and diff against `process.env.*` uses.
136
+ - **Run the Cloudflare Node-only API check unconditionally.** The render
137
+ function's `.vc-config.json` is always written with `runtime: "edge"`. Do not
138
+ skip it based on a runtime probe — ISG routes run the same bundle on Node,
139
+ but a Node-only API still breaks the edge function.
140
+ - Every `<route>.prerender-config.json` sits next to a **Serverless**
141
+ `<route>.func` (`.vc-config.json` with `launcherType: "Nodejs"`). Vercel
142
+ rejects a prerender config paired with an edge function:
155
143
  `Unexpected function type "EdgeFunction" at path "<route>"`.
156
- - Region configuration: `vercelAdapter({ regions: "all" })` is valid for the
157
- Edge render function, but generated Node ISG function configs must omit
158
- `regions` so the project's default Serverless region applies. Node configs
159
- may only contain arrays of concrete region identifiers.
160
- - An API route importing `@pracht/image/node` is an error for the Vercel Edge
144
+ - `vercelAdapter({ regions: "all" })` is valid for the Edge render function,
145
+ but generated Node ISG function configs must omit `regions` so the project
146
+ default applies; Node configs may only contain arrays of concrete region
147
+ identifiers.
148
+ - An API route importing `@pracht/image/node` is an error for the Edge
161
149
  function. Require `vercelLoader` (with aligned allowed sizes) or
162
- `passthroughLoader` instead.
163
- - Build Output API v3 sanity: `config.json` has `version: 3`.
150
+ `passthroughLoader`.
164
151
 
165
152
  ### Static export (`@pracht/adapter-static`)
166
153
 
167
- `adapterTarget` is `"static"`. There is no server to get wrong, so the
168
- checklist is about what the *host* must do and what the build cannot enforce.
154
+ There is no server to get wrong, so this checklist is about what the *host*
155
+ must do and what the build cannot enforce.
169
156
 
170
157
  - `dist/client/` exists and is the deploy root. `dist/server/` is build tooling
171
- only — it must not be uploaded (it contains the prerender bundle).
172
- - The build itself is the gate: it fails closed on `ssr`/`isg` routes, SPA
173
- loaders, non-full SPA hydration, API routes, route/not-found middleware,
158
+ and must not be uploaded — it contains the prerender bundle.
159
+ - The build is the gate: it fails closed on `ssr`/`isg` routes, SPA loaders,
160
+ non-full SPA hydration, API routes, route/not-found middleware,
174
161
  network-exposed capabilities, and any Vite `base` that is not `/` or a
175
- root-absolute path (CDN and document-relative bases are rejected). If
176
- `pracht build` succeeded, those contracts already hold — do not re-derive
177
- them by hand. Report a failing build verbatim; the message names the routes.
178
- - Host must serve `index.html` for directory URLs (clean URLs). Confirm the
179
- host's setting: S3 website endpoints need an index document, nginx needs
180
- `try_files $uri $uri/index.html`, GitHub Pages and Netlify do it by default.
181
- - Host must map `404.html` as the error document, otherwise unknown URLs get
182
- the host's generic error page instead of the app's `notFound` route. Verify
183
- `dist/client/404.html` exists; if it does not, the app declares no `notFound`
184
- page — flag it as a `warn`.
162
+ root-absolute path. If `pracht build` succeeded, those contracts already hold
163
+ — do not re-derive them by hand. Report a failing build verbatim; the message
164
+ names the routes.
165
+ - The host serves `index.html` for directory URLs. Confirm the actual setting:
166
+ S3 website endpoints need an index document, nginx needs
167
+ `try_files $uri $uri/index.html`; GitHub Pages and Netlify do it by default.
168
+ - The host maps `404.html` as its error document, or unknown URLs get the
169
+ host's generic error page instead of the app's `notFound` route. Verify
170
+ `dist/client/404.html` exists — if it does not, the app declares no
171
+ `notFound` page: `warn`.
185
172
  - **Security headers are not applied.** Every other adapter sets the four
186
173
  default security headers at request time; a static host has no request
187
- runtime. `dist/server/headers-manifest.json` records the headers each route
188
- *would* have carried — mirror the ones you need in the host's own header
189
- config (`_headers` on Netlify, CloudFront response header policies, nginx
190
- `add_header`). This is an `error` for any app handling user input, and
191
- `warn` otherwise. HSTS and CSP are host-side decisions either way.
174
+ runtime. `dist/server/headers-manifest.json` records what each route *would*
175
+ have carried — mirror the ones you need into the host's own header config
176
+ (`_headers` on Netlify, CloudFront response header policies, nginx
177
+ `add_header`). `error` for any app handling user input, `warn` otherwise.
178
+ HSTS and CSP are host-side decisions either way.
192
179
  - If `staticAdapter({ fallback })` is configured, the host needs a rewrite of
193
- unmatched URLs to that file, and the rewrite must not shadow real files.
194
- Note that it makes unknown URLs answer `200` (soft 404s). Without the
195
- rewrite the fallback file is inert — deep links into dynamic `render: "spa"`
196
- routes will 404.
197
- - Smoke test the real output, not the dev server:
198
- `pracht preview --skip-build` serves `dist/client/` the way a dumb host
199
- would. Check `/`, one dynamic SSG path, one deep link into a SPA route, and
200
- one unknown URL.
201
- - Routes exporting `markdown` rely on server-side `Accept` negotiation, which
202
- a static host cannot do — agents asking for `text/markdown` get HTML. The
203
- build prints a note when this applies; publish `.md` files under `public/`
204
- if a raw-markdown corpus matters.
205
- - Deploying to a sub-path (GitHub Pages *project* site, S3 key prefix) needs
206
- Vite `base` set to that path (`base: "/my-project/"`). Check it matches the
207
- deploy path exactly — a mismatch 404s every asset. Then check the app has no
208
- hand-written root-absolute internal links (`<a href="/about">`): those are
209
- not base-prefixed and will leave the deploy. `grep -rn 'href="/' src/` and
210
- confirm each hit is external, an asset under `public/`, or a `<Link route>`.
211
- Framework-owned URLs from `@pracht/image`'s `defaultLoader` and the OpenAPI
212
- companion UI/document already carry the base; do not flag their base-free
213
- route declarations. Custom image loaders and OpenAPI provider asset URLs
214
- still need to match the intended host.
215
- CDN bases (`https://cdn…`) and document-relative bases (`""` / `"./"`) are
216
- build errors, not sub-path deploys.
217
-
218
- ## Step 4: Cross-cutting checks
219
-
220
- - Run `audit-secrets` to confirm no `process.env.*` or `context.env.*` values
221
- flow into loader return values.
222
- - Run `audit-headers` to confirm `applyDefaultSecurityHeaders` is in use on
223
- user-facing responses (or that `headers()` exports cover the same ground).
224
- On a static export this check moves entirely to the host's header config —
225
- see the static section above.
226
- - Confirm `git status` is clean (deploying uncommitted work is a footgun).
180
+ unmatched URLs to that file that does not shadow real files. It makes unknown
181
+ URLs answer `200` (soft 404s), and without it the fallback file is inert —
182
+ deep links into dynamic `render: "spa"` routes will 404.
183
+ - Smoke test the real output, not the dev server: `pracht preview --skip-build`
184
+ serves `dist/client/` the way a dumb host would. Check `/`, one dynamic SSG
185
+ path, one deep link into a SPA route, and one unknown URL.
186
+ - Routes exporting `markdown` rely on server-side `Accept` negotiation, which a
187
+ static host cannot do — agents asking for `text/markdown` get HTML. The build
188
+ prints a note; publish `.md` files under `public/` if a raw-markdown corpus
189
+ matters.
190
+ - Sub-path deploys (GitHub Pages *project* site, S3 key prefix) need Vite
191
+ `base` set to that path (`base: "/my-project/"`), matching the deploy path
192
+ exactly — a mismatch 404s every asset. Then confirm no hand-written
193
+ root-absolute internal links: `grep -rn 'href="/' src/` and check each hit is
194
+ external, an asset under `public/`, or a `<Link route>`. Framework-owned URLs
195
+ from `@pracht/image`'s `defaultLoader` and the OpenAPI companion
196
+ UI/document already carry the base — do not flag their base-free route
197
+ declarations — but custom image loaders and OpenAPI provider asset URLs still
198
+ need to match the intended host. CDN bases (`https://cdn…`) and
199
+ document-relative bases (`""` / `"./"`) are build errors, not sub-path
200
+ deploys.
201
+
202
+ ## Step 4: Cross-cutting
203
+
204
+ - `/audit-secrets` — no `process.env.*` or `context.env.*` value flows into a
205
+ loader return value.
206
+ - `/audit-headers` — `applyDefaultSecurityHeaders` is in use on user-facing
207
+ responses, or `headers()` exports cover the same ground. On a static export
208
+ this moves entirely to the host config; see above.
209
+ - `git status` is clean. Deploying uncommitted work is a footgun.
227
210
 
228
211
  ## Step 5: Report
229
212
 
230
- Produce a checklist grouped by `Framework`, `Adapter`, `Cross-cutting`. Tag
231
- each item with a primary severity — `error` (blocks deploy), `warn` (deploy
232
- proceeds but risky), `info` — and keep pass/fail as the secondary per-item
233
- status. End with a one-line verdict: `READY` / `BLOCKED (N errors)` /
234
- `READY WITH WARNINGS (N warnings)`.
235
-
236
- ## Rules
237
-
238
- 1. Always run `pracht build` first. Do not lint a stale `dist/`.
239
- 2. Detect the adapter — never assume.
240
- 3. For Cloudflare/Vercel-edge, the Node-only API check is non-negotiable; an
241
- API not covered by the active compatibility flags will crash the worker on
242
- a code path that may never hit in dev.
243
- 4. For a static export, never report `READY` without naming the host settings
244
- the deploy depends on (clean URLs, `404.html`, security headers, and the
245
- fallback rewrite if configured). The build cannot verify any of them, so an
246
- unqualified `READY` is the one way this skill can mislead.
247
- 5. If the app does not use generated typed route files yet, note that `pracht typegen --check` is optional; if it does, stale generated files block deployment.
248
- 6. Do not deploy on the user's behalf. End the skill at the verdict.
249
- 7. If `pracht doctor` reports errors, do not run any other checks until those
250
- are resolved — they will produce noisy false positives.
213
+ A checklist grouped by `Framework`, `Adapter`, `Cross-cutting`. Tag each item
214
+ with a primary severity — `error` (blocks deploy), `warn` (proceeds but risky),
215
+ `info` — keeping pass/fail as the secondary per-item status. End with one line:
216
+ `READY` / `BLOCKED (N errors)` / `READY WITH WARNINGS (N warnings)`.
217
+
218
+ For a static export, never report `READY` without naming the host settings the
219
+ deploy depends on — clean URLs, `404.html`, security headers, and the fallback
220
+ rewrite if configured. The build cannot verify any of them, so an unqualified
221
+ `READY` is the one way this skill can mislead.
251
222
 
252
223
  $ARGUMENTS
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  name: scaffold-e2e
3
- version: 1.1.0
3
+ version: 1.1.1
4
4
  description: |
5
- Scaffold Playwright end-to-end tests for a pracht app: install Playwright,
6
- generate `playwright.config.ts` that boots `pracht dev` (or the production
7
- build via `pracht preview`), and emit a smoke test for every route in the
8
- manifest that asserts 200, head/title, no console errors, and basic
9
- navigation.
10
- Use when asked to "scaffold E2E", "set up Playwright", "add browser tests",
11
- or "create smoke tests for my routes".
5
+ Set up Playwright for a pracht app: install it, generate a
6
+ `playwright.config.ts` that boots `pracht dev` (or the build via `pracht
7
+ preview`), and emit a smoke test per manifest route asserting 200, head/title,
8
+ no console errors, and navigation.
9
+ Use for "scaffold E2E", "set up Playwright", "add browser tests", "create smoke
10
+ tests for my routes".
12
11
  allowed-tools:
13
12
  - Bash
14
13
  - Read
@@ -82,10 +81,10 @@ If `playwright.config.ts` already exists, merge — do not clobber.
82
81
 
83
82
  ## Step 4: Generate per-route smoke tests
84
83
 
85
- If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
86
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`,
87
- `generate_*`) over shelling out. Prerequisite: `pracht inspect` needs a vite
88
- config with the pracht plugin registered.
84
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
85
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify`/`generate_*`
86
+ tools over shelling out. `pracht inspect` needs the pracht plugin in the vite
87
+ config.
89
88
 
90
89
  ```bash
91
90
  pracht inspect routes --json
@@ -1,14 +1,12 @@
1
1
  ---
2
2
  name: scaffold-tests
3
- version: 1.2.0
3
+ version: 1.2.1
4
4
  description: |
5
- Scaffold Vitest unit/integration tests for pracht routes, loaders, and
6
- middleware. Asks the user once whether to use vitest browser mode with
7
- `vitest-browser-preact` (real DOM, real events) or classic JSDOM-based
8
- tests with `@testing-library/preact`. Wires `vitest.config.ts`, builds
9
- `LoaderArgs` with `@pracht/test`, and emits ready-to-run files.
10
- Use when asked to "scaffold tests", "set up Vitest", "add unit tests",
11
- "test this loader", or "test this route".
5
+ Set up Vitest for pracht routes, loaders, and middleware — browser mode with
6
+ `vitest-browser-preact` or JSDOM with `@testing-library/preact` — wiring
7
+ `vitest.config.ts`, `LoaderArgs` via `@pracht/test`, and runnable test files.
8
+ Use for "scaffold tests", "set up Vitest", "add unit tests", "test this loader",
9
+ "test this route".
12
10
  allowed-tools:
13
11
  - Bash
14
12
  - Read
@@ -134,10 +132,10 @@ If `vitest.config.ts` already exists, merge — never clobber.
134
132
 
135
133
  ## Step 4: Generate the tests
136
134
 
137
- If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
138
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`,
139
- `generate_*`) over shelling out. Prerequisite: `pracht inspect` needs a vite
140
- config with the pracht plugin registered.
135
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
136
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify`/`generate_*`
137
+ tools over shelling out. `pracht inspect` needs the pracht plugin in the vite
138
+ config.
141
139
 
142
140
  This skill covers **loaders, middleware, and components**. For API handlers
143
141
  (`src/api/**`), delegate to `/pracht-test-api` — it owns handler enumeration and
@@ -1,12 +1,11 @@
1
1
  ---
2
2
  name: tune-render-mode
3
- version: 1.1.0
3
+ version: 1.1.1
4
4
  description: |
5
- Recommend the right pracht render mode (ssg, isg, ssr, spa) for each route
6
- based on what its loader actually does. Most apps pick a mode once and never
7
- revisit; this skill surfaces routes that are mis-tuned.
8
- Use when asked to "tune render modes", "make my site faster", "should this
9
- route be SSG", "audit render modes", or "review SSG/ISG/SSR choices".
5
+ Recommend the right render mode (ssg, isg, ssr, spa) per route from what each
6
+ loader actually does, then apply the change after confirmation.
7
+ Use for "tune render modes", "should this route be SSG", "audit render modes",
8
+ "review SSG/ISG/SSR choices", "make my site faster".
10
9
  allowed-tools:
11
10
  - Bash
12
11
  - Read
@@ -61,8 +60,8 @@ For each route:
61
60
 
62
61
  ## Step 1: Enumerate
63
62
 
64
- If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
65
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`) over
63
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
64
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify` tools over
66
65
  shelling out.
67
66
 
68
67
  ```bash
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: typed-routes
3
- version: 1.1.0
3
+ version: 1.1.1
4
4
  description: |
5
- Add or maintain pracht typed routes, typed links, route-object navigation,
6
- and generated href helpers. Use when asked to "add typed routes", "fix typed
7
- links", "replace hard-coded hrefs", "run typegen", or make navigation route-id
8
- based instead of string based.
5
+ Add or maintain pracht typed routes: run typegen, adopt route-id based links and
6
+ navigation, and replace hard-coded hrefs with generated helpers.
7
+ Use for "add typed routes", "fix typed links", "replace hard-coded hrefs", "run
8
+ typegen".
9
9
  allowed-tools:
10
10
  - Bash
11
11
  - Read
@@ -24,10 +24,10 @@ Use this workflow to keep route ids, params, links, and navigation type-safe.
24
24
 
25
25
  The resolved app graph is the source of truth — not a manual glob of `src/`.
26
26
 
27
- If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
28
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`,
29
- `generate_*`) over shelling out. Prerequisite: `pracht inspect` needs a vite
30
- config with the pracht plugin registered.
27
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
28
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify`/`generate_*`
29
+ tools over shelling out. `pracht inspect` needs the pracht plugin in the vite
30
+ config.
31
31
 
32
32
  ```bash
33
33
  pracht inspect routes --json
@@ -107,8 +107,12 @@ same-origin anchor. It also accepts navigation-behavior props:
107
107
  `prefetch="none" | "hover" | "intent" | "viewport" | "render"` (per-link
108
108
  prefetch strategy, default `"intent"`), `preserveScroll` (keep the scroll
109
109
  position), and `viewTransition` (animate the navigation with the View Transitions API
110
- where supported). There is also an imperative `prefetch()` export and a
111
- `useNavigation()` hook for pending navigation/submission state.
110
+ where supported). Set `speculate={false}` on links that browser speculation
111
+ rules must not prefetch or prerender. Because it is independent of the JS
112
+ `prefetch` strategy, use both `speculate={false}` and `prefetch="none"` for GET
113
+ links with side effects. There is also an imperative
114
+ `prefetch()` export and a `useNavigation()` hook for pending
115
+ navigation/submission state.
112
116
 
113
117
  ### Outside components
114
118