codecruise 0.1.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/LICENSE +21 -0
- package/README.md +111 -0
- package/bin/codecruise.js +68 -0
- package/config/CLAUDE.md +107 -0
- package/config/agents/analyst.md +48 -0
- package/config/agents/architect-reviewer.md +161 -0
- package/config/agents/architect.md +119 -0
- package/config/agents/critic.md +63 -0
- package/config/agents/developer.md +96 -0
- package/config/agents/devops.md +81 -0
- package/config/agents/orchestrator.md +91 -0
- package/config/agents/planner.md +139 -0
- package/config/agents/retro.md +52 -0
- package/config/agents/reviewer.md +101 -0
- package/config/agents/security-reviewer.md +57 -0
- package/config/agents/stack/expo/AGENT.md +473 -0
- package/config/agents/stack/expo/rules/critical.md +427 -0
- package/config/agents/stack/expo/rules/native.md +455 -0
- package/config/agents/stack/expo/rules/navigation.md +445 -0
- package/config/agents/stack/expo/rules/performance.md +415 -0
- package/config/agents/stack/fastify/AGENT.md +397 -0
- package/config/agents/stack/fastify/rules/api-design.md +283 -0
- package/config/agents/stack/fastify/rules/critical.md +232 -0
- package/config/agents/stack/fastify/rules/queues.md +303 -0
- package/config/agents/stack/fastify/rules/security.md +384 -0
- package/config/agents/stack/index.yaml +48 -0
- package/config/agents/stack/nextjs/AGENT.md +421 -0
- package/config/agents/stack/nextjs/rules/components.md +413 -0
- package/config/agents/stack/nextjs/rules/critical.md +391 -0
- package/config/agents/stack/nextjs/rules/performance.md +403 -0
- package/config/agents/stack/nextjs/rules/styling.md +334 -0
- package/config/agents/stack/shared-ts/AGENT.md +384 -0
- package/config/agents/stack/shared-ts/rules/critical.md +315 -0
- package/config/agents/stack/shared-ts/rules/patterns.md +384 -0
- package/config/agents/stack/shared-ts/rules/zod.md +427 -0
- package/config/agents/tester.md +79 -0
- package/config/commands/architect-discuss.md +366 -0
- package/config/commands/architect-list.md +160 -0
- package/config/commands/architect-review.md +111 -0
- package/config/commands/architect.md +118 -0
- package/config/commands/compact.md +118 -0
- package/config/commands/companion.md +279 -0
- package/config/commands/dashboard.md +152 -0
- package/config/commands/doctor.md +227 -0
- package/config/commands/dogfood-report.md +101 -0
- package/config/commands/flags/run-autonomous.md +110 -0
- package/config/commands/flags/run-pause.md +80 -0
- package/config/commands/ingest.md +173 -0
- package/config/commands/init.md +128 -0
- package/config/commands/metrics.md +87 -0
- package/config/commands/parallel.md +320 -0
- package/config/commands/pause.md +55 -0
- package/config/commands/plan-review.md +130 -0
- package/config/commands/plan.md +216 -0
- package/config/commands/production-check.md +308 -0
- package/config/commands/refine.md +323 -0
- package/config/commands/resume.md +72 -0
- package/config/commands/retro.md +121 -0
- package/config/commands/retry.md +75 -0
- package/config/commands/role.md +310 -0
- package/config/commands/run.md +417 -0
- package/config/commands/scope.md +85 -0
- package/config/commands/setup-permissions.md +104 -0
- package/config/commands/skip.md +75 -0
- package/config/commands/spec-forge.md +213 -0
- package/config/commands/spec-help.md +194 -0
- package/config/commands/spec-patch.md +342 -0
- package/config/commands/spec-resolve.md +110 -0
- package/config/commands/spec-review.md +153 -0
- package/config/commands/status.md +114 -0
- package/config/commands/sync.md +131 -0
- package/config/commands/task.md +138 -0
- package/config/commands/verify.md +124 -0
- package/config/hooks/README.md +632 -0
- package/config/hooks/activity-log.sh +187 -0
- package/config/hooks/anti-rationalize.sh +52 -0
- package/config/hooks/capture-verification.sh +112 -0
- package/config/hooks/collect-metrics.sh +135 -0
- package/config/hooks/enforce-file-scope.sh +75 -0
- package/config/hooks/enforce-state-machine.sh +161 -0
- package/config/hooks/enforce-tdd.sh +180 -0
- package/config/hooks/format.sh +40 -0
- package/config/hooks/lib/activity-helpers.sh +162 -0
- package/config/hooks/lib/read-settings.sh +71 -0
- package/config/hooks/load-context-skills.sh +95 -0
- package/config/hooks/notify.sh +81 -0
- package/config/hooks/pre-commit.sample +35 -0
- package/config/hooks/protect-files.sh +63 -0
- package/config/hooks/track-agents.sh +41 -0
- package/config/hooks/track-commands.sh +37 -0
- package/config/hooks/track-enforcement.sh +44 -0
- package/config/hooks/track-ooda.sh +77 -0
- package/config/hooks/validate-commit-msg.sh +35 -0
- package/config/hooks/validate-plan.sh +213 -0
- package/config/hooks/verify-criteria.sh +46 -0
- package/config/hooks/verify-todo-completion.sh +140 -0
- package/config/rules/comments.md +25 -0
- package/config/rules/decision-rules.md +308 -0
- package/config/rules/hygiene.md +247 -0
- package/config/rules/pattern-detection.md +372 -0
- package/config/rules/profiles.md +193 -0
- package/config/rules/recovery.md +83 -0
- package/config/rules/scope-detection.md +213 -0
- package/config/rules/standards.md +127 -0
- package/config/rules/workflow.md +121 -0
- package/config/schemas.md +767 -0
- package/config/settings.json +195 -0
- package/config/skills/backend/SKILL.md +734 -0
- package/config/skills/database/SKILL.md +426 -0
- package/config/skills/frontend/SKILL.md +434 -0
- package/config/skills/git/SKILL.md +396 -0
- package/config/skills/index.yaml +36 -0
- package/config/skills/observability/SKILL.md +430 -0
- package/config/skills/package-dev/SKILL.md +498 -0
- package/config/skills/performance/SKILL.md +378 -0
- package/config/skills/resilience/SKILL.md +573 -0
- package/config/skills/testing/SKILL.md +398 -0
- package/config/skills/testing-patterns/SKILL.md +276 -0
- package/config/skills/typescript/SKILL.md +152 -0
- package/config/templates/CLAUDE.md +70 -0
- package/config/templates/README.md +117 -0
- package/config/templates/steering/adr-template.md +102 -0
- package/config/templates/steering/product.md +60 -0
- package/config/templates/steering/rfc-template.md +159 -0
- package/config/templates/steering/structure.md +146 -0
- package/config/templates/steering/tech.md +85 -0
- package/package.json +40 -0
- package/src/install.js +163 -0
- package/src/report.js +310 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Critical Rules - Next.js
|
|
2
|
+
|
|
3
|
+
Must-follow rules. Violations block PR merge.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Component Model
|
|
8
|
+
|
|
9
|
+
### NEXT-001: Default to Server Components
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// GOOD - Server Component (default)
|
|
13
|
+
async function UserList() {
|
|
14
|
+
const users = await getUsers(); // Direct server fetch
|
|
15
|
+
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// BAD - Unnecessary client component
|
|
19
|
+
'use client';
|
|
20
|
+
import { useEffect, useState } from 'react';
|
|
21
|
+
|
|
22
|
+
function UserList() {
|
|
23
|
+
const [users, setUsers] = useState([]);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
fetch('/api/users').then(r => r.json()).then(setUsers);
|
|
26
|
+
}, []);
|
|
27
|
+
// ... waterfall, slower
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### NEXT-002: Mark client components explicitly
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Required for:
|
|
35
|
+
// - useState, useEffect, useRef
|
|
36
|
+
// - onClick, onChange, onSubmit
|
|
37
|
+
// - Browser APIs (localStorage, window)
|
|
38
|
+
// - Third-party hooks
|
|
39
|
+
|
|
40
|
+
'use client'; // Must be first line
|
|
41
|
+
|
|
42
|
+
import { useState } from 'react';
|
|
43
|
+
|
|
44
|
+
export function Counter() {
|
|
45
|
+
const [count, setCount] = useState(0);
|
|
46
|
+
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### NEXT-003: Minimize client component boundaries
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// BAD - Entire page is client
|
|
54
|
+
'use client';
|
|
55
|
+
export default function DashboardPage() {
|
|
56
|
+
const [filter, setFilter] = useState('all');
|
|
57
|
+
const users = useUsers(); // All rendering on client
|
|
58
|
+
return <div>...</div>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// GOOD - Only interactive parts are client
|
|
62
|
+
export default async function DashboardPage() {
|
|
63
|
+
const users = await getUsers(); // Server fetch
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div>
|
|
67
|
+
<FilterBar /> {/* Client component for interactivity */}
|
|
68
|
+
<UserList users={users} /> {/* Server component with data */}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Data Fetching
|
|
77
|
+
|
|
78
|
+
### NEXT-004: Use Server Actions for mutations
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// BAD - API route for simple mutation
|
|
82
|
+
// app/api/users/route.ts + client fetch
|
|
83
|
+
|
|
84
|
+
// GOOD - Server Action
|
|
85
|
+
// app/actions/user.ts
|
|
86
|
+
'use server';
|
|
87
|
+
|
|
88
|
+
import { revalidatePath } from 'next/cache';
|
|
89
|
+
|
|
90
|
+
export async function createUser(formData: FormData) {
|
|
91
|
+
const data = Object.fromEntries(formData);
|
|
92
|
+
await db.user.create({ data });
|
|
93
|
+
revalidatePath('/users');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// In component
|
|
97
|
+
<form action={createUser}>...</form>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### NEXT-005: Validate all inputs in Server Actions
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
'use server';
|
|
104
|
+
|
|
105
|
+
import { z } from 'zod';
|
|
106
|
+
|
|
107
|
+
const schema = z.object({
|
|
108
|
+
email: z.string().email(),
|
|
109
|
+
name: z.string().min(2),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export async function createUser(formData: FormData) {
|
|
113
|
+
const raw = Object.fromEntries(formData);
|
|
114
|
+
|
|
115
|
+
// Always validate - formData can be spoofed
|
|
116
|
+
const result = schema.safeParse(raw);
|
|
117
|
+
if (!result.success) {
|
|
118
|
+
return { error: 'Invalid input', details: result.error.flatten() };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await db.user.create({ data: result.data });
|
|
122
|
+
return { success: true };
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### NEXT-006: Handle loading and error states
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// With Suspense
|
|
130
|
+
import { Suspense } from 'react';
|
|
131
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
132
|
+
|
|
133
|
+
export default function Page() {
|
|
134
|
+
return (
|
|
135
|
+
<ErrorBoundary fallback={<ErrorState />}>
|
|
136
|
+
<Suspense fallback={<LoadingState />}>
|
|
137
|
+
<AsyncComponent />
|
|
138
|
+
</Suspense>
|
|
139
|
+
</ErrorBoundary>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// With loading.tsx and error.tsx
|
|
144
|
+
// app/dashboard/loading.tsx
|
|
145
|
+
export default function Loading() {
|
|
146
|
+
return <Skeleton className="h-96" />;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// app/dashboard/error.tsx
|
|
150
|
+
'use client';
|
|
151
|
+
export default function Error({ error, reset }) {
|
|
152
|
+
return (
|
|
153
|
+
<div>
|
|
154
|
+
<p>Something went wrong</p>
|
|
155
|
+
<Button onClick={reset}>Try again</Button>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Security
|
|
164
|
+
|
|
165
|
+
### NEXT-007: Never expose secrets to client
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// BAD - Exposed to client bundle
|
|
169
|
+
const API_KEY = process.env.API_KEY;
|
|
170
|
+
|
|
171
|
+
// GOOD - Server-only
|
|
172
|
+
// In Server Component or Server Action
|
|
173
|
+
const API_KEY = process.env.API_KEY; // Only accessible on server
|
|
174
|
+
|
|
175
|
+
// Or explicitly mark
|
|
176
|
+
import 'server-only';
|
|
177
|
+
export const secret = process.env.SECRET;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### NEXT-008: Validate auth in Server Actions
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
'use server';
|
|
184
|
+
|
|
185
|
+
import { getSession } from '@/lib/auth';
|
|
186
|
+
import { redirect } from 'next/navigation';
|
|
187
|
+
|
|
188
|
+
export async function deleteUser(userId: string) {
|
|
189
|
+
const session = await getSession();
|
|
190
|
+
|
|
191
|
+
// Always check auth
|
|
192
|
+
if (!session) {
|
|
193
|
+
redirect('/login');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check authorization
|
|
197
|
+
if (session.user.role !== 'ADMIN') {
|
|
198
|
+
throw new Error('Unauthorized');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await db.user.delete({ where: { id: userId } });
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### NEXT-009: Sanitize user-generated content
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import DOMPurify from 'isomorphic-dompurify';
|
|
209
|
+
|
|
210
|
+
// For rendering user HTML
|
|
211
|
+
function UserContent({ html }: { html: string }) {
|
|
212
|
+
const clean = DOMPurify.sanitize(html, {
|
|
213
|
+
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
|
|
214
|
+
ALLOWED_ATTR: ['href'],
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Prefer structured content over raw HTML
|
|
221
|
+
function UserBio({ bio }: { bio: string }) {
|
|
222
|
+
// Just text, no HTML
|
|
223
|
+
return <p className="whitespace-pre-wrap">{bio}</p>;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Routing
|
|
230
|
+
|
|
231
|
+
### NEXT-010: Use proper navigation methods
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// For links - use Link component
|
|
235
|
+
import Link from 'next/link';
|
|
236
|
+
<Link href="/dashboard">Dashboard</Link>
|
|
237
|
+
|
|
238
|
+
// For programmatic navigation in Client Components
|
|
239
|
+
'use client';
|
|
240
|
+
import { useRouter } from 'next/navigation';
|
|
241
|
+
const router = useRouter();
|
|
242
|
+
router.push('/dashboard');
|
|
243
|
+
|
|
244
|
+
// For Server Actions/Components
|
|
245
|
+
import { redirect } from 'next/navigation';
|
|
246
|
+
redirect('/dashboard'); // Throws, stops execution
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### NEXT-011: Protect routes at layout level
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// app/dashboard/layout.tsx
|
|
253
|
+
import { getSession } from '@/lib/auth';
|
|
254
|
+
import { redirect } from 'next/navigation';
|
|
255
|
+
|
|
256
|
+
export default async function DashboardLayout({
|
|
257
|
+
children,
|
|
258
|
+
}: {
|
|
259
|
+
children: React.ReactNode;
|
|
260
|
+
}) {
|
|
261
|
+
const session = await getSession();
|
|
262
|
+
|
|
263
|
+
if (!session) {
|
|
264
|
+
redirect('/login');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div className="flex">
|
|
269
|
+
<Sidebar user={session.user} />
|
|
270
|
+
<main className="flex-1">{children}</main>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Rendering
|
|
279
|
+
|
|
280
|
+
### NEXT-012: Add proper metadata
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// Static metadata
|
|
284
|
+
export const metadata = {
|
|
285
|
+
title: 'Dashboard',
|
|
286
|
+
description: 'Your dashboard overview',
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Dynamic metadata
|
|
290
|
+
export async function generateMetadata({ params }) {
|
|
291
|
+
const user = await getUser(params.id);
|
|
292
|
+
return {
|
|
293
|
+
title: user.name,
|
|
294
|
+
description: `Profile of ${user.name}`,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// With template
|
|
299
|
+
// app/layout.tsx
|
|
300
|
+
export const metadata = {
|
|
301
|
+
title: {
|
|
302
|
+
template: '%s | MyApp',
|
|
303
|
+
default: 'MyApp',
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### NEXT-013: Use Image component for images
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// BAD
|
|
312
|
+
<img src="/hero.jpg" alt="Hero" />
|
|
313
|
+
|
|
314
|
+
// GOOD
|
|
315
|
+
import Image from 'next/image';
|
|
316
|
+
|
|
317
|
+
<Image
|
|
318
|
+
src="/hero.jpg"
|
|
319
|
+
alt="Hero image"
|
|
320
|
+
width={1200}
|
|
321
|
+
height={600}
|
|
322
|
+
priority // For above-the-fold images
|
|
323
|
+
/>
|
|
324
|
+
|
|
325
|
+
// For fill mode
|
|
326
|
+
<div className="relative h-64">
|
|
327
|
+
<Image
|
|
328
|
+
src={user.avatar}
|
|
329
|
+
alt={user.name}
|
|
330
|
+
fill
|
|
331
|
+
className="object-cover"
|
|
332
|
+
sizes="(max-width: 768px) 100vw, 50vw"
|
|
333
|
+
/>
|
|
334
|
+
</div>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## State Management
|
|
340
|
+
|
|
341
|
+
### NEXT-014: Avoid unnecessary client state
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// BAD - Client state for server data
|
|
345
|
+
'use client';
|
|
346
|
+
const [users, setUsers] = useState([]);
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
fetchUsers().then(setUsers);
|
|
349
|
+
}, []);
|
|
350
|
+
|
|
351
|
+
// GOOD - Server fetch, no client state needed
|
|
352
|
+
async function UserList() {
|
|
353
|
+
const users = await getUsers();
|
|
354
|
+
return <ul>...</ul>;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// OK - Client state for UI state only
|
|
358
|
+
'use client';
|
|
359
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
360
|
+
const [filter, setFilter] = useState('all');
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### NEXT-015: Use URL state for shareable state
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
'use client';
|
|
367
|
+
|
|
368
|
+
import { useSearchParams, useRouter, usePathname } from 'next/navigation';
|
|
369
|
+
|
|
370
|
+
function FilterBar() {
|
|
371
|
+
const searchParams = useSearchParams();
|
|
372
|
+
const router = useRouter();
|
|
373
|
+
const pathname = usePathname();
|
|
374
|
+
|
|
375
|
+
function setFilter(value: string) {
|
|
376
|
+
const params = new URLSearchParams(searchParams);
|
|
377
|
+
params.set('filter', value);
|
|
378
|
+
router.push(`${pathname}?${params.toString()}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<select
|
|
383
|
+
value={searchParams.get('filter') ?? 'all'}
|
|
384
|
+
onChange={(e) => setFilter(e.target.value)}
|
|
385
|
+
>
|
|
386
|
+
<option value="all">All</option>
|
|
387
|
+
<option value="active">Active</option>
|
|
388
|
+
</select>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
```
|