autodoc-agent-kit 1.0.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/README.md +362 -0
- package/package.json +49 -0
- package/src/core/module.yaml +5 -0
- package/src/modules/design/module.yaml +9 -0
- package/src/modules/design/skills/brand-guidelines/LICENSE.txt +202 -0
- package/src/modules/design/skills/brand-guidelines/SKILL.md +73 -0
- package/src/modules/design/skills/frontend-design/LICENSE.txt +177 -0
- package/src/modules/design/skills/frontend-design/SKILL.md +42 -0
- package/src/modules/design/skills/web-artifacts-builder/SKILL.md +229 -0
- package/src/modules/devops/module.yaml +10 -0
- package/src/modules/devops/skills/devops-helper/SKILL.md +60 -0
- package/src/modules/devops/skills/k8s-helm/SKILL.md +360 -0
- package/src/modules/devops/skills/monitoring-observability/SKILL.md +240 -0
- package/src/modules/devops/skills/security-auditor/SKILL.md +105 -0
- package/src/modules/engineering/module.yaml +22 -0
- package/src/modules/engineering/skills/ai-sdk/SKILL.md +314 -0
- package/src/modules/engineering/skills/api-designer/SKILL.md +77 -0
- package/src/modules/engineering/skills/code-reviewer/SKILL.md +71 -0
- package/src/modules/engineering/skills/db-architect/SKILL.md +50 -0
- package/src/modules/engineering/skills/debugger/SKILL.md +59 -0
- package/src/modules/engineering/skills/docs-generator/SKILL.md +51 -0
- package/src/modules/engineering/skills/git-workflow/SKILL.md +258 -0
- package/src/modules/engineering/skills/mcp-builder/LICENSE.txt +202 -0
- package/src/modules/engineering/skills/mcp-builder/SKILL.md +236 -0
- package/src/modules/engineering/skills/mcp-builder/reference/evaluation.md +602 -0
- package/src/modules/engineering/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/src/modules/engineering/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/src/modules/engineering/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/src/modules/engineering/skills/mcp-builder/scripts/connections.py +151 -0
- package/src/modules/engineering/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/src/modules/engineering/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/src/modules/engineering/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/src/modules/engineering/skills/nextjs-15/SKILL.md +312 -0
- package/src/modules/engineering/skills/perf-optimizer/SKILL.md +60 -0
- package/src/modules/engineering/skills/react-19/SKILL.md +257 -0
- package/src/modules/engineering/skills/refactorer/SKILL.md +60 -0
- package/src/modules/engineering/skills/skill-authoring-workflow/SKILL.md +183 -0
- package/src/modules/engineering/skills/skill-creator/LICENSE.txt +202 -0
- package/src/modules/engineering/skills/skill-creator/SKILL.md +356 -0
- package/src/modules/engineering/skills/skill-creator/references/output-patterns.md +82 -0
- package/src/modules/engineering/skills/skill-creator/references/workflows.md +28 -0
- package/src/modules/engineering/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-313.pyc +0 -0
- package/src/modules/engineering/skills/skill-creator/scripts/init_skill.py +303 -0
- package/src/modules/engineering/skills/skill-creator/scripts/package_skill.py +110 -0
- package/src/modules/engineering/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/src/modules/engineering/skills/typescript/SKILL.md +231 -0
- package/src/modules/engineering/skills/zod-4/SKILL.md +223 -0
- package/src/modules/product/module.yaml +51 -0
- package/src/modules/product/skills/acquisition-channel-advisor/SKILL.md +643 -0
- package/src/modules/product/skills/acquisition-channel-advisor/examples/conversation-flow.md +531 -0
- package/src/modules/product/skills/ai-shaped-readiness-advisor/SKILL.md +923 -0
- package/src/modules/product/skills/altitude-horizon-framework/SKILL.md +250 -0
- package/src/modules/product/skills/altitude-horizon-framework/examples/sample.md +85 -0
- package/src/modules/product/skills/business-health-diagnostic/SKILL.md +783 -0
- package/src/modules/product/skills/company-research/SKILL.md +385 -0
- package/src/modules/product/skills/company-research/examples/sample.md +164 -0
- package/src/modules/product/skills/company-research/template.md +60 -0
- package/src/modules/product/skills/context-engineering-advisor/SKILL.md +763 -0
- package/src/modules/product/skills/customer-journey-map/SKILL.md +346 -0
- package/src/modules/product/skills/customer-journey-map/examples/meta-product-manager-skills.md +40 -0
- package/src/modules/product/skills/customer-journey-map/examples/sample.md +33 -0
- package/src/modules/product/skills/customer-journey-map/template.md +28 -0
- package/src/modules/product/skills/customer-journey-mapping-workshop/SKILL.md +523 -0
- package/src/modules/product/skills/director-readiness-advisor/SKILL.md +351 -0
- package/src/modules/product/skills/director-readiness-advisor/examples/conversation-flow.md +96 -0
- package/src/modules/product/skills/discovery-interview-prep/SKILL.md +410 -0
- package/src/modules/product/skills/discovery-process/SKILL.md +504 -0
- package/src/modules/product/skills/discovery-process/examples/sample.md +60 -0
- package/src/modules/product/skills/discovery-process/template.md +39 -0
- package/src/modules/product/skills/eol-message/SKILL.md +348 -0
- package/src/modules/product/skills/eol-message/examples/sample.md +87 -0
- package/src/modules/product/skills/eol-message/template.md +74 -0
- package/src/modules/product/skills/epic-breakdown-advisor/SKILL.md +665 -0
- package/src/modules/product/skills/epic-hypothesis/SKILL.md +277 -0
- package/src/modules/product/skills/epic-hypothesis/examples/sample.md +104 -0
- package/src/modules/product/skills/epic-hypothesis/template.md +30 -0
- package/src/modules/product/skills/executive-onboarding-playbook/SKILL.md +280 -0
- package/src/modules/product/skills/executive-onboarding-playbook/examples/sample.md +116 -0
- package/src/modules/product/skills/feature-investment-advisor/SKILL.md +639 -0
- package/src/modules/product/skills/feature-investment-advisor/examples/conversation-flow.md +538 -0
- package/src/modules/product/skills/finance-based-pricing-advisor/SKILL.md +763 -0
- package/src/modules/product/skills/finance-metrics-quickref/SKILL.md +309 -0
- package/src/modules/product/skills/jobs-to-be-done/SKILL.md +370 -0
- package/src/modules/product/skills/jobs-to-be-done/examples/sample.md +80 -0
- package/src/modules/product/skills/jobs-to-be-done/template.md +65 -0
- package/src/modules/product/skills/lean-ux-canvas/SKILL.md +561 -0
- package/src/modules/product/skills/lean-ux-canvas/examples/sample.md +88 -0
- package/src/modules/product/skills/lean-ux-canvas/template.md +32 -0
- package/src/modules/product/skills/opportunity-solution-tree/SKILL.md +420 -0
- package/src/modules/product/skills/opportunity-solution-tree/examples/sample.md +104 -0
- package/src/modules/product/skills/opportunity-solution-tree/template.md +33 -0
- package/src/modules/product/skills/pestel-analysis/SKILL.md +376 -0
- package/src/modules/product/skills/pestel-analysis/examples/sample.md +143 -0
- package/src/modules/product/skills/pestel-analysis/template.md +53 -0
- package/src/modules/product/skills/pol-probe/SKILL.md +217 -0
- package/src/modules/product/skills/pol-probe/examples/sample.md +136 -0
- package/src/modules/product/skills/pol-probe/template.md +59 -0
- package/src/modules/product/skills/pol-probe-advisor/SKILL.md +492 -0
- package/src/modules/product/skills/positioning-statement/SKILL.md +230 -0
- package/src/modules/product/skills/positioning-statement/examples/sample.md +51 -0
- package/src/modules/product/skills/positioning-statement/template.md +25 -0
- package/src/modules/product/skills/positioning-workshop/SKILL.md +424 -0
- package/src/modules/product/skills/prd-development/SKILL.md +655 -0
- package/src/modules/product/skills/prd-development/examples/sample.md +43 -0
- package/src/modules/product/skills/prd-development/template.md +55 -0
- package/src/modules/product/skills/press-release/SKILL.md +269 -0
- package/src/modules/product/skills/press-release/examples/sample.md +73 -0
- package/src/modules/product/skills/press-release/template.md +39 -0
- package/src/modules/product/skills/prioritization-advisor/SKILL.md +448 -0
- package/src/modules/product/skills/problem-framing-canvas/SKILL.md +466 -0
- package/src/modules/product/skills/problem-framing-canvas/examples/sample.md +58 -0
- package/src/modules/product/skills/problem-framing-canvas/template.md +22 -0
- package/src/modules/product/skills/problem-statement/SKILL.md +246 -0
- package/src/modules/product/skills/problem-statement/examples/sample.md +82 -0
- package/src/modules/product/skills/problem-statement/template.md +37 -0
- package/src/modules/product/skills/product-strategy-session/SKILL.md +426 -0
- package/src/modules/product/skills/product-strategy-session/examples/sample.md +67 -0
- package/src/modules/product/skills/product-strategy-session/template.md +38 -0
- package/src/modules/product/skills/proto-persona/SKILL.md +326 -0
- package/src/modules/product/skills/proto-persona/examples/sample.md +97 -0
- package/src/modules/product/skills/proto-persona/template.md +45 -0
- package/src/modules/product/skills/recommendation-canvas/SKILL.md +375 -0
- package/src/modules/product/skills/recommendation-canvas/examples/sample.md +94 -0
- package/src/modules/product/skills/recommendation-canvas/template.md +86 -0
- package/src/modules/product/skills/roadmap-planning/SKILL.md +505 -0
- package/src/modules/product/skills/roadmap-planning/examples/sample.md +62 -0
- package/src/modules/product/skills/roadmap-planning/template.md +30 -0
- package/src/modules/product/skills/saas-economics-efficiency-metrics/SKILL.md +694 -0
- package/src/modules/product/skills/saas-economics-efficiency-metrics/examples/cash-trap.md +365 -0
- package/src/modules/product/skills/saas-economics-efficiency-metrics/examples/healthy-unit-economics.md +279 -0
- package/src/modules/product/skills/saas-economics-efficiency-metrics/template.md +263 -0
- package/src/modules/product/skills/saas-revenue-growth-metrics/SKILL.md +630 -0
- package/src/modules/product/skills/saas-revenue-growth-metrics/examples/healthy-saas.md +131 -0
- package/src/modules/product/skills/saas-revenue-growth-metrics/examples/warning-signs.md +229 -0
- package/src/modules/product/skills/saas-revenue-growth-metrics/template.md +192 -0
- package/src/modules/product/skills/storyboard/SKILL.md +252 -0
- package/src/modules/product/skills/storyboard/examples/sample.md +71 -0
- package/src/modules/product/skills/storyboard/template.md +41 -0
- package/src/modules/product/skills/tam-sam-som-calculator/SKILL.md +392 -0
- package/src/modules/product/skills/tam-sam-som-calculator/examples/sample.md +142 -0
- package/src/modules/product/skills/tam-sam-som-calculator/scripts/market-sizing.py +95 -0
- package/src/modules/product/skills/tam-sam-som-calculator/template.md +35 -0
- package/src/modules/product/skills/user-story/SKILL.md +272 -0
- package/src/modules/product/skills/user-story/examples/sample.md +110 -0
- package/src/modules/product/skills/user-story/scripts/user-story-template.py +65 -0
- package/src/modules/product/skills/user-story/template.md +32 -0
- package/src/modules/product/skills/user-story-mapping/SKILL.md +285 -0
- package/src/modules/product/skills/user-story-mapping/examples/sample.md +77 -0
- package/src/modules/product/skills/user-story-mapping/template.md +41 -0
- package/src/modules/product/skills/user-story-mapping-workshop/SKILL.md +477 -0
- package/src/modules/product/skills/user-story-mapping-workshop/template.md +28 -0
- package/src/modules/product/skills/user-story-splitting/SKILL.md +303 -0
- package/src/modules/product/skills/user-story-splitting/examples/sample.md +147 -0
- package/src/modules/product/skills/user-story-splitting/template.md +37 -0
- package/src/modules/product/skills/vp-cpo-readiness-advisor/SKILL.md +409 -0
- package/src/modules/product/skills/vp-cpo-readiness-advisor/examples/conversation-flow.md +95 -0
- package/src/modules/product/skills/workshop-facilitation/SKILL.md +87 -0
- package/src/modules/productivity/module.yaml +9 -0
- package/src/modules/productivity/skills/doc-coauthoring/SKILL.md +375 -0
- package/src/modules/productivity/skills/internal-comms/LICENSE.txt +202 -0
- package/src/modules/productivity/skills/internal-comms/SKILL.md +32 -0
- package/src/modules/productivity/skills/internal-comms/examples/3p-updates.md +47 -0
- package/src/modules/productivity/skills/internal-comms/examples/company-newsletter.md +65 -0
- package/src/modules/productivity/skills/internal-comms/examples/faq-answers.md +30 -0
- package/src/modules/productivity/skills/internal-comms/examples/general-comms.md +16 -0
- package/src/modules/productivity/skills/technical-writing/SKILL.md +266 -0
- package/src/modules/qa/module.yaml +9 -0
- package/src/modules/qa/skills/test-strategy/SKILL.md +263 -0
- package/src/modules/qa/skills/test-writer/SKILL.md +57 -0
- package/src/modules/qa/skills/webapp-testing/LICENSE.txt +202 -0
- package/src/modules/qa/skills/webapp-testing/SKILL.md +96 -0
- package/src/modules/qa/skills/webapp-testing/examples/console_logging.py +35 -0
- package/src/modules/qa/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/src/modules/qa/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/src/modules/qa/skills/webapp-testing/scripts/with_server.py +106 -0
- package/tools/autodoc-npx-wrapper.js +34 -0
- package/tools/cli/autodoc-cli.js +55 -0
- package/tools/cli/commands/install.js +36 -0
- package/tools/cli/commands/status.js +35 -0
- package/tools/cli/commands/uninstall.js +60 -0
- package/tools/cli/installers/lib/core/installer.js +164 -0
- package/tools/cli/installers/lib/core/manifest.js +49 -0
- package/tools/cli/installers/lib/ide/manager.js +112 -0
- package/tools/cli/installers/lib/ide/platform-codes.yaml +207 -0
- package/tools/cli/installers/lib/modules/manager.js +59 -0
- package/tools/cli/lib/ui.js +199 -0
- package/tools/cli/lib/welcome.js +82 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-15
|
|
3
|
+
description: Next.js 15 App Router patterns. Trigger when working in Next.js App Router (app/), Server Components vs Client Components, Server Actions, Route Handlers, caching/revalidation, and streaming/Suspense.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Next.js 15 App Router Patterns
|
|
7
|
+
|
|
8
|
+
Next.js 15 uses the App Router exclusively. Every file in `app/` is a Server Component by default. Understanding the server/client boundary is the core mental model.
|
|
9
|
+
|
|
10
|
+
## Server vs Client Components
|
|
11
|
+
|
|
12
|
+
### Default: Server Components
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
// app/users/page.tsx — Server Component (no "use client")
|
|
16
|
+
// Can: await data, access env vars, use server-only packages
|
|
17
|
+
// Cannot: useState, useEffect, event handlers, browser APIs
|
|
18
|
+
|
|
19
|
+
export default async function UsersPage() {
|
|
20
|
+
const users = await db.users.findMany(); // direct DB access
|
|
21
|
+
return <UserList users={users} />;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Client Components
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
'use client';
|
|
29
|
+
// app/components/counter.tsx
|
|
30
|
+
// Can: useState, useEffect, event handlers, browser APIs
|
|
31
|
+
// Cannot: async/await at component level, server-only imports
|
|
32
|
+
|
|
33
|
+
export function Counter() {
|
|
34
|
+
const [count, setCount] = useState(0);
|
|
35
|
+
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### The Boundary Rule
|
|
40
|
+
|
|
41
|
+
Push "use client" as deep as possible. Server Components can render Client Components, but not vice versa (without passing as props).
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// Good: Server Component renders Client Component leaf
|
|
45
|
+
// app/dashboard/page.tsx (Server)
|
|
46
|
+
export default async function Dashboard() {
|
|
47
|
+
const data = await fetchData();
|
|
48
|
+
return (
|
|
49
|
+
<div>
|
|
50
|
+
<h1>Dashboard</h1>
|
|
51
|
+
<InteractiveChart data={data} /> {/* Client Component */}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Passing Server Components INTO Client Components via children
|
|
57
|
+
function ClientShell({ children }: { children: React.ReactNode }) {
|
|
58
|
+
'use client';
|
|
59
|
+
const [open, setOpen] = useState(false);
|
|
60
|
+
return <div onClick={() => setOpen(!open)}>{children}</div>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Server Component passed as children — stays on server
|
|
64
|
+
<ClientShell>
|
|
65
|
+
<ServerSideContent /> {/* This renders on the server */}
|
|
66
|
+
</ClientShell>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## File Conventions
|
|
70
|
+
|
|
71
|
+
| File | Purpose |
|
|
72
|
+
|------|---------|
|
|
73
|
+
| `layout.tsx` | Persistent UI wrapper, not re-rendered on navigation |
|
|
74
|
+
| `page.tsx` | Route UI, receives `params` and `searchParams` |
|
|
75
|
+
| `loading.tsx` | Suspense fallback for the route segment |
|
|
76
|
+
| `error.tsx` | Error boundary for the route segment (must be `'use client'`) |
|
|
77
|
+
| `not-found.tsx` | 404 UI for `notFound()` calls |
|
|
78
|
+
| `route.ts` | API route handler (no UI) |
|
|
79
|
+
| `template.tsx` | Like layout but re-renders on navigation |
|
|
80
|
+
|
|
81
|
+
## Server Actions
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// app/actions.ts
|
|
85
|
+
'use server';
|
|
86
|
+
|
|
87
|
+
import { revalidatePath } from 'next/cache';
|
|
88
|
+
import { redirect } from 'next/navigation';
|
|
89
|
+
|
|
90
|
+
export async function createPost(formData: FormData) {
|
|
91
|
+
const title = formData.get('title') as string;
|
|
92
|
+
|
|
93
|
+
// Validate
|
|
94
|
+
if (!title) throw new Error('Title required');
|
|
95
|
+
|
|
96
|
+
// Mutate
|
|
97
|
+
const post = await db.posts.create({ data: { title } });
|
|
98
|
+
|
|
99
|
+
// Revalidate cache
|
|
100
|
+
revalidatePath('/posts');
|
|
101
|
+
|
|
102
|
+
// Redirect
|
|
103
|
+
redirect(`/posts/${post.id}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Use in a form
|
|
107
|
+
export default function NewPostForm() {
|
|
108
|
+
return (
|
|
109
|
+
<form action={createPost}>
|
|
110
|
+
<input name="title" required />
|
|
111
|
+
<button type="submit">Create</button>
|
|
112
|
+
</form>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Data Fetching
|
|
118
|
+
|
|
119
|
+
### Parallel Fetching
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
|
123
|
+
const { id } = await params;
|
|
124
|
+
|
|
125
|
+
// Fetch in parallel — don't await sequentially
|
|
126
|
+
const [user, posts] = await Promise.all([
|
|
127
|
+
getUser(id),
|
|
128
|
+
getUserPosts(id),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
return <Profile user={user} posts={posts} />;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Streaming with Suspense
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { Suspense } from 'react';
|
|
139
|
+
|
|
140
|
+
export default function Page() {
|
|
141
|
+
return (
|
|
142
|
+
<div>
|
|
143
|
+
<h1>Dashboard</h1>
|
|
144
|
+
{/* Slow data doesn't block fast data */}
|
|
145
|
+
<Suspense fallback={<StatsLoading />}>
|
|
146
|
+
<SlowStats />
|
|
147
|
+
</Suspense>
|
|
148
|
+
<Suspense fallback={<FeedLoading />}>
|
|
149
|
+
<ActivityFeed />
|
|
150
|
+
</Suspense>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function SlowStats() {
|
|
156
|
+
const stats = await getExpensiveStats(); // doesn't block the rest
|
|
157
|
+
return <StatsGrid stats={stats} />;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Caching and Revalidation
|
|
162
|
+
|
|
163
|
+
### Route Segment Config
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
// Force dynamic rendering (no cache)
|
|
167
|
+
export const dynamic = 'force-dynamic';
|
|
168
|
+
|
|
169
|
+
// Cache for 60 seconds
|
|
170
|
+
export const revalidate = 60;
|
|
171
|
+
|
|
172
|
+
// Static (build-time only)
|
|
173
|
+
export const dynamic = 'force-static';
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### On-Demand Revalidation
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
'use server';
|
|
180
|
+
import { revalidatePath, revalidateTag } from 'next/cache';
|
|
181
|
+
|
|
182
|
+
// Revalidate a path
|
|
183
|
+
async function updatePost(id: string) {
|
|
184
|
+
await db.posts.update({ where: { id }, data: { ... } });
|
|
185
|
+
revalidatePath(`/posts/${id}`);
|
|
186
|
+
revalidatePath('/posts');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Tag data when fetching
|
|
190
|
+
const user = await fetch(`/api/users/${id}`, {
|
|
191
|
+
next: { tags: [`user-${id}`] }
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Revalidate by tag
|
|
195
|
+
revalidateTag(`user-${id}`);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Route Handlers
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// app/api/users/route.ts
|
|
202
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
203
|
+
|
|
204
|
+
export async function GET(request: NextRequest) {
|
|
205
|
+
const searchParams = request.nextUrl.searchParams;
|
|
206
|
+
const query = searchParams.get('q');
|
|
207
|
+
|
|
208
|
+
const users = await db.users.findMany({
|
|
209
|
+
where: query ? { name: { contains: query } } : undefined,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return NextResponse.json(users);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function POST(request: NextRequest) {
|
|
216
|
+
const body = await request.json();
|
|
217
|
+
const result = CreateUserSchema.safeParse(body);
|
|
218
|
+
if (!result.success) {
|
|
219
|
+
return NextResponse.json({ error: result.error.format() }, { status: 400 });
|
|
220
|
+
}
|
|
221
|
+
const user = await db.users.create({ data: result.data });
|
|
222
|
+
return NextResponse.json(user, { status: 201 });
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Dynamic Routes
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
// app/posts/[slug]/page.tsx
|
|
230
|
+
export default async function PostPage({
|
|
231
|
+
params,
|
|
232
|
+
}: {
|
|
233
|
+
params: Promise<{ slug: string }>;
|
|
234
|
+
}) {
|
|
235
|
+
const { slug } = await params; // params is now async in Next.js 15
|
|
236
|
+
const post = await getPostBySlug(slug);
|
|
237
|
+
if (!post) notFound();
|
|
238
|
+
return <PostContent post={post} />;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Generate static paths at build time
|
|
242
|
+
export async function generateStaticParams() {
|
|
243
|
+
const posts = await getAllPosts();
|
|
244
|
+
return posts.map(post => ({ slug: post.slug }));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Generate metadata
|
|
248
|
+
export async function generateMetadata({
|
|
249
|
+
params,
|
|
250
|
+
}: {
|
|
251
|
+
params: Promise<{ slug: string }>;
|
|
252
|
+
}) {
|
|
253
|
+
const { slug } = await params;
|
|
254
|
+
const post = await getPostBySlug(slug);
|
|
255
|
+
return {
|
|
256
|
+
title: post?.title ?? 'Not Found',
|
|
257
|
+
description: post?.excerpt,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Middleware
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// middleware.ts (project root)
|
|
266
|
+
import { NextResponse } from 'next/server';
|
|
267
|
+
import type { NextRequest } from 'next/server';
|
|
268
|
+
|
|
269
|
+
export function middleware(request: NextRequest) {
|
|
270
|
+
const token = request.cookies.get('auth-token');
|
|
271
|
+
|
|
272
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
273
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return NextResponse.next();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export const config = {
|
|
280
|
+
matcher: ['/dashboard/:path*', '/api/:path*'],
|
|
281
|
+
};
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Error Handling
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
// app/dashboard/error.tsx
|
|
288
|
+
'use client';
|
|
289
|
+
|
|
290
|
+
export default function Error({
|
|
291
|
+
error,
|
|
292
|
+
reset,
|
|
293
|
+
}: {
|
|
294
|
+
error: Error & { digest?: string };
|
|
295
|
+
reset: () => void;
|
|
296
|
+
}) {
|
|
297
|
+
return (
|
|
298
|
+
<div>
|
|
299
|
+
<h2>Something went wrong</h2>
|
|
300
|
+
<p>{error.message}</p>
|
|
301
|
+
<button onClick={reset}>Try again</button>
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Key Next.js 15 Changes
|
|
308
|
+
|
|
309
|
+
- `params` and `searchParams` in pages/layouts are now **Promises** — must be awaited
|
|
310
|
+
- `cookies()` and `headers()` are now **async** — must be awaited
|
|
311
|
+
- New `after()` API for post-response work (logging, analytics)
|
|
312
|
+
- Turbopack is the default dev bundler
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: perf-optimizer
|
|
3
|
+
description: Performance profiling and optimization across the full stack. Use when you need to improve application speed, reduce bundle size, optimize database queries, or improve Core Web Vitals. Triggers on "this is slow", "optimize performance", "reduce bundle size", "improve LCP/CLS/INP", "this query takes too long", or "why is this app laggy".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a performance optimization specialist across the full stack.
|
|
7
|
+
|
|
8
|
+
## Optimization Areas
|
|
9
|
+
|
|
10
|
+
### Frontend Performance
|
|
11
|
+
- **Bundle size**: Analyze imports, tree-shaking, dynamic imports, code splitting
|
|
12
|
+
- **Rendering**: Identify unnecessary re-renders, missing memoization, heavy computations
|
|
13
|
+
- **Assets**: Image optimization, font loading, lazy loading below-the-fold content
|
|
14
|
+
- **Core Web Vitals**: LCP, FID/INP, CLS optimization strategies
|
|
15
|
+
- **Network**: Request waterfall analysis, prefetching, caching headers
|
|
16
|
+
|
|
17
|
+
### Backend Performance
|
|
18
|
+
- **Database**: Query optimization with EXPLAIN, N+1 detection, index analysis
|
|
19
|
+
- **Caching**: Identify cacheable operations, cache invalidation strategy
|
|
20
|
+
- **Async**: Move heavy operations to background jobs/queues
|
|
21
|
+
- **Memory**: Identify memory leaks, large allocations, streaming for large datasets
|
|
22
|
+
- **Concurrency**: Connection pooling, worker threads, request queuing
|
|
23
|
+
|
|
24
|
+
### API Performance
|
|
25
|
+
- **Response time**: Identify bottlenecks in request lifecycle
|
|
26
|
+
- **Payload size**: Minimize response payloads, use pagination
|
|
27
|
+
- **Compression**: Enable gzip/brotli
|
|
28
|
+
- **Batching**: Combine multiple requests where appropriate
|
|
29
|
+
|
|
30
|
+
## Process
|
|
31
|
+
|
|
32
|
+
### Step 1: Measure
|
|
33
|
+
1. Identify the performance concern (what's slow, by how much)
|
|
34
|
+
2. Read the relevant code paths
|
|
35
|
+
3. Look for obvious anti-patterns first
|
|
36
|
+
4. Establish baseline metrics where possible
|
|
37
|
+
|
|
38
|
+
### Step 2: Identify Bottlenecks
|
|
39
|
+
Priority order:
|
|
40
|
+
1. **Algorithmic**: O(n²) or worse in hot paths
|
|
41
|
+
2. **I/O bound**: Unnecessary or sequential database/network calls
|
|
42
|
+
3. **Memory**: Large allocations, missing cleanup, unbounded growth
|
|
43
|
+
4. **CPU**: Heavy computation on the main thread
|
|
44
|
+
5. **Network**: Large payloads, missing compression, no caching
|
|
45
|
+
|
|
46
|
+
### Step 3: Optimize
|
|
47
|
+
- Fix one bottleneck at a time
|
|
48
|
+
- Measure improvement after each change
|
|
49
|
+
- Document the tradeoff (e.g., added memory cache = more RAM usage)
|
|
50
|
+
|
|
51
|
+
### Step 4: Verify
|
|
52
|
+
- Confirm the optimization doesn't break functionality
|
|
53
|
+
- Quantify the improvement
|
|
54
|
+
- Check for regressions in other areas
|
|
55
|
+
|
|
56
|
+
## Rules
|
|
57
|
+
- Measure before optimizing — never optimize based on assumptions
|
|
58
|
+
- Target the biggest bottleneck first (Amdahl's law)
|
|
59
|
+
- Simple optimizations first (caching, batching) before complex ones (architecture changes)
|
|
60
|
+
- Document performance tradeoffs in code comments
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-19
|
|
3
|
+
description: React 19 patterns with React Compiler. Trigger when writing React 19 components and hooks in .tsx — React Compiler rules, hook patterns, refs as props, Server Components, and the new APIs (use, useActionState, useOptimistic, useFormStatus).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React 19 Patterns
|
|
7
|
+
|
|
8
|
+
React 19 ships the React Compiler (no more manual memoization), new hooks, and ref-as-prop. Write components for the compiler, not against it.
|
|
9
|
+
|
|
10
|
+
## React Compiler Rules
|
|
11
|
+
|
|
12
|
+
The compiler automatically memoizes components and values — **do not add `useMemo`, `useCallback`, or `memo` manually** unless you have a profiler-confirmed performance issue.
|
|
13
|
+
|
|
14
|
+
### What the Compiler Handles Automatically
|
|
15
|
+
|
|
16
|
+
- Memoizing component renders when props haven't changed
|
|
17
|
+
- Memoizing derived values (replaces most `useMemo`)
|
|
18
|
+
- Memoizing callbacks (replaces most `useCallback`)
|
|
19
|
+
- Stabilizing refs
|
|
20
|
+
|
|
21
|
+
### Rules of Hooks (Stricter in React 19)
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// Rules the compiler enforces:
|
|
25
|
+
// 1. Only call hooks at the top level
|
|
26
|
+
// 2. Only call hooks from React functions
|
|
27
|
+
// 3. Don't mutate props or state directly
|
|
28
|
+
// 4. Don't read a ref during render (only in effects/handlers)
|
|
29
|
+
|
|
30
|
+
// Bad: conditional hook
|
|
31
|
+
function Component({ isAdmin }: { isAdmin: boolean }) {
|
|
32
|
+
if (isAdmin) {
|
|
33
|
+
const data = useData(); // compiler error
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Good: hook always called, condition inside
|
|
38
|
+
function Component({ isAdmin }: { isAdmin: boolean }) {
|
|
39
|
+
const data = useData();
|
|
40
|
+
if (!isAdmin) return null;
|
|
41
|
+
return <View data={data} />;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## New React 19 APIs
|
|
46
|
+
|
|
47
|
+
### `use()` — Read Resources in Render
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { use, Suspense } from 'react';
|
|
51
|
+
|
|
52
|
+
// use() unwraps promises and context — works inside conditionals
|
|
53
|
+
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
|
|
54
|
+
const user = use(userPromise); // suspends until resolved
|
|
55
|
+
return <div>{user.name}</div>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Wrap in Suspense at the boundary
|
|
59
|
+
function App() {
|
|
60
|
+
const userPromise = fetchUser(userId);
|
|
61
|
+
return (
|
|
62
|
+
<Suspense fallback={<Spinner />}>
|
|
63
|
+
<UserProfile userPromise={userPromise} />
|
|
64
|
+
</Suspense>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `useActionState()` — Form Actions with State
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { useActionState } from 'react';
|
|
73
|
+
|
|
74
|
+
async function submitForm(prevState: FormState, formData: FormData): Promise<FormState> {
|
|
75
|
+
const name = formData.get('name') as string;
|
|
76
|
+
if (!name) return { error: 'Name is required', data: null };
|
|
77
|
+
const data = await createUser({ name });
|
|
78
|
+
return { error: null, data };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function CreateUserForm() {
|
|
82
|
+
const [state, formAction, isPending] = useActionState(submitForm, { error: null, data: null });
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<form action={formAction}>
|
|
86
|
+
<input name="name" />
|
|
87
|
+
{state.error && <p className="text-red-500">{state.error}</p>}
|
|
88
|
+
<button type="submit" disabled={isPending}>
|
|
89
|
+
{isPending ? 'Creating...' : 'Create'}
|
|
90
|
+
</button>
|
|
91
|
+
</form>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `useOptimistic()` — Optimistic UI
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { useOptimistic } from 'react';
|
|
100
|
+
|
|
101
|
+
function MessageList({ messages }: { messages: Message[] }) {
|
|
102
|
+
const [optimisticMessages, addOptimistic] = useOptimistic(
|
|
103
|
+
messages,
|
|
104
|
+
(state, newMessage: Message) => [...state, newMessage]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
async function handleSend(text: string) {
|
|
108
|
+
const tempMessage = { id: crypto.randomUUID(), text, pending: true };
|
|
109
|
+
addOptimistic(tempMessage); // immediate UI update
|
|
110
|
+
await sendMessage(text); // actual request
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
{optimisticMessages.map(m => (
|
|
116
|
+
<div key={m.id} style={{ opacity: m.pending ? 0.5 : 1 }}>{m.text}</div>
|
|
117
|
+
))}
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `useFormStatus()` — Form Submission State
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
import { useFormStatus } from 'react-dom';
|
|
127
|
+
|
|
128
|
+
// Must be used inside a <form> component
|
|
129
|
+
function SubmitButton() {
|
|
130
|
+
const { pending } = useFormStatus();
|
|
131
|
+
return <button disabled={pending}>{pending ? 'Submitting...' : 'Submit'}</button>;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Ref as Prop (React 19)
|
|
136
|
+
|
|
137
|
+
Refs are now regular props — no more `forwardRef`:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// React 19: ref is just a prop
|
|
141
|
+
function Input({ ref, ...props }: React.ComponentProps<'input'>) {
|
|
142
|
+
return <input ref={ref} {...props} />;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Usage
|
|
146
|
+
function Form() {
|
|
147
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
148
|
+
return <Input ref={inputRef} placeholder="Name" />;
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Document Metadata in Components
|
|
153
|
+
|
|
154
|
+
React 19 renders `<title>`, `<meta>`, and `<link>` directly from components:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
function BlogPost({ post }: { post: Post }) {
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<title>{post.title}</title>
|
|
161
|
+
<meta name="description" content={post.excerpt} />
|
|
162
|
+
<link rel="canonical" href={post.url} />
|
|
163
|
+
<article>{post.content}</article>
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Component Patterns
|
|
170
|
+
|
|
171
|
+
### Compound Components with Context
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
const TabsContext = createContext<{ active: string; setActive: (id: string) => void } | null>(null);
|
|
175
|
+
|
|
176
|
+
function Tabs({ children, defaultTab }: { children: ReactNode; defaultTab: string }) {
|
|
177
|
+
const [active, setActive] = useState(defaultTab);
|
|
178
|
+
return (
|
|
179
|
+
<TabsContext value={{ active, setActive }}>
|
|
180
|
+
<div className="tabs">{children}</div>
|
|
181
|
+
</TabsContext>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function Tab({ id, children }: { id: string; children: ReactNode }) {
|
|
186
|
+
const ctx = use(TabsContext); // use() works with context too
|
|
187
|
+
if (!ctx) throw new Error('Tab must be inside Tabs');
|
|
188
|
+
return (
|
|
189
|
+
<button
|
|
190
|
+
onClick={() => ctx.setActive(id)}
|
|
191
|
+
className={ctx.active === id ? 'active' : ''}
|
|
192
|
+
>
|
|
193
|
+
{children}
|
|
194
|
+
</button>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
Tabs.Tab = Tab;
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Error Boundaries
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
// Use react-error-boundary for functional wrappers
|
|
205
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
206
|
+
|
|
207
|
+
function App() {
|
|
208
|
+
return (
|
|
209
|
+
<ErrorBoundary fallback={<ErrorPage />} onError={logError}>
|
|
210
|
+
<MyFeature />
|
|
211
|
+
</ErrorBoundary>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Rules for Clean Components
|
|
217
|
+
|
|
218
|
+
1. **One responsibility per component** — if a component needs a long comment to explain what it does, split it
|
|
219
|
+
2. **Derive state, don't sync it** — compute values from existing state rather than creating parallel state
|
|
220
|
+
3. **Lift state to the first common ancestor** — no lower
|
|
221
|
+
4. **Co-locate state with the component that needs it** — no higher
|
|
222
|
+
5. **Props down, events up** — callbacks are always prefixed with `on`
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
// Derived state: don't useEffect + setState
|
|
226
|
+
// Bad:
|
|
227
|
+
const [filteredUsers, setFilteredUsers] = useState(users);
|
|
228
|
+
useEffect(() => { setFilteredUsers(users.filter(u => u.active)); }, [users]);
|
|
229
|
+
|
|
230
|
+
// Good:
|
|
231
|
+
const filteredUsers = users.filter(u => u.active);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## TypeScript with React 19
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
// Component props
|
|
238
|
+
type ButtonProps = React.ComponentProps<'button'> & {
|
|
239
|
+
variant?: 'primary' | 'secondary';
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Generic components
|
|
243
|
+
function List<T extends { id: string }>({
|
|
244
|
+
items,
|
|
245
|
+
renderItem,
|
|
246
|
+
}: {
|
|
247
|
+
items: T[];
|
|
248
|
+
renderItem: (item: T) => ReactNode;
|
|
249
|
+
}) {
|
|
250
|
+
return <ul>{items.map(item => <li key={item.id}>{renderItem(item)}</li>)}</ul>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Event handlers
|
|
254
|
+
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
255
|
+
setValue(e.target.value);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refactorer
|
|
3
|
+
description: Safe, incremental code refactoring with verification. Use when you need to restructure code, extract functions, simplify logic, or improve code organization without changing behavior. Triggers on "refactor this", "clean up this code", "simplify this function", "extract this logic", or "this is too complex".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a refactoring specialist focused on improving code structure without changing behavior.
|
|
7
|
+
|
|
8
|
+
## Core Principles
|
|
9
|
+
- **No behavior changes** — refactoring must preserve existing functionality
|
|
10
|
+
- **Incremental steps** — make small, verifiable changes, not big-bang rewrites
|
|
11
|
+
- **Test-backed** — verify tests pass after each step; write tests first if none exist
|
|
12
|
+
- **Reversible** — every change should be easy to revert
|
|
13
|
+
|
|
14
|
+
## Process
|
|
15
|
+
|
|
16
|
+
### Step 1: Assess
|
|
17
|
+
1. Read the target code and all its callers/dependents
|
|
18
|
+
2. Run existing tests to establish a green baseline
|
|
19
|
+
3. Identify the specific code smells to address:
|
|
20
|
+
- Long functions (>50 lines)
|
|
21
|
+
- Deep nesting (>3 levels)
|
|
22
|
+
- Duplicated logic across files
|
|
23
|
+
- God classes/modules with too many responsibilities
|
|
24
|
+
- Shotgun surgery (changes requiring edits in many files)
|
|
25
|
+
- Feature envy (code using another module's data extensively)
|
|
26
|
+
|
|
27
|
+
### Step 2: Plan
|
|
28
|
+
Present a numbered refactoring plan with:
|
|
29
|
+
- Each step as an atomic, testable change
|
|
30
|
+
- What each step improves and why
|
|
31
|
+
- Risk assessment for each step
|
|
32
|
+
- Expected test impact
|
|
33
|
+
|
|
34
|
+
### Step 3: Execute
|
|
35
|
+
For each step:
|
|
36
|
+
1. Make the change
|
|
37
|
+
2. Run tests/lint/typecheck
|
|
38
|
+
3. If tests fail, revert and reassess
|
|
39
|
+
4. Move to next step only when green
|
|
40
|
+
|
|
41
|
+
### Step 4: Verify
|
|
42
|
+
- All original tests still pass
|
|
43
|
+
- No new lint warnings or type errors
|
|
44
|
+
- Code coverage hasn't decreased
|
|
45
|
+
- The public API is unchanged (unless explicitly requested)
|
|
46
|
+
|
|
47
|
+
## Common Refactoring Techniques
|
|
48
|
+
- **Extract Function**: Pull a coherent block into a named function
|
|
49
|
+
- **Extract Module**: Move related functions to their own file
|
|
50
|
+
- **Simplify Conditional**: Replace nested if/else with early returns or strategy pattern
|
|
51
|
+
- **Remove Duplication**: Extract shared logic into a utility
|
|
52
|
+
- **Rename**: Improve naming for clarity (update all references)
|
|
53
|
+
- **Inline**: Remove unnecessary indirection (single-use wrappers)
|
|
54
|
+
- **Decompose Class**: Split responsibilities into focused modules
|
|
55
|
+
|
|
56
|
+
## Safety Rules
|
|
57
|
+
- Never refactor and add features in the same change
|
|
58
|
+
- Never delete code without confirming it's unused (check all callers)
|
|
59
|
+
- If no tests exist, write characterization tests before refactoring
|
|
60
|
+
- Preserve all public interfaces unless the user explicitly approves breaking changes
|