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
@@ -0,0 +1,335 @@
1
+ ---
2
+ name: audit-agent-surface
3
+ version: 1.0.4
4
+ description: |
5
+ Inventory what agents can reach in a pracht app — capability exposure (HTTP,
6
+ WebMCP, remote MCP), `agents` trust config, the destructive-confirmation gate,
7
+ `llms.txt`, Markdown negotiation, OpenAPI — and report where the surface is
8
+ wider than intended, or confirm an opt-out app ships none.
9
+ Use for "audit the agent surface", "what can agents do on my site", "is my MCP
10
+ endpoint safe", "did this PR widen what agents can reach".
11
+ allowed-tools:
12
+ - Bash
13
+ - Read
14
+ - Grep
15
+ - Glob
16
+ ---
17
+
18
+ # Pracht Audit Agent Surface
19
+
20
+ Pracht's agent surface is opt-in end to end (`docs/CAPABILITIES.md`,
21
+ `docs/AGENT_TRUST.md`, `docs/REMOTE_MCP.md`). State that baseline before
22
+ auditing the opt-outs:
23
+
24
+ - No loader or API route is ever inferred as a capability; a capability without
25
+ `expose` is unreachable over the network.
26
+ - `destructive` capabilities may be exposed over HTTP and remote MCP, never as
27
+ WebMCP page tools, and every dispatch is confirmation-gated.
28
+ - Remote MCP rejects cookie-bearing and browser-originated requests, and serves
29
+ destructive capabilities only with `agents.mcp.destructive` plus a registered
30
+ approval store — otherwise it filters them out at serve time. `agents.mcp.auth`
31
+ additionally makes it an OAuth 2.0 protected resource; without it the endpoint
32
+ is open and authentication is the capability middleware's job.
33
+ - An app that registers neither capabilities nor `agents` has the dispatch path
34
+ and Web Bot Auth verifier dropped from its server bundle at build time.
35
+
36
+ This skill reports; it never mutates. Prerequisites: `pracht inspect` needs a
37
+ vite config registering the pracht plugin. If the pracht MCP server is
38
+ registered (see `docs/MCP.md`), prefer its tools (`inspect_agents`,
39
+ `inspect_capabilities`, `inspect_routes`, `inspect_api`, `doctor`, `verify`)
40
+ over shelling out.
41
+
42
+ ## Step 1: Inventory the declared surface
43
+
44
+ ```bash
45
+ pracht inspect agents --json # the whole configured surface in one call
46
+ pracht inspect capabilities --json # name, effect, transports, HTTP path, middleware, schemas
47
+ pracht inspect routes --json # markdown negotiation, hydration, middleware
48
+ pracht inspect api --json
49
+ pracht verify --json # contract, exposure, and projection checks
50
+ ```
51
+
52
+ `inspect agents` reports `webBotAuth`, confirmation policy, MCP endpoint and
53
+ OAuth policy, `llmsTxt`, each capability's effect/policy/transports/path, and
54
+ exposure counts (`private` means unexposed). Use `inspect capabilities` for
55
+ schemas and middleware.
56
+
57
+ Build the inventory table: capability → effect → transports → HTTP path →
58
+ middleware → `agentPolicy`. A capability reported as `unreadable` means
59
+ `@pracht/capabilities` is not installed; treat it as an `error` and stop
60
+ reasoning about its policy until it loads.
61
+
62
+ Cross-check `inspect agents` against the manifest's `agents` block. It reads
63
+ resolved app and production `llmsTxt` config, including computed branches. A
64
+ `null` `llmsTxt.enabled` means an older plugin: report unknown and recommend an
65
+ upgrade. Use resolved `mcp.auth`; `null` means framework-level OAuth is open.
66
+
67
+ ## Step 2: Exposure vs. intent
68
+
69
+ For every exposed capability, ask whether the exposure is deliberate:
70
+
71
+ - `expose.mcp` set but no `agents.mcp` configured — declared, served by
72
+ nothing. `pracht verify` warns; report it so the intent gets resolved.
73
+ - `expose.mcp` set on an operation whose authorization relies on a browser
74
+ session — remote MCP rejects cookies, so the only credentials it sees are the
75
+ forwarded `Authorization` header and `context.agent`. A middleware chain that
76
+ reads a session cookie authorizes nobody there.
77
+ - `expose.webmcp` — the in-page agent acts as the signed-in user in their tab.
78
+ Confirm that is intended for every one, and that the route's hydration is not
79
+ `"none"` (which registers no tools). Flag a webmcp capability whose effective
80
+ agent policy is `"require"` (capability-level, or inherited from
81
+ `agents.webBotAuth.policy`): page-tool calls are unsigned browser fetches, so
82
+ the tool is dead — every call 401s. Also check that capabilities returning
83
+ user-generated or third-party content set
84
+ `expose.webmcp: { untrustedContent: true }` so hosts treat the output as
85
+ untrusted.
86
+ - Private capabilities used as building blocks: `invokeCapability()` runs their
87
+ named middleware but **not** app-level `api.middleware`. Their named
88
+ middleware is the only authorization seam — flag private capabilities with an
89
+ empty `middleware` list that touch sensitive data.
90
+ - Custom `expose.http.path` values that land outside `/api/**` and therefore
91
+ escape path-scoped middleware or host rules.
92
+ - Declared vs. actually served: `expose.mcp` in source is what the graph
93
+ claims. A `pracht eval` scenario with `"transport": "mcp"` proves what the
94
+ endpoint answers — it performs a real `initialize` handshake and issues each
95
+ step as a `tools/call`. When the endpoint has `mcp.auth`, set scenario-level
96
+ `mcpHeaders.authorization` so the token is sent on the handshake and every
97
+ later request; do not commit a production token. Run it only against a local
98
+ throwaway server, and only with `read` steps. If the app ships MCP-exposed capabilities with no
99
+ such scenario, report the missing proof: an HTTP-only scenario says nothing
100
+ about whether an MCP host can reach the tool.
101
+
102
+ ## Step 2b: The `/mcp` auth posture
103
+
104
+ `agents: { mcp: {} }` without `auth` is open; authorization rests entirely on
105
+ each tool's named middleware. Report an exposed tool with no middleware as
106
+ `error`; otherwise `warn` and name the middleware carrying the boundary.
107
+
108
+ With `agents.mcp.auth`, check:
109
+
110
+ - `resource` is canonical HTTPS (loopback HTTP only), has no query, fragment,
111
+ or non-root trailing slash, and exactly identifies the endpoint. `/` may
112
+ identify the deployed root; the origin-root identifier is slashless. Aliases,
113
+ query variants, and trailing slashes must 308 to `resource` before challenge.
114
+ - Every `authorizationServers` issuer is canonical HTTPS without query or
115
+ fragment. Reject unknown `agents.mcp`/`auth` keys and API-route collisions.
116
+ - `verify` is a module reference under `src/server`, `src/middleware`, or
117
+ `src/capabilities`, resolves uniquely, and default-exports a function. Inline,
118
+ missing, ambiguous, or non-callable verifiers are `error`. Its request clone
119
+ may consume the body without consuming later JSON-RPC dispatch. Overlapping
120
+ source directories may register the same normalized file more than once;
121
+ that is one verifier, not ambiguity. A `blocked` inspection reason for an
122
+ unusable verifier is conclusive because the adapter server entry cannot
123
+ replace the configured module reference.
124
+ - The verifier binds token audience to `resource`; otherwise tokens for another
125
+ service authenticate here (`error`). Require `requiredScopes` or per-tool
126
+ checks of `context.tokenAuth.scopes` (`warn` otherwise). The initial challenge
127
+ must advertise required scopes; scope tokens follow OAuth's printable-ASCII
128
+ grammar. `context.tokenAuth` is MCP-only and nested calls cannot replace it.
129
+ - Under base `/app/`, resource includes `/app/mcp` while metadata stays at the
130
+ origin-root `/.well-known/oauth-protected-resource/app/mcp`; fetch it and its
131
+ bare alias, ensuring app/static routes cannot shadow either. The bare
132
+ well-known path is reserved and its CORS-open metadata is expected; flag only
133
+ sensitive scope names.
134
+ - `CapabilityAuditEvent` records Web Bot Auth `agent`, not `tokenAuth`. Report
135
+ this as `info`, or `warn` when per-account attribution is required and no
136
+ middleware/capability forwards the principal to an audit sink.
137
+
138
+ ## Step 3: The destructive gate
139
+
140
+ - `webmcp` on a `destructive` capability is rejected by the framework — if you
141
+ find it in source, the build is failing.
142
+ - `mcp` on a `destructive` capability is a **served remote tool** only when the
143
+ manifest sets `agents: { mcp: { destructive: true } }`. Report it as a
144
+ deliberate widening and check both halves: the opt-in, and a
145
+ `setCapabilityApprovalStore()` call the running server actually executes
146
+ (imported by a server entry, a capability module, or applied API/capability
147
+ middleware — a module nothing imports registers nothing). Opt-in without a store is an `error` in your report: the
148
+ endpoint refuses to serve at all, and `pracht verify` only warns (its source
149
+ scan cannot see a registration in a workspace package, so it must not
150
+ hard-block). Two more preconditions fail the endpoint the same way — a
151
+ missing `PRACHT_CONFIRMATION_SECRET`, and `mode: "human"` with neither
152
+ `agents.webBotAuth` with a valid 32-byte base64url Ed25519 static key or HTTPS
153
+ directory nor a principal resolver — so check all three together.
154
+ Runtime-backed `/_pracht` reports a verified endpoint-wide failure by marking
155
+ every MCP exposure `mcp(unserved)`. Graph-only `pracht dev`, `pracht inspect
156
+ capabilities`, `pracht inspect agents`, and MCP inspection use
157
+ `mcp(unverified)` when the same missing
158
+ preconditions may be registered by the adapter server entry they deliberately
159
+ skip. JSON inspection exposes `mcpEndpoint`, `mcpDestructive`,
160
+ `mcpRuntimeStatus`, and `mcpUnavailableReasons`; use those fields instead of
161
+ treating a declared `mcp` transport as proof of reachability. These surfaces
162
+ load applied setup middleware modules without executing the middleware
163
+ functions.
164
+ Destructive `expose.mcp` *without* the opt-in is dead exposure: the tool is
165
+ invisible, and `pracht verify` warns.
166
+ - `PRACHT_CONFIRMATION_SECRET` must be set in the server environment for each
167
+ deployment target (build environment too on Vercel, since it becomes the
168
+ bypass token there). Missing → every destructive call answers
169
+ `403 confirmation_unavailable`.
170
+ - Record the honest limits in the report: the stateless HMAC token is replayable
171
+ within its TTL (default 120 s), the calling agent can hand the token back to
172
+ itself, and without Web Bot Auth or
173
+ `setCapabilityApprovalPrincipalResolver()` both phases run as `"anonymous"`.
174
+ Flag `confirmation: { singleUse: true }` used as if it were durable — it is a
175
+ per-instance in-memory cache, lost on restart.
176
+ - If an approval store is registered, confirm its backend supports atomic
177
+ conditional writes and that all replicas share it: with a store registered, a
178
+ token whose proposal is unknown is refused, so a per-instance store breaks
179
+ commits. `createSqlApprovalStore()` over D1/Postgres/Turso qualifies;
180
+ `createMemoryApprovalStore()` in a deployed multi-replica app does not, and
181
+ neither does a hand-rolled store over Cloudflare KV.
182
+ - `confirmation: { mode: "human" }` without both a store and an authenticated
183
+ principal fails closed — check both exist.
184
+
185
+ ## Step 4: Identity and policy
186
+
187
+ - `agents.webBotAuth.policy: "require"` gates capability HTTP endpoints only —
188
+ pages and API routes are not gated. Flag any assumption that it protects
189
+ pages.
190
+ - `directories` is an allowlist; an empty one means no directory fetching at all
191
+ (deliberate SSRF protection). Flag a directory origin that is not the agent
192
+ ecosystem endpoint the app intends to trust.
193
+ - `agentPolicy: "require"` on a capability while `webBotAuth` is unconfigured
194
+ answers 401 for every caller — a loud misconfiguration, report as `error`.
195
+ - Note the replay property: Pracht's stateless verifier does not enforce `nonce`
196
+ uniqueness, and the default covered components (`@authority`,
197
+ `signature-agent`) bind a signature to a host, not to a method, path, or body.
198
+ Treat a verified identity as authentication, not per-request authorization.
199
+ - Confirm an audit sink exists (`setCapabilityAuditHook()`,
200
+ `addCapabilityAuditListener()`, or `onCapabilityAudit`) — without one there
201
+ is no record of who called what. Grep for all three; `setCapabilityAuditHook`
202
+ is a single slot, so two calls to it mean one sink is silently dead — report
203
+ that as a `warn` and point at `addCapabilityAuditListener(name, hook)`. Also
204
+ flag a computed or non-constant sink name: same-name registration is what
205
+ makes the call idempotent under dev HMR. A module-scope listener must also
206
+ register its unsubscribe with `import.meta.hot.dispose()`; otherwise removing
207
+ the module or renaming the sink leaves the old registration active until the
208
+ dev server restarts.
209
+ - Know what the trail does **not** cover before treating it as a security
210
+ record: a cross-origin 403, an unknown-capability 404, and an unknown or
211
+ unexposed MCP tool name all return *before* dispatch and emit no event. An
212
+ agent enumerating tool names leaves no trace, so never conclude "nothing
213
+ tried" from an empty trail — that question belongs to the HTTP access log.
214
+ - To see the surface actually being exercised rather than merely declared, run
215
+ the app with `pracht dev`, drive the capability, and read the **Agents**
216
+ section of `/_pracht` (JSON under `agentTraffic` at `/_pracht.json`). It
217
+ records transport, `via` for nested composition, verified identity, outcome
218
+ code, and duration — useful for proving a guard actually fires. The page
219
+ counts verified identities, MCP, and MCP-caused composition as
220
+ agent-attributed; shows top-level unsigned HTTP, HTTP-caused composition, and
221
+ client-declared WebMCP markers separately as unverified client dispatches;
222
+ and hides only `invokeCapability()` work with no served-request provenance
223
+ behind a first-party toggle. The JSON keeps everything.
224
+ The traffic buffer outlives app-graph HMR, so retained calls stay visible
225
+ after the final capability is removed, until the dev server restarts.
226
+ It is dev-only, and under adapter-owned dev servers (Cloudflare `workerd`)
227
+ `/_pracht` does not exist at all — a 404 there means the middleware never
228
+ ran, not that no agent traffic occurred.
229
+
230
+ ## Step 5: The discovery surface
231
+
232
+ - `llmsTxt` in the vite config: every listed path is a URL the app *invites* an
233
+ agent to fetch. Cross-check the `exclude` list against routes behind auth
234
+ middleware, internal tooling, and deliberate error routes — nothing about a
235
+ middleware tells the framework whether it gates or merely logs, so an
236
+ auth-gated route missing from `exclude` is a `warn` (see `docs/LLMS_TXT.md`).
237
+ Capabilities appear there with their effect class; destructive ones are
238
+ annotated `requires confirmation`.
239
+ - A collection-driven `llmsTxtArtifacts()` (see `/add-content`) is a second
240
+ generator with its own coverage — compare what each publishes.
241
+ - Routes exporting `markdown` or declaring `markdown: true` serve a Markdown
242
+ representation to agents. Confirm the Markdown variant is not more permissive
243
+ than the HTML page (it carries `Vary: Accept`, so it is separately cached).
244
+ - An enabled OpenAPI document (`/openapi.json`, `dist/client/openapi.json`) is
245
+ public unless the host protects it — check descriptions and examples for
246
+ internal hostnames or credentials, and that "Try it out" mutation endpoints
247
+ carry real authentication.
248
+ - `/.well-known/http-message-signatures-directory` and the MCP endpoint path
249
+ should both be intentional; the MCP endpoint stays active with an empty
250
+ capability graph.
251
+ - `/.well-known/oauth-protected-resource` (and the RFC 9728 path-suffixed form,
252
+ e.g. `/.well-known/oauth-protected-resource/mcp`) is served only when
253
+ `agents.mcp.auth` is configured. Its presence is a *good* signal; its absence
254
+ next to a live `/mcp` means no MCP host can authenticate to the endpoint.
255
+
256
+ ## Step 6: Did this change widen the surface?
257
+
258
+ ```bash
259
+ pracht plan --json --base origin/main
260
+ ```
261
+
262
+ `widensAgentSurface` and the `!` capability lines answer the question a route
263
+ diff cannot: a new exposure, a destructive capability reclassified out of the
264
+ gate, an `agentPolicy` downgraded from `require`, dropped middleware, a
265
+ loosened input schema (dropped `required`, opened `additionalProperties`, raised
266
+ bound), newly enabled `agents.mcp`, newly enabled `agents.mcp.destructive` when
267
+ a declared destructive MCP capability actually exists, OAuth protection
268
+ removed from a still-live MCP endpoint, a removed required scope, or a newly
269
+ trusted authorization server. Enabling the destructive switch in advance,
270
+ with no such tool, is not a widening. The snapshot records the OAuth policy
271
+ separately from the endpoint path, so an unchanged `/mcp` is not evidence that
272
+ the guard stayed the same. Report every widening explicitly, with the
273
+ before/after. A stale snapshot makes this useless — `pracht verify` fails on
274
+ staleness, so trust it only when verify passes.
275
+
276
+ ## Step 7: The no-agent-surface case
277
+
278
+ When the app is supposed to have none:
279
+
280
+ - Confirm the manifest registers no `capabilities` and no `agents`. That lets
281
+ the build define the surface away (~15 KB gzip of an example server bundle).
282
+ - Analysis is one-sided: a spread, a regex literal, or otherwise opaque syntax
283
+ in the manifest leaves the define unset and keeps the runtime in the bundle.
284
+ Flag manifest constructs that defeat the static read.
285
+ - Confirm `llmsTxt` is off if the app should not advertise itself, and that no
286
+ route sets `markdown: true`.
287
+ - `create-pracht --no-agent-tools` controls the *scaffolded developer* tooling
288
+ (`.mcp.json`, skills) — it has nothing to do with the deployed agent surface.
289
+ Do not conflate them in the report.
290
+
291
+ ## Step 8: Report
292
+
293
+ | Surface | Item | Reach | Guard | Severity |
294
+ | ------- | ---- | ----- | ----- | -------- |
295
+
296
+ Severities:
297
+
298
+ - `error` — destructive capability reachable without a configured confirmation
299
+ secret; `agents.mcp.destructive` with no registered approval store, or with a
300
+ memory store on a multi-replica deployment; `agentPolicy: "require"` with no
301
+ `webBotAuth`; capability module unreadable; MCP-exposed capability whose only
302
+ authorization is a cookie session; approval store on a backend without
303
+ conditional writes; unguarded `expose.mcp` tool on an endpoint with neither
304
+ `agents.mcp.auth` nor named middleware; `agents.mcp.auth.verify` that does not
305
+ bind the token audience to `resource`, or that is an inline function in the
306
+ manifest.
307
+ - `warn` — auth-gated route advertised in `llms.txt`; `expose.mcp` with no
308
+ `agents.mcp`; destructive `expose.mcp` with no `agents.mcp.destructive` (dead
309
+ exposure); exposed capability with no named middleware; unbounded output
310
+ (no `limit`/`maximum`); no audit sink; a second `setCapabilityAuditHook()`
311
+ call silently replacing the first; a module-scope listener without HMR
312
+ disposal; `singleUse` treated as durable;
313
+ `agents.mcp.auth` with no `requiredScopes` and no per-capability scope check.
314
+ - `info` — exposure that is intentional and guarded, recorded so the reviewer
315
+ sees the whole surface in one place; framework gaps that are deployment
316
+ responsibilities (rate limiting, write idempotency, result-size limits).
317
+
318
+ ## Rules
319
+
320
+ 1. Report only — never change exposure, policy, or configuration. Propose the
321
+ diff and let the owner apply it.
322
+ 2. Never call a destructive capability to test it, not even the prepare phase,
323
+ against anything but a local throwaway environment.
324
+ 3. Do not treat client-declared signals as trust: the `webmcp` transport marker
325
+ is informational, and only MCP dispatch state is trustworthy for
326
+ attributing nested effects.
327
+ 4. Distinguish `pracht mcp` (the development-time stdio server exposing the app
328
+ *graph* to coding agents) from the deployed `/mcp` endpoint exposing the app's
329
+ *operations*. They have different threat models.
330
+ 5. State the framework guarantee before each finding so the reader can tell an
331
+ opt-out from a hole.
332
+ 6. Pair with `/audit-auth`, `/audit-csrf`, and `/audit-secrets` — this skill
333
+ owns agent reachability, not general request authorization.
334
+
335
+ $ARGUMENTS
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: audit-auth
3
- version: 1.2.3
3
+ version: 1.2.4
4
4
  description: |
