claude-code-pilot 3.2.0 → 3.3.0
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/CHANGELOG.md +57 -0
- package/README.md +14 -9
- package/bin/install.js +113 -15
- package/manifest.json +18 -3
- package/package.json +3 -2
- package/src/agents/django-build-resolver.md +252 -0
- package/src/agents/django-reviewer.md +169 -0
- package/src/agents/fastapi-reviewer.md +79 -0
- package/src/agents/fsharp-reviewer.md +109 -0
- package/src/agents/swift-build-resolver.md +170 -0
- package/src/agents/swift-reviewer.md +116 -0
- package/src/commands/ccp/cost-report.md +107 -0
- package/src/commands/ccp/intel.md +3 -3
- package/src/commands/ccp/mvp-phase.md +45 -0
- package/src/commands/ccp/plan-prd.md +160 -0
- package/src/commands/ccp/pr-ecc.md +184 -0
- package/src/commands/ccp/security-scan.md +74 -0
- package/src/hooks/ccp-bash-hook-dispatcher.js +96 -0
- package/src/hooks/ccp-context-monitor.js +23 -0
- package/src/hooks/ccp-doc-file-warning.js +93 -0
- package/src/hooks/ccp-pre-bash-dispatcher.js +24 -0
- package/src/hooks/ccp-write-gateguard.js +868 -0
- package/src/lib/project-detect.js +0 -2
- package/src/lib/shell-substitution.js +499 -0
- package/src/pilot/references/execute-mvp-tdd.md +81 -0
- package/src/pilot/references/mvp-concepts.md +49 -0
- package/src/pilot/references/planner-graphify-auto-update.md +67 -0
- package/src/pilot/references/planner-human-verify-mode.md +57 -0
- package/src/pilot/references/planner-mvp-mode.md +53 -0
- package/src/pilot/references/skeleton-template.md +48 -0
- package/src/pilot/references/spidr-splitting.md +69 -0
- package/src/pilot/references/user-story-template.md +58 -0
- package/src/pilot/references/verify-mvp-mode.md +85 -0
- package/src/pilot/references/worktree-path-safety.md +89 -0
- package/src/pilot/workflows/help.md +5 -0
- package/src/pilot/workflows/mvp-phase.md +199 -0
- package/src/skills/agent-architecture-audit/SKILL.md +256 -0
- package/src/skills/agent-harness-design/SKILL.md +73 -0
- package/src/skills/angular-developer/SKILL.md +154 -0
- package/src/skills/angular-developer/references/angular-animations.md +160 -0
- package/src/skills/angular-developer/references/angular-aria.md +410 -0
- package/src/skills/angular-developer/references/cli.md +86 -0
- package/src/skills/angular-developer/references/component-harnesses.md +59 -0
- package/src/skills/angular-developer/references/component-styling.md +91 -0
- package/src/skills/angular-developer/references/components.md +117 -0
- package/src/skills/angular-developer/references/creating-services.md +97 -0
- package/src/skills/angular-developer/references/data-resolvers.md +69 -0
- package/src/skills/angular-developer/references/define-routes.md +67 -0
- package/src/skills/angular-developer/references/defining-providers.md +72 -0
- package/src/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/src/skills/angular-developer/references/e2e-testing.md +56 -0
- package/src/skills/angular-developer/references/effects.md +83 -0
- package/src/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/src/skills/angular-developer/references/host-elements.md +80 -0
- package/src/skills/angular-developer/references/injection-context.md +63 -0
- package/src/skills/angular-developer/references/inputs.md +101 -0
- package/src/skills/angular-developer/references/linked-signal.md +59 -0
- package/src/skills/angular-developer/references/loading-strategies.md +61 -0
- package/src/skills/angular-developer/references/mcp.md +108 -0
- package/src/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/src/skills/angular-developer/references/outputs.md +86 -0
- package/src/skills/angular-developer/references/reactive-forms.md +122 -0
- package/src/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/src/skills/angular-developer/references/resource.md +77 -0
- package/src/skills/angular-developer/references/route-animations.md +56 -0
- package/src/skills/angular-developer/references/route-guards.md +52 -0
- package/src/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/src/skills/angular-developer/references/router-testing.md +87 -0
- package/src/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/src/skills/angular-developer/references/signal-forms.md +795 -0
- package/src/skills/angular-developer/references/signals-overview.md +94 -0
- package/src/skills/angular-developer/references/tailwind-css.md +69 -0
- package/src/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/src/skills/angular-developer/references/testing-fundamentals.md +65 -0
- package/src/skills/error-handling/SKILL.md +376 -0
- package/src/skills/fastapi-patterns/SKILL.md +327 -0
- package/src/skills/flox-environments/SKILL.md +496 -0
- package/src/skills/fsharp-testing/SKILL.md +280 -0
- package/src/skills/ios-icon-gen/SKILL.md +157 -0
- package/src/skills/ios-icon-gen/scripts/generate_icons.swift +258 -0
- package/src/skills/ios-icon-gen/scripts/iconify_gen.sh +235 -0
- package/src/skills/make-interfaces-feel-better/SKILL.md +151 -0
- package/src/skills/mysql-patterns/SKILL.md +412 -0
- package/src/skills/plan-orchestrate/SKILL.md +220 -0
- package/src/skills/prisma-patterns/SKILL.md +371 -0
- package/src/skills/production-audit/SKILL.md +206 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/candidate-playbook.md +49 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/report.json +35 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/scenario.json +62 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/trace.json +45 -0
- package/src/skills/security-scan/references/agentshield-policy-exception/verifier-result.json +35 -0
- package/src/skills/vite-patterns/SKILL.md +449 -0
- package/src/skills/windows-desktop-e2e/SKILL.md +887 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prisma-patterns
|
|
3
|
+
description: Prisma ORM patterns for TypeScript backends — schema design, query optimization, transactions, pagination, and critical traps like updateMany returning count not records, $transaction timeouts, migrate dev resetting the DB, @updatedAt skipped on bulk writes, and serverless connection exhaustion.
|
|
4
|
+
origin: ECC
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Prisma Patterns
|
|
8
|
+
|
|
9
|
+
Production patterns and non-obvious traps for Prisma ORM in TypeScript backends.
|
|
10
|
+
Tested against Prisma 5.x and 6.x. Some behaviors differ from Prisma 4.
|
|
11
|
+
|
|
12
|
+
Check the Prisma version before applying version-specific patterns:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx prisma --version
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Prisma 5 introduced `relationJoins`, which can load relations via JOIN rather than separate queries depending on query strategy and configuration. The `omit` field modifier and `prisma.$extends` Client Extensions API were also added. Note: `relationJoins` can cause row explosion on large 1:N relations or deep nested `include` — benchmark both approaches when relations may return many rows per parent.
|
|
19
|
+
|
|
20
|
+
## When to Activate
|
|
21
|
+
|
|
22
|
+
- Designing or modifying Prisma schema models and relations
|
|
23
|
+
- Writing queries, transactions, or pagination logic
|
|
24
|
+
- Using `updateMany`, `deleteMany`, or any bulk operation
|
|
25
|
+
- Running or planning database migrations
|
|
26
|
+
- Deploying to serverless environments (Vercel, Lambda, Cloudflare Workers)
|
|
27
|
+
- Implementing soft delete or multi-tenant row filtering
|
|
28
|
+
|
|
29
|
+
## Core Concepts
|
|
30
|
+
|
|
31
|
+
### ID Strategy
|
|
32
|
+
|
|
33
|
+
| Strategy | Use When | Avoid When |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `@default(cuid())` | Default choice — URL-safe, sortable, no collisions | Sequential IDs needed for external systems |
|
|
36
|
+
| `@default(uuid())` | Interoperability with non-Prisma systems required | High-write tables (random UUIDs fragment B-tree indexes) |
|
|
37
|
+
| `@default(autoincrement())` | Internal join tables, audit logs | Public-facing IDs (exposes record count) |
|
|
38
|
+
|
|
39
|
+
### Schema Defaults
|
|
40
|
+
|
|
41
|
+
```prisma
|
|
42
|
+
model User {
|
|
43
|
+
id String @id @default(cuid())
|
|
44
|
+
email String @unique // @unique already creates an index — no @@index needed
|
|
45
|
+
name String
|
|
46
|
+
role Role @default(USER)
|
|
47
|
+
posts Post[]
|
|
48
|
+
createdAt DateTime @default(now())
|
|
49
|
+
updatedAt DateTime @updatedAt
|
|
50
|
+
deletedAt DateTime?
|
|
51
|
+
|
|
52
|
+
@@index([createdAt])
|
|
53
|
+
@@index([deletedAt, createdAt]) // composite for soft-delete + sort queries
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- Add `@@index` on every foreign key and column used in `WHERE` or `ORDER BY`.
|
|
58
|
+
- Declare `deletedAt DateTime?` upfront when soft delete is a foreseeable requirement — adding it later requires a migration on a live table.
|
|
59
|
+
- `updatedAt @updatedAt` is set automatically by Prisma on `update` and `upsert` only (see Anti-Patterns for bulk update trap).
|
|
60
|
+
|
|
61
|
+
### `include` vs `select`
|
|
62
|
+
|
|
63
|
+
| | `include` | `select` |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Returns | All scalar fields + specified relations | Only specified fields |
|
|
66
|
+
| Use when | You need most fields plus a relation | Hot paths, large tables, avoiding over-fetch |
|
|
67
|
+
| Performance | May over-fetch on wide tables | Minimal payload, faster on large datasets |
|
|
68
|
+
| Prisma 5 note | Uses JOIN by default (`relationJoins`) | Same |
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// include — all columns + relation
|
|
72
|
+
const user = await prisma.user.findUnique({
|
|
73
|
+
where: { id },
|
|
74
|
+
include: { posts: { select: { id: true, title: true } } },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// select — explicit allowlist
|
|
78
|
+
const user = await prisma.user.findUnique({
|
|
79
|
+
where: { id },
|
|
80
|
+
select: { id: true, email: true, name: true },
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Never return raw Prisma entities from API responses — map to response DTOs to control exposed fields:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// BAD: leaks passwordHash, deletedAt, internal fields
|
|
88
|
+
return await prisma.user.findUniqueOrThrow({ where: { id } });
|
|
89
|
+
|
|
90
|
+
// GOOD: explicit DTO mapping
|
|
91
|
+
const user = await prisma.user.findUniqueOrThrow({ where: { id } });
|
|
92
|
+
return { id: user.id, name: user.name, email: user.email };
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Transaction Form Selection
|
|
96
|
+
|
|
97
|
+
| Situation | Use |
|
|
98
|
+
|---|---|
|
|
99
|
+
| Independent operations, no inter-dependency | Array form |
|
|
100
|
+
| Later step depends on earlier result | Interactive form |
|
|
101
|
+
| External calls (email, HTTP) involved | Outside transaction entirely |
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// Array form — batched in one round trip
|
|
105
|
+
const [user, post] = await prisma.$transaction([
|
|
106
|
+
prisma.user.update({ where: { id }, data: { name } }),
|
|
107
|
+
prisma.post.create({ data: { title, authorId: id } }),
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
// Interactive form — use tx client only, never the outer prisma client
|
|
111
|
+
const post = await prisma.$transaction(async (tx) => {
|
|
112
|
+
const user = await tx.user.findUniqueOrThrow({ where: { id } });
|
|
113
|
+
if (user.role !== 'ADMIN') throw new Error('Forbidden');
|
|
114
|
+
return tx.post.create({ data: { title, authorId: user.id } });
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### PrismaClient Singleton
|
|
119
|
+
|
|
120
|
+
Each `PrismaClient` instance opens its own connection pool. Instantiate once.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// lib/prisma.ts
|
|
124
|
+
import { PrismaClient } from '@prisma/client';
|
|
125
|
+
|
|
126
|
+
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
|
|
127
|
+
|
|
128
|
+
export const prisma =
|
|
129
|
+
globalForPrisma.prisma ??
|
|
130
|
+
new PrismaClient({
|
|
131
|
+
log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The `globalThis` pattern prevents duplicate instances during hot reload (Next.js, nodemon, ts-node-dev).
|
|
138
|
+
|
|
139
|
+
### N+1 Problem
|
|
140
|
+
|
|
141
|
+
Loading relations inside a loop issues one query per row.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// BAD: N+1 — one extra query per user
|
|
145
|
+
const users = await prisma.user.findMany();
|
|
146
|
+
for (const user of users) {
|
|
147
|
+
const posts = await prisma.post.findMany({ where: { authorId: user.id } });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// GOOD: single query
|
|
151
|
+
const users = await prisma.user.findMany({ include: { posts: true } });
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
With Prisma 5+ `relationJoins`, the `include` form uses a single JOIN. On large 1:N sets this may increase result set size — benchmark both approaches if the relation can return many rows per parent.
|
|
155
|
+
|
|
156
|
+
## Code Examples
|
|
157
|
+
|
|
158
|
+
### Cursor Pagination (preferred for feeds and large datasets)
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
async function getPosts(cursor?: string, limit = 20) {
|
|
162
|
+
const items = await prisma.post.findMany({
|
|
163
|
+
where: { published: true },
|
|
164
|
+
orderBy: [
|
|
165
|
+
{ createdAt: 'desc' },
|
|
166
|
+
{ id: 'desc' }, // secondary sort prevents unstable pagination on duplicate timestamps
|
|
167
|
+
],
|
|
168
|
+
take: limit + 1,
|
|
169
|
+
...(cursor && { cursor: { id: cursor }, skip: 1 }),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const hasNextPage = items.length > limit;
|
|
173
|
+
if (hasNextPage) items.pop();
|
|
174
|
+
|
|
175
|
+
return { items, nextCursor: hasNextPage ? items[items.length - 1].id : null };
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Fetch `limit + 1` and pop — canonical way to detect `hasNextPage` without an extra count query. Always include a unique field (e.g. `id`) as a secondary `orderBy` to prevent unstable pagination when multiple rows share the same timestamp. Use offset pagination only when users need to jump to arbitrary pages (admin tables).
|
|
180
|
+
|
|
181
|
+
### Soft Delete
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
// Always filter explicitly — do not rely on middleware (hides behavior, hard to debug)
|
|
185
|
+
const activeUsers = await prisma.user.findMany({ where: { deletedAt: null } });
|
|
186
|
+
|
|
187
|
+
await prisma.user.update({ where: { id }, data: { deletedAt: new Date() } });
|
|
188
|
+
await prisma.user.update({ where: { id }, data: { deletedAt: null } }); // restore
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Error Handling
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { Prisma } from '@prisma/client';
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await prisma.user.create({ data: { email } });
|
|
198
|
+
} catch (e) {
|
|
199
|
+
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
|
200
|
+
if (e.code === 'P2002') throw new ConflictError('Email already exists');
|
|
201
|
+
if (e.code === 'P2025') throw new NotFoundError('Record not found');
|
|
202
|
+
if (e.code === 'P2003') throw new BadRequestError('Referenced record does not exist');
|
|
203
|
+
}
|
|
204
|
+
throw e;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Common codes: `P2002` unique violation · `P2025` not found · `P2003` foreign key violation.
|
|
209
|
+
|
|
210
|
+
Catch at the service boundary and translate to domain errors. Never expose raw Prisma messages to API consumers.
|
|
211
|
+
|
|
212
|
+
### Connection Pool — Serverless
|
|
213
|
+
|
|
214
|
+
Embed connection params directly in `DATABASE_URL` — string concatenation breaks if the URL already has query parameters (e.g. `?schema=public`):
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# .env — preferred: embed params in the URL
|
|
218
|
+
DATABASE_URL="postgresql://user:pass@host/db?connection_limit=1&pool_timeout=20"
|
|
219
|
+
|
|
220
|
+
# With an external pooler (PgBouncer, Supabase pooler)
|
|
221
|
+
DATABASE_URL="postgresql://user:pass@host/db?pgbouncer=true&connection_limit=1"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// Vercel, AWS Lambda, and similar serverless runtimes: cap pool to 1 per instance
|
|
226
|
+
// connection_limit and pool_timeout are controlled via DATABASE_URL
|
|
227
|
+
const prisma = new PrismaClient();
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Anti-Patterns
|
|
231
|
+
|
|
232
|
+
### `updateMany` returns a count, not records
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// BAD: result is { count: 2 } — users[0] is undefined
|
|
236
|
+
const users = await prisma.user.updateMany({ where: { role: 'GUEST' }, data: { role: 'USER' } });
|
|
237
|
+
|
|
238
|
+
// GOOD: capture IDs first, then update, then fetch only the affected rows
|
|
239
|
+
const targets = await prisma.user.findMany({
|
|
240
|
+
where: { role: 'GUEST' },
|
|
241
|
+
select: { id: true },
|
|
242
|
+
});
|
|
243
|
+
const ids = targets.map((u) => u.id);
|
|
244
|
+
await prisma.user.updateMany({ where: { id: { in: ids } }, data: { role: 'USER' } });
|
|
245
|
+
const updated = await prisma.user.findMany({ where: { id: { in: ids } } });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Same applies to `deleteMany` — returns `{ count: n }`, never the deleted rows.
|
|
249
|
+
|
|
250
|
+
### `$transaction` interactive form times out after 5 seconds
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
// BAD: external call inside transaction exceeds 5s default → "Transaction already closed"
|
|
254
|
+
await prisma.$transaction(async (tx) => {
|
|
255
|
+
const user = await tx.user.findUniqueOrThrow({ where: { id } });
|
|
256
|
+
await sendWelcomeEmail(user.email); // external call
|
|
257
|
+
await tx.user.update({ where: { id }, data: { emailSent: true } });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// GOOD: external calls outside the transaction
|
|
261
|
+
const user = await prisma.user.findUniqueOrThrow({ where: { id } });
|
|
262
|
+
await sendWelcomeEmail(user.email);
|
|
263
|
+
await prisma.user.update({ where: { id }, data: { emailSent: true } });
|
|
264
|
+
|
|
265
|
+
// Only raise timeout when bulk processing genuinely needs it
|
|
266
|
+
await prisma.$transaction(async (tx) => { ... }, { timeout: 30_000 });
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### `migrate dev` can reset the database
|
|
270
|
+
|
|
271
|
+
`migrate dev` detects schema drift and may prompt to reset the DB, dropping all data.
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# NEVER on shared dev, staging, or production
|
|
275
|
+
npx prisma migrate dev --name add_column
|
|
276
|
+
|
|
277
|
+
# Safe everywhere except local solo dev
|
|
278
|
+
npx prisma migrate deploy
|
|
279
|
+
|
|
280
|
+
# Check drift without applying
|
|
281
|
+
npx prisma migrate diff \
|
|
282
|
+
--from-migrations ./prisma/migrations \
|
|
283
|
+
--to-schema-datamodel ./prisma/schema.prisma \
|
|
284
|
+
--shadow-database-url "$SHADOW_DATABASE_URL"
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Manually editing a migration file breaks future deploys
|
|
288
|
+
|
|
289
|
+
Prisma checksums every migration file. Editing after apply causes `P3006 checksum mismatch` on every environment where the original already ran. Create a new migration instead.
|
|
290
|
+
|
|
291
|
+
### Breaking schema changes require multi-step migration
|
|
292
|
+
|
|
293
|
+
Adding `NOT NULL` to an existing column or renaming a column in one migration will lock the table or drop data. Use expand-and-contract:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
# Step 1: create migration locally, then deploy
|
|
297
|
+
npx prisma migrate dev --name add_new_column # local only
|
|
298
|
+
npx prisma migrate deploy # staging / production
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
// Step 2: backfill data (run in a script or migration job, not in the shell)
|
|
303
|
+
await prisma.user.updateMany({ data: { newColumn: derivedValue } });
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
# Step 3: create the NOT NULL constraint migration locally, then deploy
|
|
308
|
+
npx prisma migrate dev --name make_new_column_required # local only
|
|
309
|
+
npx prisma migrate deploy # staging / production
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### `@updatedAt` does not fire on `updateMany`
|
|
313
|
+
|
|
314
|
+
`@updatedAt` is set automatically only on `update` and `upsert`. Bulk writes leave it stale.
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
// BAD: updatedAt stays at its old value
|
|
318
|
+
await prisma.post.updateMany({ where: { authorId }, data: { published: true } });
|
|
319
|
+
|
|
320
|
+
// GOOD
|
|
321
|
+
await prisma.post.updateMany({
|
|
322
|
+
where: { authorId },
|
|
323
|
+
data: { published: true, updatedAt: new Date() },
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Soft delete + `findUniqueOrThrow` leaks deleted records
|
|
328
|
+
|
|
329
|
+
`findUniqueOrThrow` throws `P2025` only when the row does not exist in the DB. Soft-deleted rows still exist and are returned without error.
|
|
330
|
+
|
|
331
|
+
`findUniqueOrThrow` requires a unique constraint field in `where` — adding `deletedAt: null` alongside `id` breaks the type because `{ id, deletedAt }` is not a compound unique constraint. Use `findFirstOrThrow` instead.
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
// BAD: returns soft-deleted user
|
|
335
|
+
const user = await prisma.user.findUniqueOrThrow({ where: { id } });
|
|
336
|
+
|
|
337
|
+
// BAD: Prisma type error — { id, deletedAt } is not a unique constraint
|
|
338
|
+
const user = await prisma.user.findUniqueOrThrow({ where: { id, deletedAt: null } });
|
|
339
|
+
|
|
340
|
+
// GOOD: findFirstOrThrow supports arbitrary where conditions
|
|
341
|
+
const user = await prisma.user.findFirstOrThrow({ where: { id, deletedAt: null } });
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### `deleteMany` without `where` deletes every row
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
// BAD: silently wipes the table
|
|
348
|
+
await prisma.post.deleteMany();
|
|
349
|
+
|
|
350
|
+
// GOOD
|
|
351
|
+
await prisma.post.deleteMany({ where: { authorId: userId } });
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Best Practices
|
|
355
|
+
|
|
356
|
+
| Rule | Reason |
|
|
357
|
+
|---|---|
|
|
358
|
+
| `migrate deploy` in CI/CD, `migrate dev` only locally | `migrate dev` can reset the DB on drift |
|
|
359
|
+
| Map entities to response DTOs | Prevents leaking internal fields |
|
|
360
|
+
| Catch `PrismaClientKnownRequestError` at service boundary | Translate to domain errors |
|
|
361
|
+
| Prefer `*OrThrow` methods over manual null checks | Throws P2025 automatically; use `findFirstOrThrow` when filtering non-unique fields |
|
|
362
|
+
| `connection_limit=1` + external pooler in serverless | Prevents connection exhaustion |
|
|
363
|
+
| Always provide `where` on `deleteMany` | Prevents accidental table wipe |
|
|
364
|
+
| Set `updatedAt: new Date()` manually in `updateMany` | `@updatedAt` skips bulk writes |
|
|
365
|
+
|
|
366
|
+
## Related Skills
|
|
367
|
+
|
|
368
|
+
- `nestjs-patterns` — NestJS service layer that integrates Prisma
|
|
369
|
+
- `postgres-patterns` — PostgreSQL-level indexing and connection tuning
|
|
370
|
+
- `database-migrations` — multi-step migration planning for production
|
|
371
|
+
- `backend-patterns` — general API and service layer design
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: production-audit
|
|
3
|
+
description: Local-evidence production readiness audit for shipped apps, pre-launch reviews, post-merge checks, and "what breaks in prod?" questions without sending repo data to an external audit service.
|
|
4
|
+
origin: community
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Production Audit
|
|
8
|
+
|
|
9
|
+
Use this skill when the user asks whether an application is ready to ship, what
|
|
10
|
+
could break in production, or what must be fixed before a launch. This is a
|
|
11
|
+
maintainer-safe rewrite of the stale community production-audit idea: it keeps
|
|
12
|
+
the useful production-readiness lens and removes unpinned external execution and
|
|
13
|
+
third-party data sharing.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- The user asks "is this production-ready", "what would break in prod", "what
|
|
18
|
+
did we miss", "audit this repo", or "ready to ship?"
|
|
19
|
+
- A feature was merged and needs a pre-deploy or post-merge risk pass.
|
|
20
|
+
- A public launch, demo, customer rollout, or investor walkthrough is close.
|
|
21
|
+
- CI is green but the user wants production risk, not only test status.
|
|
22
|
+
- A deployed URL, release branch, PR, or current checkout is available for
|
|
23
|
+
evidence gathering.
|
|
24
|
+
|
|
25
|
+
## When Not to Use
|
|
26
|
+
|
|
27
|
+
- During active implementation when the right lens is line-level secure coding;
|
|
28
|
+
use `security-review` first.
|
|
29
|
+
- For pure libraries, templates, docs-only repos, or scaffolds unless the user
|
|
30
|
+
wants packaging/release readiness rather than application readiness.
|
|
31
|
+
- When the user asks for a formal compliance audit. This skill is engineering
|
|
32
|
+
triage, not legal, financial, medical, or regulatory certification.
|
|
33
|
+
- When the only available evidence is a product idea with no repo, deployment,
|
|
34
|
+
CI, or runtime surface.
|
|
35
|
+
|
|
36
|
+
## How It Works
|
|
37
|
+
|
|
38
|
+
Build the audit from local and user-authorized evidence. Do not run unpinned
|
|
39
|
+
remote code, upload repository contents to third-party services, or call
|
|
40
|
+
external scanners unless the user explicitly approves that specific tool and
|
|
41
|
+
data flow.
|
|
42
|
+
|
|
43
|
+
Use this order:
|
|
44
|
+
|
|
45
|
+
1. Establish the release surface.
|
|
46
|
+
2. Read recent changes and current branch state.
|
|
47
|
+
3. Inspect runtime, auth, data, payment, background-job, AI, and deployment
|
|
48
|
+
boundaries that actually exist in the repo.
|
|
49
|
+
4. Check CI, tests, migrations, environment documentation, and rollback path.
|
|
50
|
+
5. Produce a short ship/block recommendation with specific fixes.
|
|
51
|
+
|
|
52
|
+
## Evidence Checklist
|
|
53
|
+
|
|
54
|
+
Start with cheap, local signals:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
git status --short --branch
|
|
58
|
+
git log --oneline --decorate -20
|
|
59
|
+
git diff --stat origin/main...HEAD
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then inspect the project-specific surface:
|
|
63
|
+
|
|
64
|
+
- Package scripts, CI workflows, release scripts, Docker files, and deployment
|
|
65
|
+
manifests.
|
|
66
|
+
- API routes, webhooks, auth middleware, background workers, cron jobs, and
|
|
67
|
+
database migrations.
|
|
68
|
+
- Environment variable documentation and startup checks.
|
|
69
|
+
- Observability hooks, error reporting, logs, health checks, and dashboards.
|
|
70
|
+
- Rollback, seed, migration, and backfill instructions.
|
|
71
|
+
- E2E coverage for the user paths that matter most.
|
|
72
|
+
|
|
73
|
+
If a deployed URL is in scope, use browser or HTTP checks only against that URL
|
|
74
|
+
and avoid credentialed actions unless the user supplies a safe test account.
|
|
75
|
+
|
|
76
|
+
## Risk Lenses
|
|
77
|
+
|
|
78
|
+
### Security And Auth
|
|
79
|
+
|
|
80
|
+
- Are public routes, API routes, and admin routes clearly separated?
|
|
81
|
+
- Are auth and authorization enforced server-side?
|
|
82
|
+
- Are secrets kept out of client bundles, logs, example output, and checked-in
|
|
83
|
+
files?
|
|
84
|
+
- Are rate limits, CSRF protections, CORS policy, and upload validation present
|
|
85
|
+
where the app needs them?
|
|
86
|
+
- Does the AI or agent surface defend against prompt injection, tool abuse, and
|
|
87
|
+
untrusted content crossing into privileged actions?
|
|
88
|
+
|
|
89
|
+
### Data Integrity
|
|
90
|
+
|
|
91
|
+
- Do migrations run forward cleanly and have a rollback or recovery plan?
|
|
92
|
+
- Are destructive migrations, backfills, and data imports staged safely?
|
|
93
|
+
- Do database policies, grants, and service-role boundaries match the app's
|
|
94
|
+
tenancy model?
|
|
95
|
+
- Are retries idempotent for writes, jobs, and webhook handlers?
|
|
96
|
+
|
|
97
|
+
### Payments And Webhooks
|
|
98
|
+
|
|
99
|
+
- Are webhook signatures verified before parsing trusted payload fields?
|
|
100
|
+
- Is each payment, subscription, or fulfillment webhook idempotent?
|
|
101
|
+
- Are replay, duplicate delivery, and out-of-order delivery handled?
|
|
102
|
+
- Are test-mode and live-mode credentials separated?
|
|
103
|
+
|
|
104
|
+
### Operations
|
|
105
|
+
|
|
106
|
+
- Can the app start from a clean checkout using documented commands?
|
|
107
|
+
- Are required environment variables named, validated, and fail-fast?
|
|
108
|
+
- Is there a health check that proves dependencies are reachable?
|
|
109
|
+
- Are deploy, rollback, and incident-owner paths documented?
|
|
110
|
+
- Are logs useful without leaking secrets or personal data?
|
|
111
|
+
|
|
112
|
+
### User Experience
|
|
113
|
+
|
|
114
|
+
- Are the launch-critical paths covered on desktop and mobile?
|
|
115
|
+
- Are forms usable on mobile without input zoom, layout overlap, or blocked
|
|
116
|
+
submission states?
|
|
117
|
+
- Do loading, empty, error, and permission-denied states tell the user what
|
|
118
|
+
happened?
|
|
119
|
+
- Is there a support or recovery path when a critical operation fails?
|
|
120
|
+
|
|
121
|
+
## Scoring
|
|
122
|
+
|
|
123
|
+
Use scores to force prioritization, not to imply mathematical certainty.
|
|
124
|
+
|
|
125
|
+
| Band | Score | Meaning |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| Blocked | 0-49 | Do not ship until the top risks are fixed |
|
|
128
|
+
| Risky | 50-69 | Ship only behind a small rollout or internal beta |
|
|
129
|
+
| Launchable With Caveats | 70-84 | Ship if owners accept the listed risks |
|
|
130
|
+
| Strong | 85-100 | No obvious launch blockers from available evidence |
|
|
131
|
+
|
|
132
|
+
Cap the score at `69` if any of these are true:
|
|
133
|
+
|
|
134
|
+
- Authentication or authorization is missing on sensitive data.
|
|
135
|
+
- Payment or fulfillment webhooks are not idempotent.
|
|
136
|
+
- Required migrations cannot be run safely.
|
|
137
|
+
- Secrets are exposed in client bundles, logs, or committed files.
|
|
138
|
+
- There is no rollback path for a high-impact release.
|
|
139
|
+
|
|
140
|
+
Cap the score at `84` if CI is not green or the launch-critical path was not
|
|
141
|
+
tested end to end.
|
|
142
|
+
|
|
143
|
+
## Output Format
|
|
144
|
+
|
|
145
|
+
Lead with one sentence:
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
Production audit: 76/100, launchable with caveats, with webhook idempotency and rollback docs as the two risks to fix before public launch.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Then list:
|
|
152
|
+
|
|
153
|
+
- `Blockers`: must-fix items before deploy.
|
|
154
|
+
- `High-value fixes`: next fixes if the user wants to improve the score.
|
|
155
|
+
- `Evidence checked`: files, commands, CI, deployed URL, or PRs inspected.
|
|
156
|
+
- `Evidence missing`: what would change confidence if provided.
|
|
157
|
+
- `Next action`: one concrete fix or verification step.
|
|
158
|
+
|
|
159
|
+
Keep strengths short. The user asked for readiness, so the useful answer is the
|
|
160
|
+
remaining risk and the next action.
|
|
161
|
+
|
|
162
|
+
## Example
|
|
163
|
+
|
|
164
|
+
User:
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
is this ready to ship?
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Response:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
Production audit: 68/100, risky, because Stripe webhooks are verified but not idempotent and there is no rollback note for the pending migration.
|
|
174
|
+
|
|
175
|
+
Blockers:
|
|
176
|
+
- Add idempotency for `checkout.session.completed` before fulfilling orders.
|
|
177
|
+
- Write and test the rollback path for `20260511_add_billing_state.sql`.
|
|
178
|
+
|
|
179
|
+
High-value fixes:
|
|
180
|
+
- Add a health check that verifies database and payment-provider reachability.
|
|
181
|
+
- Add one E2E path for upgrade, webhook fulfillment, and billing-page refresh.
|
|
182
|
+
|
|
183
|
+
Evidence checked:
|
|
184
|
+
- `api/stripe/webhook.ts`
|
|
185
|
+
- `db/migrations/20260511_add_billing_state.sql`
|
|
186
|
+
- GitHub Actions run for the release branch
|
|
187
|
+
|
|
188
|
+
Next action: Want me to patch webhook idempotency first?
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Anti-Patterns
|
|
192
|
+
|
|
193
|
+
- Running `npx <package>@latest` or a remote scanner as the default audit path.
|
|
194
|
+
- Uploading source, secrets, customer data, or private topology to an external
|
|
195
|
+
audit service without explicit approval.
|
|
196
|
+
- Producing a score without naming the evidence checked.
|
|
197
|
+
- Treating green CI as production readiness.
|
|
198
|
+
- Ending with a generic "let me know what you want to do."
|
|
199
|
+
|
|
200
|
+
## See Also
|
|
201
|
+
|
|
202
|
+
- Skill: `security-review`
|
|
203
|
+
- Skill: `deployment-patterns`
|
|
204
|
+
- Skill: `e2e-testing`
|
|
205
|
+
- Skill: `tdd-workflow`
|
|
206
|
+
- Skill: `verification-loop`
|
package/src/skills/security-scan/references/agentshield-policy-exception/candidate-playbook.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# AgentShield Policy Exception Playbook
|
|
2
|
+
|
|
3
|
+
Candidate id: `sarif-backed-timeboxed-exception-review`
|
|
4
|
+
|
|
5
|
+
Use this playbook when AgentShield organization-policy output produces a
|
|
6
|
+
finding that may need remediation, a time-boxed exception, or explicit
|
|
7
|
+
enforcement.
|
|
8
|
+
|
|
9
|
+
## Accepted Path
|
|
10
|
+
|
|
11
|
+
1. Identify the AgentShield finding id, category, severity, affected file or
|
|
12
|
+
MCP/hook surface, and policy pack or organization baseline.
|
|
13
|
+
2. Retrieve scanner evidence before judgment:
|
|
14
|
+
- SARIF/code-scanning result, especially `agentshield-policy/*`
|
|
15
|
+
- JSON/HTML report evidence
|
|
16
|
+
- terminal or GitHub Action job-summary counts
|
|
17
|
+
3. Record lifecycle fields for any exception request: owner, ticket, scope,
|
|
18
|
+
expiry, rationale, and whether it is active, expiring soon, or expired.
|
|
19
|
+
4. Keep expired exceptions rejected or enforced until new evidence exists.
|
|
20
|
+
5. Decide whether immediate remediation is possible. If not, only promote a
|
|
21
|
+
narrow time-boxed exception tied to the named owner, ticket, scope, and
|
|
22
|
+
expiry.
|
|
23
|
+
6. Keep AgentShield code, policy packs, enforcement settings, release state,
|
|
24
|
+
and live security posture out of the read-only evaluator run.
|
|
25
|
+
|
|
26
|
+
## Rejected Path
|
|
27
|
+
|
|
28
|
+
Do not blanket suppress a policy category, policy pack, or organization gate
|
|
29
|
+
because a finding is inconvenient.
|
|
30
|
+
|
|
31
|
+
Do not downgrade critical/high findings without SARIF or report evidence and a
|
|
32
|
+
current owner, ticket, scope, and expiry.
|
|
33
|
+
|
|
34
|
+
Do not treat expired exceptions as active. Expired means the policy gate should
|
|
35
|
+
remain enforced until a maintainer creates a fresh, bounded exception or fixes
|
|
36
|
+
the underlying issue.
|
|
37
|
+
|
|
38
|
+
## Minimum Validation
|
|
39
|
+
|
|
40
|
+
- `npx ecc-agentshield scan --format json`
|
|
41
|
+
- AgentShield SARIF/code-scanning artifact or report evidence
|
|
42
|
+
- `npx ecc-agentshield scan --format html` when executive review evidence is
|
|
43
|
+
needed
|
|
44
|
+
- Current exception lifecycle fields: owner, ticket, scope, expiry, status
|
|
45
|
+
- `node tests/docs/evaluator-rag-prototype.test.js`
|
|
46
|
+
- `git diff --check`
|
|
47
|
+
|
|
48
|
+
Record the scanner evidence, lifecycle state, policy-pack source, and
|
|
49
|
+
remediation-versus-exception decision in the maintainer PR body or handoff.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "ecc.evaluator-rag.report.v1",
|
|
3
|
+
"scenario_id": "agentshield-policy-exception",
|
|
4
|
+
"run_id": "2026-05-12-agentshield-policy-exception-prototype",
|
|
5
|
+
"result": "prototype_passed",
|
|
6
|
+
"read_only": true,
|
|
7
|
+
"scores": {
|
|
8
|
+
"sarif_report_evidence": 0.95,
|
|
9
|
+
"exception_lifecycle": 0.93,
|
|
10
|
+
"ownership_specificity": 0.9,
|
|
11
|
+
"remediation_decision": 0.88,
|
|
12
|
+
"blanket_suppression_safety": 1
|
|
13
|
+
},
|
|
14
|
+
"findings": [
|
|
15
|
+
{
|
|
16
|
+
"id": "sarif-report-match-required",
|
|
17
|
+
"severity": "warning",
|
|
18
|
+
"summary": "AgentShield policy exceptions must name SARIF or report evidence before a remediation or exception playbook can be promoted."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "expired-exception-enforcement",
|
|
22
|
+
"severity": "warning",
|
|
23
|
+
"summary": "Expired exceptions must remain rejected or enforced; the evaluator cannot treat stale approvals as active evidence."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "bounded-owner-fields",
|
|
27
|
+
"severity": "info",
|
|
28
|
+
"summary": "Accepted exceptions preserve owner, ticket, scope, expiry, policy-pack source, and affected surface fields."
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"recommended_next_action": {
|
|
32
|
+
"candidate_id": "sarif-backed-timeboxed-exception-review",
|
|
33
|
+
"action": "Use the promoted playbook for future AgentShield policy exception requests before changing gates, suppressing categories, or accepting security risk."
|
|
34
|
+
}
|
|
35
|
+
}
|