@dennisrongo/skills 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 +230 -0
- package/bin/claude-skills.js +189 -0
- package/lib/install.js +111 -0
- package/lib/list.js +63 -0
- package/lib/paths.js +39 -0
- package/lib/remove.js +52 -0
- package/lib/skills.js +121 -0
- package/package.json +48 -0
- package/skills/_template/SKILL.md +34 -0
- package/skills/conventional-commits/SKILL.md +136 -0
- package/skills/diagnose/SKILL.md +140 -0
- package/skills/dotnet-onion-api/SKILL.md +267 -0
- package/skills/dotnet-onion-api/references/anti-patterns.md +155 -0
- package/skills/dotnet-onion-api/references/solution-layout.md +113 -0
- package/skills/dotnet-onion-api/references/templates/appsettings.md +75 -0
- package/skills/dotnet-onion-api/references/templates/base-controller.cs.md +90 -0
- package/skills/dotnet-onion-api/references/templates/csproj-files.md +178 -0
- package/skills/dotnet-onion-api/references/templates/dbcontext.cs.md +149 -0
- package/skills/dotnet-onion-api/references/templates/exception-middleware.cs.md +101 -0
- package/skills/dotnet-onion-api/references/templates/feature-slice.md +349 -0
- package/skills/dotnet-onion-api/references/templates/program-cs.md +171 -0
- package/skills/dotnet-onion-api/references/templates/worker-program.cs.md +166 -0
- package/skills/grill-with-docs/SKILL.md +203 -0
- package/skills/handoff/SKILL.md +155 -0
- package/skills/improve-codebase-architecture/DEEPENING.md +37 -0
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
- package/skills/improve-codebase-architecture/SKILL.md +121 -0
- package/skills/nextjs-app-router/SKILL.md +328 -0
- package/skills/nextjs-app-router/references/anti-patterns.md +203 -0
- package/skills/nextjs-app-router/references/folder-layout.md +151 -0
- package/skills/nextjs-app-router/references/good-patterns.md +212 -0
- package/skills/nextjs-app-router/references/templates/api-base.md +62 -0
- package/skills/nextjs-app-router/references/templates/api-slice.md +75 -0
- package/skills/nextjs-app-router/references/templates/auth-slice.md +95 -0
- package/skills/nextjs-app-router/references/templates/ci-and-hooks.md +94 -0
- package/skills/nextjs-app-router/references/templates/components-json.md +41 -0
- package/skills/nextjs-app-router/references/templates/env-and-utils.md +110 -0
- package/skills/nextjs-app-router/references/templates/eslint-prettier.md +82 -0
- package/skills/nextjs-app-router/references/templates/feature-slice.md +184 -0
- package/skills/nextjs-app-router/references/templates/form-with-zod.md +192 -0
- package/skills/nextjs-app-router/references/templates/middleware.md +88 -0
- package/skills/nextjs-app-router/references/templates/next-config.md +42 -0
- package/skills/nextjs-app-router/references/templates/package.md +99 -0
- package/skills/nextjs-app-router/references/templates/providers-and-store.md +79 -0
- package/skills/nextjs-app-router/references/templates/root-layout.md +146 -0
- package/skills/nextjs-app-router/references/templates/route-group-layouts.md +90 -0
- package/skills/nextjs-app-router/references/templates/tailwind-config.md +89 -0
- package/skills/nextjs-app-router/references/templates/testing.md +137 -0
- package/skills/nextjs-app-router/references/templates/tsconfig.md +40 -0
- package/skills/plan-and-build/SKILL.md +214 -0
- package/skills/pr-review/SKILL.md +132 -0
- package/skills/write-a-skill/SKILL.md +191 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Route group layouts
|
|
2
|
+
|
|
3
|
+
Layouts are **server components** by default. Push interactive chrome into `'use client'` children.
|
|
4
|
+
|
|
5
|
+
## `src/app/(public)/layout.tsx` (SERVER)
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
export default function PublicLayout({ children }: { children: React.ReactNode }) {
|
|
9
|
+
return (
|
|
10
|
+
<main className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
11
|
+
<div className="w-full max-w-md">{children}</div>
|
|
12
|
+
</main>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## `src/app/(app)/layout.tsx` (SERVER, renders client shell)
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { AppShell } from './_components/AppShell';
|
|
21
|
+
|
|
22
|
+
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
|
23
|
+
// Server-side session check happens in middleware.ts.
|
|
24
|
+
// This layout assumes the request reached here because the user is authenticated.
|
|
25
|
+
return <AppShell>{children}</AppShell>;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## `src/app/(app)/_components/AppShell.tsx` (CLIENT, holds interactive UI state)
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
'use client';
|
|
33
|
+
|
|
34
|
+
import { useState } from 'react';
|
|
35
|
+
import { Sidebar } from './Sidebar';
|
|
36
|
+
import { Header } from './Header';
|
|
37
|
+
|
|
38
|
+
export function AppShell({ children }: { children: React.ReactNode }) {
|
|
39
|
+
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex min-h-screen">
|
|
43
|
+
<Sidebar open={sidebarOpen} />
|
|
44
|
+
<div className="flex flex-1 flex-col">
|
|
45
|
+
<Header onToggleSidebar={() => setSidebarOpen((s) => !s)} />
|
|
46
|
+
<main className="flex-1 overflow-y-auto p-6">{children}</main>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## `src/app/(app)/loading.tsx` (SERVER)
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
export default function Loading() {
|
|
57
|
+
return (
|
|
58
|
+
<div className="flex h-[50vh] items-center justify-center">
|
|
59
|
+
<div className="size-8 animate-spin rounded-full border-2 border-muted border-t-foreground" />
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## `src/app/(app)/error.tsx` (CLIENT — Next requires it)
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
'use client';
|
|
69
|
+
|
|
70
|
+
import { useEffect } from 'react';
|
|
71
|
+
import { Button } from '@/components/ui/button';
|
|
72
|
+
|
|
73
|
+
export default function AppError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
console.error(error);
|
|
76
|
+
}, [error]);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className="flex flex-col items-center gap-4 p-8">
|
|
80
|
+
<h2 className="text-xl font-semibold">Couldn't load this page</h2>
|
|
81
|
+
<p className="text-muted-foreground">{error.message}</p>
|
|
82
|
+
<Button onClick={reset}>Retry</Button>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## `src/app/(admin)/layout.tsx` (OPTIONAL, SERVER)
|
|
89
|
+
|
|
90
|
+
Same shape as `(app)/layout.tsx`. The role check happens in `middleware.ts`; this layout just renders an admin-flavored shell.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# `tailwind.config.ts` template
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import type { Config } from 'tailwindcss';
|
|
5
|
+
|
|
6
|
+
const config: Config = {
|
|
7
|
+
darkMode: ['class'],
|
|
8
|
+
content: ['./src/**/*.{ts,tsx}'],
|
|
9
|
+
theme: {
|
|
10
|
+
container: {
|
|
11
|
+
center: true,
|
|
12
|
+
padding: '1rem',
|
|
13
|
+
screens: {
|
|
14
|
+
'2xl': '1400px',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
extend: {
|
|
18
|
+
colors: {
|
|
19
|
+
// shadcn theme tokens — driven by CSS variables in globals.css.
|
|
20
|
+
border: 'hsl(var(--border))',
|
|
21
|
+
input: 'hsl(var(--input))',
|
|
22
|
+
ring: 'hsl(var(--ring))',
|
|
23
|
+
background: 'hsl(var(--background))',
|
|
24
|
+
foreground: 'hsl(var(--foreground))',
|
|
25
|
+
primary: {
|
|
26
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
27
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
28
|
+
},
|
|
29
|
+
secondary: {
|
|
30
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
31
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
32
|
+
},
|
|
33
|
+
destructive: {
|
|
34
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
35
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
36
|
+
},
|
|
37
|
+
muted: {
|
|
38
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
39
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
40
|
+
},
|
|
41
|
+
accent: {
|
|
42
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
43
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
44
|
+
},
|
|
45
|
+
popover: {
|
|
46
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
47
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
48
|
+
},
|
|
49
|
+
card: {
|
|
50
|
+
DEFAULT: 'hsl(var(--card))',
|
|
51
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
52
|
+
},
|
|
53
|
+
// Brand colors go here with a project prefix (e.g. brand-primary).
|
|
54
|
+
},
|
|
55
|
+
borderRadius: {
|
|
56
|
+
lg: 'var(--radius)',
|
|
57
|
+
md: 'calc(var(--radius) - 2px)',
|
|
58
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
59
|
+
},
|
|
60
|
+
keyframes: {
|
|
61
|
+
'accordion-down': {
|
|
62
|
+
from: { height: '0' },
|
|
63
|
+
to: { height: 'var(--radix-accordion-content-height)' },
|
|
64
|
+
},
|
|
65
|
+
'accordion-up': {
|
|
66
|
+
from: { height: 'var(--radix-accordion-content-height)' },
|
|
67
|
+
to: { height: '0' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
animation: {
|
|
71
|
+
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
72
|
+
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
plugins: [require('tailwindcss-animate')],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default config;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Do NOT add to `extend`:**
|
|
83
|
+
- Dozens of custom spacing values. Tailwind's defaults cover almost every case.
|
|
84
|
+
- Commented-out animation entries.
|
|
85
|
+
- Custom widths/heights named after pixel counts. Use the arbitrary `[Npx]` syntax for one-off needs.
|
|
86
|
+
|
|
87
|
+
**Add to `extend.colors` only:**
|
|
88
|
+
- Brand colors with a project prefix.
|
|
89
|
+
- Semantic colors that the design system actually uses across multiple components.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Testing setup (Vitest + RTL + Playwright)
|
|
2
|
+
|
|
3
|
+
A new project ships with three real tests: a reducer test, a component test, and an E2E auth spec. Three is enough to make CI meaningful; the user adds more as features grow.
|
|
4
|
+
|
|
5
|
+
## `vitest.config.ts`
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { defineConfig } from 'vitest/config';
|
|
9
|
+
import react from '@vitejs/plugin-react';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
plugins: [react()],
|
|
14
|
+
test: {
|
|
15
|
+
environment: 'jsdom',
|
|
16
|
+
globals: true,
|
|
17
|
+
setupFiles: ['./tests/setup.ts'],
|
|
18
|
+
css: false,
|
|
19
|
+
include: ['tests/unit/**/*.{test,spec}.{ts,tsx}'],
|
|
20
|
+
},
|
|
21
|
+
resolve: {
|
|
22
|
+
alias: {
|
|
23
|
+
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## `tests/setup.ts`
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import '@testing-library/jest-dom/vitest';
|
|
33
|
+
import { cleanup } from '@testing-library/react';
|
|
34
|
+
import { afterEach } from 'vitest';
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
cleanup();
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## `tests/unit/auth.slice.test.ts`
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { describe, expect, it } from 'vitest';
|
|
45
|
+
import { authSlice, logout, setUser } from '@/redux/features/authSlice';
|
|
46
|
+
|
|
47
|
+
const initial = authSlice.getInitialState();
|
|
48
|
+
|
|
49
|
+
describe('authSlice', () => {
|
|
50
|
+
it('starts logged out', () => {
|
|
51
|
+
expect(initial.user).toBeNull();
|
|
52
|
+
expect(initial.token).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('sets the user', () => {
|
|
56
|
+
const state = authSlice.reducer(initial, setUser({ id: '1', email: 'a@b.c', name: 'A', roles: [] }));
|
|
57
|
+
expect(state.user?.id).toBe('1');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('logout resets state', () => {
|
|
61
|
+
const seeded = { ...initial, user: { id: '1', email: 'a@b.c', name: 'A', roles: [] } };
|
|
62
|
+
expect(authSlice.reducer(seeded, logout())).toEqual(initial);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## `tests/unit/login-form.test.tsx`
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { describe, expect, it } from 'vitest';
|
|
71
|
+
import { render, screen } from '@testing-library/react';
|
|
72
|
+
import userEvent from '@testing-library/user-event';
|
|
73
|
+
import { Provider } from 'react-redux';
|
|
74
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
75
|
+
import { api } from '@/redux/api/api';
|
|
76
|
+
import { authSlice } from '@/redux/features/authSlice';
|
|
77
|
+
import { LoginForm } from '@/app/(public)/auth/login/_components/LoginForm';
|
|
78
|
+
|
|
79
|
+
function renderWithStore(ui: React.ReactElement) {
|
|
80
|
+
const store = configureStore({
|
|
81
|
+
reducer: { [api.reducerPath]: api.reducer, auth: authSlice.reducer },
|
|
82
|
+
middleware: (gdm) => gdm().concat(api.middleware),
|
|
83
|
+
});
|
|
84
|
+
return render(<Provider store={store}>{ui}</Provider>);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe('<LoginForm />', () => {
|
|
88
|
+
it('shows a validation error for an empty email', async () => {
|
|
89
|
+
renderWithStore(<LoginForm />);
|
|
90
|
+
await userEvent.click(screen.getByRole('button', { name: /sign in/i }));
|
|
91
|
+
expect(await screen.findByText(/required|invalid/i)).toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## `playwright.config.ts`
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
100
|
+
|
|
101
|
+
export default defineConfig({
|
|
102
|
+
testDir: './tests/e2e',
|
|
103
|
+
fullyParallel: true,
|
|
104
|
+
forbidOnly: !!process.env.CI,
|
|
105
|
+
retries: process.env.CI ? 1 : 0,
|
|
106
|
+
reporter: process.env.CI ? 'github' : 'list',
|
|
107
|
+
use: {
|
|
108
|
+
baseURL: process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:3000',
|
|
109
|
+
trace: 'on-first-retry',
|
|
110
|
+
},
|
|
111
|
+
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
|
112
|
+
webServer: {
|
|
113
|
+
command: 'pnpm dev',
|
|
114
|
+
url: 'http://localhost:3000',
|
|
115
|
+
reuseExistingServer: !process.env.CI,
|
|
116
|
+
timeout: 120_000,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## `tests/e2e/auth.spec.ts`
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { test, expect } from '@playwright/test';
|
|
125
|
+
|
|
126
|
+
test('unauthenticated user is redirected to login', async ({ page }) => {
|
|
127
|
+
await page.goto('/dashboard');
|
|
128
|
+
await expect(page).toHaveURL(/\/auth\/login/);
|
|
129
|
+
await expect(page.getByRole('heading', { name: /sign in/i })).toBeVisible();
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
That spec alone validates: middleware fires, the login page renders, the route group split works. If any of those regress, this test breaks.
|
|
134
|
+
|
|
135
|
+
**Notes:**
|
|
136
|
+
- Do not include Playwright in the default `pnpm test` script — keep it as `pnpm test:e2e` so CI can run Vitest fast and Playwright separately (or in a different job).
|
|
137
|
+
- Real backend mocking (MSW) is *not* in the default scaffold. Add only if the user asks.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# `tsconfig.json` template
|
|
2
|
+
|
|
3
|
+
```jsonc
|
|
4
|
+
{
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
8
|
+
"module": "esnext",
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"jsx": "preserve",
|
|
11
|
+
"incremental": true,
|
|
12
|
+
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUncheckedIndexedAccess": true,
|
|
15
|
+
"noImplicitOverride": true,
|
|
16
|
+
"forceConsistentCasingInFileNames": true,
|
|
17
|
+
|
|
18
|
+
"allowJs": false,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"esModuleInterop": true,
|
|
21
|
+
"isolatedModules": true,
|
|
22
|
+
"resolveJsonModule": true,
|
|
23
|
+
"verbatimModuleSyntax": false,
|
|
24
|
+
|
|
25
|
+
"noEmit": true,
|
|
26
|
+
"plugins": [{ "name": "next" }],
|
|
27
|
+
"paths": {
|
|
28
|
+
"@/*": ["./src/*"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts", "tests/**/*.ts", "tests/**/*.tsx"],
|
|
32
|
+
"exclude": ["node_modules", ".next", "out", "dist"]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Rationale for the non-default flags:**
|
|
37
|
+
- `noUncheckedIndexedAccess: true` — accessing an array index or object key by string now returns `T | undefined`. Catches a whole class of "looks fine, crashes at runtime" bugs.
|
|
38
|
+
- `forceConsistentCasingInFileNames: true` — surfaces the duplicate-by-case folder bug at compile time, before Linux production does.
|
|
39
|
+
- `noImplicitOverride: true` — requires `override` on subclass methods.
|
|
40
|
+
- `allowJs: false` — the project is TypeScript-only; no surprise `.js` files leaking in.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan-and-build
|
|
3
|
+
description: Plan-first feature builder. Grills the user about the feature until the design is unambiguous, presents a plan, gates on `ExitPlanMode` approval, then builds the feature using the project's existing patterns — TDD-first with NUnit when the work touches a .NET API (appending to the matching test class if one already exists), and writing migration files but never executing SQL or `dotnet ef database update`. Use this skill whenever the user says "build a feature", "add a feature", "implement this feature", "/plan-and-build", "plan and build", "new feature", or pastes a feature spec and asks you to design + implement it — even if they don't name the skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plan and Build
|
|
7
|
+
|
|
8
|
+
Build a feature the way a careful senior engineer would: understand it first, design it against the existing codebase, get explicit sign-off on the plan, then implement it by reusing what's already there. Never start writing code on a fuzzy spec.
|
|
9
|
+
|
|
10
|
+
## When to use this skill
|
|
11
|
+
|
|
12
|
+
Trigger on any of:
|
|
13
|
+
|
|
14
|
+
- "build a feature" / "add a feature" / "implement this feature"
|
|
15
|
+
- "new feature" / "add `<X>` end-to-end" when a spec is attached or pasted
|
|
16
|
+
- `/plan-and-build` or "plan and build"
|
|
17
|
+
- The user pastes a feature description, ticket, or screenshot and asks for implementation
|
|
18
|
+
- Any request that will plausibly touch an API endpoint **and** a data model — the kind of change that needs tests and a migration
|
|
19
|
+
|
|
20
|
+
Do **not** use this skill for: one-line fixes, renames, single-file refactors, doc edits, or anything the user has already pre-designed in detail and explicitly asked you to just type in. Use [`diagnose`](../diagnose/SKILL.md) for bugs.
|
|
21
|
+
|
|
22
|
+
## Phases
|
|
23
|
+
|
|
24
|
+
The skill has six phases. Do **not** skip Phase 1 or Phase 3 — they're the value of this skill. Other phases compress naturally when the feature is small.
|
|
25
|
+
|
|
26
|
+
### Phase 1 — Grill
|
|
27
|
+
|
|
28
|
+
Interview the user relentlessly about every aspect of the feature until you reach a shared, unambiguous understanding. Walk down each branch of the design tree, resolving dependencies between decisions one at a time.
|
|
29
|
+
|
|
30
|
+
If the feature is large, fuzzy, or still in discovery — defer to [`grill-with-docs`](../grill-with-docs/SKILL.md) first to sharpen the spec and update `CONTEXT.md` / ADRs, then come back here to build. For smaller, well-scoped features keep grilling inline below.
|
|
31
|
+
|
|
32
|
+
Rules:
|
|
33
|
+
|
|
34
|
+
- **One question at a time.** Wait for the answer before the next one. Use `AskUserQuestion` with 2–4 concrete options and a recommended pick first.
|
|
35
|
+
- **Explore the codebase instead of asking** when the answer is already there. If a similar feature, entity, controller, or migration exists, read it before asking the user how a related thing should work.
|
|
36
|
+
- **Sharpen fuzzy language.** When the user says "account", ask: "Customer or User? Those are different things here." When they say "cancel", ask whether that means soft-delete, hard-delete, or a status transition.
|
|
37
|
+
- **Probe with concrete scenarios.** Invent edge cases that force the user to be precise. "What happens if the user submits twice while the first request is still in flight?"
|
|
38
|
+
- **Cross-reference with code.** If the user's stated behaviour contradicts what the code already does, surface it. "Your existing `OrderService.Cancel` cancels the whole order — you just described partial cancellation. Which wins?"
|
|
39
|
+
- **Look for a `CONTEXT.md` glossary** at repo root (or `CONTEXT-MAP.md` for multi-context repos) and at the relevant `src/<context>/CONTEXT.md`. If terminology conflicts with the glossary, call it out immediately and reconcile.
|
|
40
|
+
|
|
41
|
+
Topics to cover before leaving Phase 1 — pick the ones the spec hasn't already answered:
|
|
42
|
+
|
|
43
|
+
- **Intent.** What problem does this solve, for whom, and how do we know it worked?
|
|
44
|
+
- **Inputs / outputs.** Exact request shape, response shape, error cases.
|
|
45
|
+
- **Domain.** Which entities, value objects, and aggregates are touched. New ones vs. extensions to existing ones.
|
|
46
|
+
- **Authorization.** Who can call this? What claims/roles? Multi-tenant scoping?
|
|
47
|
+
- **Persistence.** Does it need schema changes? New columns, new tables, new indexes? Backfill required?
|
|
48
|
+
- **Side effects.** Emails, queue messages, audit log entries, cache invalidation, webhook calls.
|
|
49
|
+
- **Concurrency.** Idempotency keys, locking, retry behaviour.
|
|
50
|
+
- **Edge cases.** Empty inputs, max sizes, unicode, timezones, partial failure.
|
|
51
|
+
- **Observability.** What gets logged / traced / metricked.
|
|
52
|
+
|
|
53
|
+
Stop asking the moment further questions stop changing the design. Don't pad.
|
|
54
|
+
|
|
55
|
+
### Phase 2 — Detect stack and conventions
|
|
56
|
+
|
|
57
|
+
Before designing, look at what the repo already does so the plan reuses it. Run these in parallel with `Glob` / `Grep` / `Read`:
|
|
58
|
+
|
|
59
|
+
- **Backend stack.** Look for `*.sln`, `*.csproj`, `package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `pom.xml`, `build.gradle`. Note framework (ASP.NET Core, Next.js API routes, Express, Django, etc.).
|
|
60
|
+
- **Test framework.** For .NET: check existing test `.csproj` for `NUnit`, `xunit`, or `MSTest` package references. **The user's standard for this skill is NUnit when the project is .NET** — but if the repo is already on xUnit or MSTest, surface that conflict to the user before writing tests in a different framework.
|
|
61
|
+
- **Existing patterns.** Find a recent feature slice resembling the requested one and read it end-to-end (entity, port/interface, service, controller, mapping, validator, repository, tests, migration). The new code must match its shape.
|
|
62
|
+
- **DI / wiring.** How are services registered? Scrutor auto-registration, hand-rolled `services.AddScoped<>`, `Microsoft.Extensions.DependencyInjection` modules, etc.
|
|
63
|
+
- **Persistence layer.** EF Core (look for `DbContext`), Dapper, raw ADO, or another ORM. Migration tool: EF Core migrations (`Migrations/` folder), FluentMigrator, DbUp, Flyway, Prisma, Knex, Alembic.
|
|
64
|
+
- **Validation, mapping, error responses.** FluentValidation vs. DataAnnotations; AutoMapper vs. hand-written; centralized exception middleware vs. controller-level try/catch.
|
|
65
|
+
- **Folder conventions.** Per-feature folders, ONION layering, vertical slices — match whatever's already there.
|
|
66
|
+
|
|
67
|
+
Record what you find. The plan will reference these directly so the user can see you're not inventing new patterns.
|
|
68
|
+
|
|
69
|
+
### Phase 3 — Plan
|
|
70
|
+
|
|
71
|
+
Enter Plan Mode (`EnterPlanMode`). The plan must include:
|
|
72
|
+
|
|
73
|
+
1. **Restatement of the feature** in one short paragraph, using the project's canonical terminology from `CONTEXT.md` (if present).
|
|
74
|
+
2. **Files to create**, grouped by layer, each with a one-line purpose.
|
|
75
|
+
3. **Files to modify**, with a one-line description of the change (e.g. "register `IOrderRepository → OrderRepository` in `DependencyInjection.cs`").
|
|
76
|
+
4. **Test plan.** For each new behaviour, list the NUnit test cases by name (or the project's actual test framework if different). Mark which existing test class each test will be **appended to** vs. which test class is new.
|
|
77
|
+
5. **Migration plan.** If the feature touches the schema:
|
|
78
|
+
- The migration name (e.g. `AddPriorityToOrders`).
|
|
79
|
+
- The columns / tables / indexes added, dropped, or altered.
|
|
80
|
+
- Whether a backfill is required and how it's gated.
|
|
81
|
+
- **Always end with:** "Migration file will be generated. No SQL, no `dotnet ef database update`, no `ExecuteSql*` calls will be run — the user runs the migration themselves."
|
|
82
|
+
6. **Pattern reuse table.** Two columns: "New code I'm about to write" and "Existing example it follows" with a path. If a row has no existing example, justify the new pattern.
|
|
83
|
+
7. **Open questions** still unresolved, if any. If there are any, loop back to Phase 1 — do not exit plan mode with open questions.
|
|
84
|
+
|
|
85
|
+
Then exit with `ExitPlanMode` and wait. **Do not write a single file until the user approves the plan.** If the user pushes back, revise and re-present; don't half-implement against an unapproved plan.
|
|
86
|
+
|
|
87
|
+
### Phase 4 — Build (test-first when an API changes)
|
|
88
|
+
|
|
89
|
+
Build in the order that gives you the fastest feedback loop.
|
|
90
|
+
|
|
91
|
+
**If the feature adds or changes an API endpoint, follow TDD:**
|
|
92
|
+
|
|
93
|
+
1. **Locate the test class.** For each API change, search for an existing test class that already covers that controller / service / use-case. Common patterns:
|
|
94
|
+
- `tests/**/<Controller>Tests.cs` for `<Controller>Controller.cs`
|
|
95
|
+
- `tests/**/<Service>Tests.cs` for `<Service>.cs`
|
|
96
|
+
- `tests/**/<UseCase>Tests.cs` for a CQRS handler
|
|
97
|
+
Use `Glob` with a broad pattern (`**/<Name>Tests.cs`, `**/*<Feature>*Tests.cs`) before deciding nothing exists.
|
|
98
|
+
2. **Append, don't duplicate.** If a test class already exists for that section, **add new `[Test]` / `[TestCase]` methods to it** rather than creating a parallel class. Reuse its `[SetUp]`, fixtures, `TestCaseSource`s, and helper builders. Never create `FooServiceTests2.cs`.
|
|
99
|
+
3. **Write the tests first**, covering at minimum:
|
|
100
|
+
- Happy path (correct inputs → correct outputs, correct state change)
|
|
101
|
+
- Validation failures (each rule that should reject input)
|
|
102
|
+
- Authorization failures (when applicable)
|
|
103
|
+
- Edge cases identified in Phase 1 (idempotency, concurrent calls, empty/max inputs)
|
|
104
|
+
- Persistence side effects (entity saved, row count, foreign key set)
|
|
105
|
+
4. **Run the tests, watch them fail** for the right reason (not "class not found"). If they fail for the wrong reason, fix the test before writing production code.
|
|
106
|
+
5. **Implement just enough** to turn each test green, following the patterns from Phase 2.
|
|
107
|
+
6. **Refactor for DRY** once green. Extract helpers, builders, and parameterized cases. Reuse `TestCaseSource` / `[Values]` rather than copy-pasting near-identical tests.
|
|
108
|
+
|
|
109
|
+
**If the feature is non-API (worker, frontend page, internal job),** still write tests where the project has a precedent. Skip the "TDD by default" cycle only when there is no testable seam and the project itself doesn't test that layer — and call that out in the plan.
|
|
110
|
+
|
|
111
|
+
**Code rules during the build:**
|
|
112
|
+
|
|
113
|
+
- **Reuse existing patterns.** No new abstraction layers. No new base classes. No new DI container. If you need a helper that's almost identical to one that exists, use the existing one or extend it — don't fork.
|
|
114
|
+
- **DRY.** If you find yourself typing the same three-line block twice, extract it. If you're tempted to copy a controller's wiring code, find the existing extension method that does it.
|
|
115
|
+
- **Minimal comments.** Default to no comments. Only add one when the *why* is non-obvious (a workaround, a non-trivial invariant, a domain rule that isn't visible from the names). Never write block headers, never restate what the next line does, never leave `// TODO` without an issue link.
|
|
116
|
+
- **Match formatting.** Use the project's brace style, naming, file headers, `using` placement. Don't reformat unrelated code.
|
|
117
|
+
- **No dead code.** Don't commit commented-out lines or "for future use" stubs.
|
|
118
|
+
|
|
119
|
+
### Phase 5 — Migrations (generate only, never execute)
|
|
120
|
+
|
|
121
|
+
If the feature requires a schema change:
|
|
122
|
+
|
|
123
|
+
1. **Generate the migration file using the project's tool** — but only the file. Examples:
|
|
124
|
+
- **EF Core:** Write the `Migrations/<Timestamp>_<Name>.cs` and `Migrations/<Timestamp>_<Name>.Designer.cs` (and update the model snapshot) **by hand from the existing migrations as templates**. Do not run `dotnet ef migrations add` if that would also write to a connected DB or trigger a connection. If running it locally is required to get the snapshot right, run it against an in-memory provider or document why.
|
|
125
|
+
- **FluentMigrator / DbUp / Flyway / Knex / Alembic:** Create the migration script in the conventional location matching existing migrations' naming.
|
|
126
|
+
2. **Never run the migration.** Forbidden during this skill:
|
|
127
|
+
- `dotnet ef database update`
|
|
128
|
+
- `flyway migrate`, `knex migrate:latest`, `alembic upgrade`, `prisma migrate deploy`
|
|
129
|
+
- Any `ExecuteSqlRaw` / `ExecuteSqlInterpolated` call that performs DDL
|
|
130
|
+
- Raw `CREATE TABLE` / `ALTER TABLE` issued through a SQL client
|
|
131
|
+
3. **Never run ad-hoc queries against the user's database** as part of the build, even read-only. If a question requires data, ask the user to run it and paste results.
|
|
132
|
+
4. **Tell the user what to run.** End the build report with the exact command the user should run themselves, e.g.:
|
|
133
|
+
> "Migration `20260515_AddPriorityToOrders` is generated. Run `dotnet ef database update -p src/Acme.Infrastructure -s src/Acme.Api` when you're ready."
|
|
134
|
+
5. **Flag destructive changes** in the report: `DROP COLUMN`, `DROP TABLE`, `ALTER COLUMN ... NOT NULL` without a default on existing data, renames that EF Core models as drop+add. Tell the user the data-loss risk before they run anything.
|
|
135
|
+
|
|
136
|
+
### Phase 6 — Verify and report
|
|
137
|
+
|
|
138
|
+
- Run the test suite for the affected project(s). Tests must pass.
|
|
139
|
+
- Run the build (`dotnet build`, `pnpm build`, `pytest --collect-only`, etc.) and confirm it's green.
|
|
140
|
+
- Re-grep for stray comments, debug logs, dead code, and undeleted scratch files.
|
|
141
|
+
- Report:
|
|
142
|
+
- Files created, files modified (paths only — let the user open them).
|
|
143
|
+
- Test summary: counts of tests added per class, classes appended-to vs. created.
|
|
144
|
+
- Migration name + the exact command the user should run.
|
|
145
|
+
- Anything the plan said you'd do but you ended up not doing, with a one-line reason.
|
|
146
|
+
|
|
147
|
+
## Test-class discovery rules (the "append vs. create" decision)
|
|
148
|
+
|
|
149
|
+
When you're about to write a test, decide where it goes using this order. Stop at the first match.
|
|
150
|
+
|
|
151
|
+
1. **Exact match on the unit under test.** `OrderService.cs` → look for `OrderServiceTests.cs`, `OrderServiceTest.cs`, `OrderService_Tests.cs` anywhere under `tests/`. Append there.
|
|
152
|
+
2. **Behavioural grouping.** Some codebases group by feature, not class: `tests/Orders/CancellationTests.cs` covers `OrderService.Cancel`, `CancellationPolicy.Evaluate`, and the controller endpoint together. If such a class exists for this feature area, append.
|
|
153
|
+
3. **Controller-level integration tests.** If the project uses `WebApplicationFactory` integration tests grouped per controller (`OrdersControllerTests.cs`), and the new endpoint is on `OrdersController`, append there as well as / instead of a service-level class — match the project's habit.
|
|
154
|
+
4. **Only create a new test class** when none of the above match. Name it to mirror the existing convention (don't introduce a new naming style).
|
|
155
|
+
|
|
156
|
+
When appending:
|
|
157
|
+
|
|
158
|
+
- Reuse the existing `[SetUp]` / fixtures / mocks rather than redeclaring them.
|
|
159
|
+
- Add to existing `TestCaseSource` arrays when the new case is the same shape.
|
|
160
|
+
- Don't reorder or reformat the existing tests in the class.
|
|
161
|
+
|
|
162
|
+
## Required patterns
|
|
163
|
+
|
|
164
|
+
- **Tests are first-class.** Test code follows the same DRY and minimal-comments rules as production code. Use builders / object mothers for fixtures rather than copy-pasted setup blocks.
|
|
165
|
+
- **One reason to change per file.** If you find yourself editing seven files for one logical concept and one of them is "unrelated cleanup", drop the cleanup.
|
|
166
|
+
- **CancellationToken plumbing** in async .NET code: every new async method takes `CancellationToken ct` as its last parameter and forwards it.
|
|
167
|
+
- **No new dependencies without asking.** If the feature seems to need a new NuGet/npm package, raise it in Phase 1 — don't quietly add one in Phase 4.
|
|
168
|
+
|
|
169
|
+
## Anti-patterns
|
|
170
|
+
|
|
171
|
+
- ❌ Skipping Phase 1 and going straight to "let me read the code and start building" — the whole point of this skill is the grill.
|
|
172
|
+
- ❌ Exiting plan mode while open questions remain. Loop back instead.
|
|
173
|
+
- ❌ Writing production code before the test it makes pass (when the feature touches an API).
|
|
174
|
+
- ❌ Creating `FooServiceTests2.cs` because `FooServiceTests.cs` already exists. Always append.
|
|
175
|
+
- ❌ Inventing a new pattern for something the codebase already has a pattern for. If you can't find the precedent, ask before forking.
|
|
176
|
+
- ❌ Running `dotnet ef database update`, `prisma migrate deploy`, or any DDL against the user's DB.
|
|
177
|
+
- ❌ Running ad-hoc `SELECT` / `UPDATE` queries against the user's DB to "check something" — ask the user instead.
|
|
178
|
+
- ❌ Block comments and "what this does" comments. Names should do that work.
|
|
179
|
+
- ❌ Leaving debug logs, `// TODO`s, or commented-out code in the final files.
|
|
180
|
+
- ❌ Reformatting unrelated code in the diff. Keep the change small.
|
|
181
|
+
|
|
182
|
+
## Examples
|
|
183
|
+
|
|
184
|
+
### Example 1: Add a feature to a .NET ONION API
|
|
185
|
+
|
|
186
|
+
**User:** "Plan and build a feature: customers can mark an order as 'priority'. It should bump the order in the queue and audit who set it."
|
|
187
|
+
|
|
188
|
+
**Claude:**
|
|
189
|
+
1. **Phase 1 — Grill.** Asks one question at a time: "Is 'priority' a boolean or an ordered priority (1–N)?" → recommends boolean for v1. Then: "Which roles can set it?" → checks existing `[Authorize(Roles=...)]` usage. Then: "What does 'bump in the queue' mean — sort order in the API response, or affect a background processor?" Continues until the spec is unambiguous, reading `OrderService.cs`, `OrdersController.cs`, and a recent `Migrations/*_AddX.cs` along the way to inform questions.
|
|
190
|
+
2. **Phase 2 — Detect.** Records: ASP.NET Core 8, EF Core 8, NUnit, FluentValidation, AutoMapper, Scrutor, FluentMigrator-style EF migrations under `src/Acme.Infrastructure/Migrations/`.
|
|
191
|
+
3. **Phase 3 — Plan.** Enters plan mode. Lists: new `IsPriority` column on `Orders` + `SetPriorityAsync` on `OrderService` + `[HttpPatch("{id}/priority")]` on `OrdersController` + `OrderAuditLog` row insert. Test plan: appends `SetPriority_HappyPath`, `SetPriority_Unauthorized`, `SetPriority_AlreadyPriority_IsIdempotent` to existing `OrderServiceTests.cs` (NUnit). Migration plan: `AddPriorityToOrders` adds a non-nullable `bit` with default `0`, no backfill needed. Pattern reuse table maps each new file to a precedent in the repo. Exits plan mode and waits.
|
|
192
|
+
4. **Phase 4 — Build (TDD).** Writes the three new `[Test]` methods inside the existing `OrderServiceTests.cs`, reusing its `[SetUp]`-injected mocks. Runs `dotnet test` — red for the right reason. Implements `OrderService.SetPriorityAsync` and the controller endpoint following the recent `OrderService.Cancel` shape. Reruns — green.
|
|
193
|
+
5. **Phase 5 — Migration.** Writes `Migrations/20260515120000_AddPriorityToOrders.cs` and the model snapshot update by hand from the previous migration as a template. Does **not** run `dotnet ef database update`.
|
|
194
|
+
6. **Phase 6 — Report.** Lists files, summarises tests (3 appended to `OrderServiceTests.cs`, 0 new test classes), quotes the exact migration command for the user.
|
|
195
|
+
|
|
196
|
+
### Example 2: Add a feature without a schema change
|
|
197
|
+
|
|
198
|
+
**User:** "Add an endpoint to export the customer list as CSV."
|
|
199
|
+
|
|
200
|
+
**Claude:** Grills (CSV format, columns, filter scope, auth). Detects stack. Plans: new `ExportAsync` on `CustomerService`, new `[HttpGet("export")]` on `CustomersController`, no schema change. Appends two `[Test]` methods to existing `CustomerServiceTests.cs`. Builds. No migration phase. Reports.
|
|
201
|
+
|
|
202
|
+
### Example 3: User asks to skip the plan
|
|
203
|
+
|
|
204
|
+
**User:** "Just add a `DeletedAt` column to Users and a soft-delete endpoint, you don't need to grill me."
|
|
205
|
+
|
|
206
|
+
**Claude:** Still asks the questions whose answers affect the design — "Hard-delete or soft-delete the underlying row? Does any query need to opt in to seeing deleted users? Cascading effect on Orders?" — but compresses Phase 1 to those questions only. Still enters plan mode with the migration plan. Still does not run the migration.
|
|
207
|
+
|
|
208
|
+
## Notes
|
|
209
|
+
|
|
210
|
+
- This skill **composes with** [`dotnet-onion-api`](../dotnet-onion-api/SKILL.md) and [`nextjs-app-router`](../nextjs-app-router/SKILL.md): if the user's repo was scaffolded with one of those, follow that skill's layout and forbidden-pattern list when generating new files.
|
|
211
|
+
- If the project uses `CONTEXT.md` / `docs/adr/`, prefer adding to those over inventing a new doc folder. Update `CONTEXT.md` inline when a Phase 1 conversation resolves a fuzzy term — it's a glossary, not a spec.
|
|
212
|
+
- If the feature seems to demand an ADR (hard-to-reverse choice, surprising to a future reader, a real trade-off with alternatives), offer it after the build, not before — you have more information once it's working.
|
|
213
|
+
- The "no SQL execution" rule is absolute. If the user explicitly asks you to run a migration, decline within this skill and tell them to run it themselves, then mention they can drop the skill if they want autonomous execution.
|
|
214
|
+
- Adapted in part from Matt Pocock's `grill-with-docs` skill (interview-relentlessly, sharpen-fuzzy-terms, cross-reference-with-code), extended with a build phase and explicit guardrails around tests and migrations. The standalone [`grill-with-docs`](../grill-with-docs/SKILL.md) in this library handles the discovery-only case.
|