5
- Find pracht routes that look protected but aren't — missing auth middleware,
6
- middleware that augments context but never gates, client-side auth checks
7
- with no server enforcement, and API mutations exposed without guards.
8
- Use when asked to "audit auth", "check route protection", "is my dashboard
9
- protected", "find unauthenticated routes", or "review middleware coverage".
5
+ Find pracht routes that look protected but aren't: missing auth middleware,
6
+ middleware that augments context but never gates, client-only checks, and
7
+ unguarded API mutations.
8
+ Use for "audit auth", "check route protection", "find unauthenticated routes",
9
+ "review middleware coverage".
10
10
  allowed-tools:
11
11
  - Bash
12
12
  - Read
@@ -36,8 +36,8 @@ pracht plugin.
36
36
  pracht inspect routes --json
37
37
  ```
38
38
 
39
- If the pracht MCP server is registered (see `docs/MCP.md`), prefer its tools
40
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`) over
39
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
40
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify` tools over
41
41
  shelling out.
42
42
 
43
43
  Middleware is registered by name in the app manifest —
@@ -116,8 +116,12 @@ target. From `pracht inspect api --json`:
116
116
  - Inspect every HTTP-, WebMCP-, or MCP-exposed capability body for
117
117
  `invokeCapability()`. Direct composition never re-applies app-level API
118
118
  middleware. Remote MCP additionally re-applies the callee's `agentPolicy`
119
- and refuses destructive callees, but private non-destructive capabilities
120
- stay composable and rely on their named middleware for authorization. For
119
+ and refuses destructive callees unless the tool being served is itself a
120
+ destructive capability that already cleared prepare/commit — a request-scoped
121
+ grant over every destructive callee, private ones included, so audit a
122
+ confirmed destructive tool's body the way you would a confirmed HTTP
123
+ endpoint's. Private non-destructive capabilities stay composable and rely on
124
+ their named middleware for authorization. For
121
125
  HTTP/WebMCP composition, flag sensitive callees whose required transport
122
126
  authorization or approval is absent from the composing capability and the
123
127
  callee's named middleware.
@@ -168,7 +172,8 @@ Severity is the primary scale; the verdict is a secondary domain label:
168
172
  listed but not flagged.
169
173
  5. Do not auto-add middleware. Auth wiring is policy.
170
174
  6. Treat allowed composed capability reachability as transitive. MCP blocks
171
- destructive callees and re-applies `agentPolicy`; named middleware remains
175
+ destructive callees unless the served tool already cleared its own
176
+ confirmation gate, and re-applies `agentPolicy`; named middleware remains
172
177
  the authorization seam for private non-destructive composition. Audit events
173
178
  identify every nested attempt with `transport: "server"` and trusted request
174
179
  provenance in `via`, but observability is not an authorization gate.
@@ -1,13 +1,12 @@
1
1
  ---
2
2
  name: audit-bundles
3
- version: 1.2.0
3
+ version: 1.2.1
4
4
  description: |
5
- Analyze a pracht production build. Report client bundle size per route,
6
- flag fat vendor chunks, find route components that ship large dependencies,
7
- and suggest dynamic `import()` and prefetch strategies based on observed
8
- navigation patterns.
9
- Use when asked to "audit bundles", "why is my JS so big", "bundle size per
10
- route", "what's in my vendor chunk", or "tune prefetching".
5
+ Analyze a pracht production build: client bytes per route, fat vendor chunks,
6
+ routes shipping large dependencies, and dynamic `import()` / prefetch
7
+ recommendations.
8
+ Use for "audit bundles", "why is my JS so big", "bundle size per route", "what's
9
+ in my vendor chunk", "tune prefetching".
11
10
  allowed-tools:
12
11
  - Bash
13
12
  - Read
@@ -23,11 +22,10 @@ and surfaces the worst offenders.
23
22
 
24
23
  ## Step 1: Build with analysis
25
24
 
26
- If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
27
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`) over
28
- shelling out. Prerequisites: `pracht inspect` needs a vite config with the
29
- pracht plugin; `pracht inspect build` reads artifacts from a prior
30
- `pracht build`.
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
27
+ shelling out. `pracht inspect` needs the pracht plugin in the vite config.
28
+ `inspect build` needs a prior `pracht build`.
31
29
 
32
30
  ```bash
33
31
  pracht build --analyze --json
@@ -137,6 +135,52 @@ overrides the route-level strategy for a single anchor (and accepts the extra
137
135
  option), so a route can stay on `"intent"` while its primary-nav link opts
138
136
  into `"viewport"` or `"render"`.
139
137
 
138
+ If every route ends up on `"none"`, do not stop there — the prefetch listeners
139
+ still ship, in a chunk the router lazily imports on every page. Recommend
140
+ `pracht({ client: { prefetch: false } })` instead, which compiles the whole
141
+ mechanism out (~2.6 KB gzip and one fewer request). Confirm nothing relies on
142
+ prefetching first: the router silently stops honouring `route({ prefetch })` and
143
+ `<Link prefetch>`, and the imperative `prefetch()` export becomes a no-op. See
144
+ `docs/PERFORMANCE.md#switching-off-js-prefetching`.
145
+
146
+ ## Step 6b: Chunk-group composition
147
+
148
+ App-level chunking and pracht's vendor chunk coexist — recommend it freely.
149
+ Pracht reads `build.rollupOptions.output` and contributes its Preact group in
150
+ whichever form the app used, so adding a group (to merge the long tail of small
151
+ initial chunks, say) does not cost the framework chunk:
152
+
153
+ ```ts
154
+ // vite.config.ts
155
+ build: {
156
+ rollupOptions: {
157
+ output: {
158
+ codeSplitting: { groups: [{ name: "app", tags: ["$initial"], minSize: 20_000 }] },
159
+ },
160
+ },
161
+ }
162
+ ```
163
+
164
+ Two things to check when an app already configures chunking:
165
+
166
+ - **Mixed forms.** Rolldown lets `codeSplitting` silence both `advancedChunks`
167
+ and `manualChunks`. An app that sets more than one is running only
168
+ `codeSplitting`; report the dead config as `warn`.
169
+ - **A group that swallows Preact.** Precedence is higher `priority` first, then
170
+ declaration order, with pracht's group last — so an app group matching
171
+ `node_modules` at equal priority absorbs the framework runtime. If a separate,
172
+ long-cached framework chunk was the intent, recommend a `priority` bump or a
173
+ `test` that excludes Preact.
174
+
175
+ Verify any grouping change against the prerendered HTML, not just the numbers:
176
+ a group broad enough to reshuffle entry chunks can drop the stylesheet links
177
+ pracht injects per route. Compare `<link rel="stylesheet">` tags in
178
+ `dist/client/**/index.html` before and after.
179
+
180
+ `pracht({ vendorChunk: false })` makes pracht contribute nothing, for an app
181
+ that wants Preact inside its own chunks or places `frameworkChunkGroups()`
182
+ itself.
183
+
140
184
  ## Step 7: Report
141
185
 
142
186
  Three sections:
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  name: audit-csrf
3
- version: 1.2.0
3
+ version: 1.2.1
4
4
  description: |
5
- Inventory every form submission and mutation API in the project, then verify
6
- the CSRF posture. Pracht enforces same-origin on mutation API requests by
7
- default (`api.requireSameOrigin`); this skill checks that the default is
8
- intact and that cookie strategy, middleware, or tokens cover whatever the
9
- built-in check does not.
10
- Use when asked to "audit CSRF", "check CSRF protection", "are forms safe",
11
- "review session security", or after enabling cross-origin form usage.
5
+ Check CSRF posture across every form and mutation API. Pracht enforces
6
+ same-origin on mutation API requests by default (`api.requireSameOrigin`); this
7
+ verifies the default is intact and that cookies, middleware, or tokens cover
8
+ what it does not.
9
+ Use for "audit CSRF", "check CSRF protection", "are forms safe", "review session
10
+ security".
12
11
  allowed-tools:
13
12
  - Bash
14
13
  - Read
@@ -71,8 +70,8 @@ Grep for `<Form ` across `src/`. For each occurrence:
71
70
  pracht inspect api --json
72
71
  ```
73
72
 
74
- If the pracht MCP server is registered (see `docs/MCP.md`), prefer its tools
75
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`) over
73
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
74
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify` tools over
76
75
  shelling out.
77
76
 
78
77
  For each API route, read the exported `methods`. Mutation methods: `POST`,
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: audit-deps
3
- version: 1.1.0
3
+ version: 1.1.1
4
4
  description: |
5
- Run a dependency vulnerability audit and map each finding to the pracht
6
- routes, loaders, middleware, or API handlers that import the affected
7
- package — so users know which surface area they need to test after upgrading.
8
- Use when asked to "audit deps", "scan for CVEs", "which routes use this
9
- vulnerable package", "npm audit", or "dependency security review".
5
+ Run a dependency vulnerability audit and map each advisory to the pracht routes,
6
+ loaders, middleware, and API handlers that import it — so you know what to
7
+ retest after upgrading.
8
+ Use for "audit deps", "scan for CVEs", "npm audit", "which routes use this
9
+ vulnerable package".
10
10
  allowed-tools:
11
11
  - Bash
12
12
  - Read
@@ -56,8 +56,8 @@ is the one the user owns.
56
56
 
57
57
  ## Step 3: Map to routes/APIs
58
58
 
59
- If the pracht MCP server is registered (see `docs/MCP.md`), prefer its tools
60
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`) over
59
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
60
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify` tools over
61
61
  shelling out.
62
62
 
63
63
  For each direct dependency identified in step 2:
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  name: audit-headers
3
- version: 1.2.0
3
+ version: 1.2.1
4
4
  description: |
5
- Audit security header coverage in a pracht app. The framework applies four
6
- default security headers on every response path; this skill audits the
7
- exceptions — static output served outside first-party adapters, `headers()`
8
- exports that weaken the defaults, and the headers only the user can decide
9
- (HSTS, CSP).
10
- Use when asked to "audit security headers", "check CSP", "harden headers",
11
- "set up HSTS", or "review header policy".
5
+ Audit security headers in a pracht app. The framework sets four defaults on
6
+ every response path; this covers the exceptions — static output served outside
7
+ first-party adapters, `headers()` exports that weaken defaults, and the choices
8
+ only you can make (HSTS, CSP).
9
+ Use for "audit security headers", "check CSP", "harden headers", "set up HSTS",
10
+ "review header policy".
12
11
  allowed-tools:
13
12
  - Bash
14
13
  - Read
@@ -62,8 +61,8 @@ pracht inspect routes --json
62
61
  pracht inspect api --json
63
62
  ```
64
63
 
65
- If the pracht MCP server is registered (see `docs/MCP.md`), prefer its tools
66
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`) over
64
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
65
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify` tools over
67
66
  shelling out.
68
67
 
69
68
  Only route modules and shells have a `headers()` export — API handlers do not
@@ -1,14 +1,13 @@
1
1
  ---
2
2
  name: audit-islands
3
- version: 1.0.0
3
+ version: 1.0.1
4
4
  description: |
5
- Audit pracht islands usage: find over-hydrated routes that should use
6
- `hydration: "islands"` or `"none"`, dead interactivity outside the islands
7
- directory, non-serializable island props, mis-tuned client strategies, and
8
- invalid render/hydration combinations.
9
- Use when asked to "audit islands", "reduce hydration", "why is this island
10
- not interactive", "should this page be an island", or "check partial
11
- hydration".
5
+ Audit pracht hydration: over-hydrated routes that should be `hydration:
6
+ "islands"` or `"none"`, dead interactivity outside the islands directory,
7
+ non-serializable island props, mis-tuned `client` strategies, and invalid
8
+ render/hydration pairs.
9
+ Use for "audit islands", "reduce hydration", "why is this island not
10
+ interactive", "check partial hydration".
12
11
  allowed-tools:
13
12
  - Bash
14
13
  - Read
@@ -26,8 +25,8 @@ islands wired in ways that break at render time or ship dead handlers.
26
25
 
27
26
  ## Step 1: Enumerate routes and hydration modes
28
27
 
29
- If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
30
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`) over
28
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
29
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify` tools over
31
30
  shelling out.
32
31
 
33
32
  ```bash