@polymorphism-tech/morph-spec 4.10.7 → 4.10.8
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/CLAUDE.md +3 -3
- package/LICENSE +71 -71
- package/README.md +379 -379
- package/bin/morph-spec.js +1 -1
- package/claude-plugin.json +14 -14
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +206 -206
- package/framework/agents/README.md +14 -14
- package/framework/agents/architecture/standards-architect.md +159 -159
- package/framework/agents/frontend/blazor-builder.md +13 -0
- package/framework/agents/frontend/css-specialist.md +87 -0
- package/framework/agents/frontend/nextjs-expert.md +13 -0
- package/framework/agents/frontend/nextjs-ui-specialist.md +218 -0
- package/framework/agents/frontend/ui-ux-designer.md +80 -10
- package/framework/agents/infrastructure/azure-architect.md +149 -149
- package/framework/agents/infrastructure/infra-architect.md +48 -48
- package/framework/agents.json +2268 -2204
- package/framework/hooks/README.md +204 -204
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +10 -24
- package/framework/hooks/shared/phase-utils.js +139 -139
- package/framework/hooks/shared/worktree-helpers.js +30 -8
- package/framework/rules/csharp-standards.md +13 -13
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +16 -16
- package/framework/rules/testing-standards.md +14 -14
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +12 -0
- package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +254 -254
- package/framework/skills/level-0-meta/morph-init/SKILL.md +88 -3
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +247 -247
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +270 -270
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +515 -499
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +508 -500
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +12 -1
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +272 -262
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +317 -311
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +424 -323
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +2040 -2040
- package/framework/standards/frontend/nextjs/motion-patterns.md +321 -0
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +17 -17
- package/framework/templates/README.md +814 -814
- package/framework/templates/code/dotnet/contracts/Commands.cs +74 -74
- package/framework/templates/code/dotnet/contracts/Entities.cs +25 -25
- package/framework/templates/code/dotnet/contracts/Queries.cs +74 -74
- package/framework/templates/code/dotnet/contracts/README.md +74 -74
- package/framework/templates/code/dotnet/contracts/api-contracts.cs +173 -173
- package/framework/templates/code/dotnet/frontend/component.razor +239 -239
- package/framework/templates/code/dotnet/jobs/agent.cs +163 -163
- package/framework/templates/code/dotnet/jobs/job.cs +171 -171
- package/framework/templates/code/dotnet/test.cs +239 -239
- package/framework/templates/code/sql/rls-policy.sql +57 -57
- package/framework/templates/code/sql/supabase-migration.sql +100 -100
- package/framework/templates/code/sql/supabase-migration.template.sql +113 -113
- package/framework/templates/code/typescript/contracts.ts +168 -168
- package/framework/templates/context/CONTEXT.md +181 -181
- package/framework/templates/context/design-system.md +211 -0
- package/framework/templates/docs/proposal.md +182 -182
- package/framework/templates/docs/user-stories.md +34 -34
- package/framework/templates/examples/design-system-examples.md +357 -357
- package/framework/templates/examples/spec-examples.md +90 -90
- package/framework/templates/feature/decisions.md +187 -187
- package/framework/templates/feature/recap.md +146 -146
- package/framework/templates/feature/tasks.md +199 -199
- package/framework/templates/infrastructure/azure/README.md +286 -286
- package/framework/templates/infrastructure/azure/deploy-checklist.md +426 -426
- package/framework/templates/infrastructure/azure/parameters.dev.json +29 -29
- package/framework/templates/infrastructure/azure/parameters.prod.json +29 -29
- package/framework/templates/infrastructure/azure/parameters.staging.json +29 -29
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +67 -67
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/docker/easypanel.template.json +54 -54
- package/package.json +82 -82
- package/src/commands/agents/index.js +13 -13
- package/src/commands/project/install-plugin-cmd.js +28 -28
- package/src/commands/project/setup-infra-cmd.js +12 -12
- package/src/commands/project/worktree.js +58 -2
- package/src/commands/tasks/index.js +4 -4
- package/src/commands/templates/index.js +5 -5
- package/src/commands/utils/index.js +3 -3
- package/src/commands/validation/index.js +4 -4
- package/src/core/state/phase-state-machine.js +217 -217
- package/src/lib/generators/design-system-generator.js +298 -298
- package/src/lib/generators/index.js +10 -10
- package/src/lib/generators/metadata-extractor.js +387 -387
- package/src/lib/stack-filter.js +65 -65
- package/src/lib/standards/index.js +7 -7
- package/src/lib/standards/standards-context-injector.js +302 -302
- package/src/lib/validators/architecture/architecture-validator.js +387 -387
- package/src/lib/validators/architecture/index.js +7 -7
- package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +316 -316
- package/src/lib/validators/blazor/blazor-state-validator.js +279 -279
- package/src/lib/validators/blazor/blazor-validator.js +369 -369
- package/src/lib/validators/blazor/index.js +9 -9
- package/src/lib/validators/content/content-validator.js +287 -287
- package/src/lib/validators/content/index.js +7 -7
- package/src/lib/validators/contracts/index.js +7 -7
- package/src/lib/validators/css/css-validator.js +352 -352
- package/src/lib/validators/css/index.js +7 -7
- package/src/lib/validators/design-system/design-system-validator.js +231 -231
- package/src/lib/validators/design-system/index.js +7 -7
- package/src/lib/validators/nextjs/index.js +6 -6
- package/src/lib/validators/nextjs/next-component-validator.js +183 -183
- package/src/lib/validators/packages/index.js +7 -7
- package/src/lib/validators/packages/package-validator.js +360 -360
- package/src/lib/validators/shared/index.js +12 -12
- package/src/lib/validators/shared/issue-counter.js +18 -18
- package/src/lib/validators/shared/result-formatter.js +124 -124
- package/src/lib/validators/ui/index.js +7 -7
- package/src/lib/validators/ui/ui-contrast-validator.js +422 -422
- package/src/scripts/global-install.js +58 -58
- package/src/scripts/install-plugin.js +126 -126
- package/src/utils/claude-md-injector.js +90 -90
- package/src/utils/file-copier.js +200 -196
- package/src/utils/hooks-installer.js +556 -494
- package/src/utils/logger.js +32 -32
- package/src/utils/skills-installer.js +178 -178
- package/src/utils/version-checker.js +175 -175
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md
CHANGED
|
@@ -1,254 +1,254 @@
|
|
|
1
|
-
# Code Review Report — User Management Feature
|
|
2
|
-
|
|
3
|
-
> Example of a well-structured Next.js code review output. Filled-in reference — not a template.
|
|
4
|
-
|
|
5
|
-
**Scope:** `src/features/users/` + `src/app/(dashboard)/users/page.tsx`
|
|
6
|
-
**Date:** 2026-02-23
|
|
7
|
-
**Reviewer:** code-review-nextjs skill
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Automated Scan Results
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
$ node scripts/scan-nextjs.mjs src/features/users/
|
|
15
|
-
|
|
16
|
-
CRITICAL (1)
|
|
17
|
-
features/users/components/UserList.tsx:1 — React hooks used (useState) without 'use client'
|
|
18
|
-
|
|
19
|
-
HIGH/MEDIUM (2)
|
|
20
|
-
features/users/components/UserList.tsx — PascalCase filename (should be user-list.tsx)
|
|
21
|
-
features/users/components/user-form.tsx — 'use client' directive present but no interactivity detected
|
|
22
|
-
|
|
23
|
-
Summary: 8 files scanned | 3 issues (1 critical, 2 high/medium)
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Findings by Severity
|
|
29
|
-
|
|
30
|
-
| Severity | Count |
|
|
31
|
-
|----------|-------|
|
|
32
|
-
| CRITICAL | 2 |
|
|
33
|
-
| HIGH | 3 |
|
|
34
|
-
| MEDIUM | 2 |
|
|
35
|
-
| LOW | 1 |
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
### [CRITICAL] Missing 'use client' on interactive component
|
|
40
|
-
|
|
41
|
-
**File:** `src/features/users/components/UserList.tsx:1`
|
|
42
|
-
|
|
43
|
-
**Current code:**
|
|
44
|
-
```tsx
|
|
45
|
-
import { useState } from 'react';
|
|
46
|
-
|
|
47
|
-
export function UserList() {
|
|
48
|
-
const [selected, setSelected] = useState<string | null>(null);
|
|
49
|
-
return <div onClick={() => setSelected('test')}>...</div>;
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Suggested fix:**
|
|
54
|
-
```tsx
|
|
55
|
-
'use client';
|
|
56
|
-
|
|
57
|
-
import { useState } from 'react';
|
|
58
|
-
// ... rest unchanged
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Why:** React hooks (`useState`) require `'use client'`. Without it, Next.js treats this as a Server Component and the `useState` call fails at runtime.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
### [CRITICAL] useEffect for data fetching
|
|
66
|
-
|
|
67
|
-
**File:** `src/features/users/components/user-list.tsx:8`
|
|
68
|
-
|
|
69
|
-
**Current code:**
|
|
70
|
-
```tsx
|
|
71
|
-
'use client';
|
|
72
|
-
import { useEffect, useState } from 'react';
|
|
73
|
-
|
|
74
|
-
export function UserList() {
|
|
75
|
-
const [users, setUsers] = useState([]);
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
fetch('/api/users').then(r => r.json()).then(setUsers);
|
|
78
|
-
}, []);
|
|
79
|
-
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Suggested fix:**
|
|
84
|
-
```tsx
|
|
85
|
-
// Option A — Server Component (no 'use client' needed)
|
|
86
|
-
// app/(dashboard)/users/page.tsx
|
|
87
|
-
async function getUsers() {
|
|
88
|
-
const res = await fetch(`${process.env.API_URL}/api/users`, { next: { revalidate: 30 } });
|
|
89
|
-
return res.json();
|
|
90
|
-
}
|
|
91
|
-
export default async function UsersPage() {
|
|
92
|
-
const users = await getUsers();
|
|
93
|
-
return <UserList initialData={users} />;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Option B — TanStack Query (when client interactivity needed)
|
|
97
|
-
'use client';
|
|
98
|
-
import { useUsers } from '@/features/users/hooks/use-users';
|
|
99
|
-
export function UserList() {
|
|
100
|
-
const { data: users = [], isLoading } = useUsers();
|
|
101
|
-
if (isLoading) return <div>Loading...</div>;
|
|
102
|
-
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
**Why:** `useEffect` + `useState` for fetching: (1) doubles renders, (2) no caching, (3) no SSR, (4) no error/loading states, (5) can't be cancelled. TanStack Query or Server Components solve all five.
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
### [HIGH] PascalCase file name
|
|
111
|
-
|
|
112
|
-
**File:** `src/features/users/components/UserList.tsx`
|
|
113
|
-
|
|
114
|
-
**Suggested fix:** Rename to `user-list.tsx`
|
|
115
|
-
|
|
116
|
-
**Why:** Linux servers are case-sensitive. `UserList.tsx` and `user-list.tsx` are different files on the server but the same on macOS/Windows, causing import failures in production.
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
### [HIGH] Default export on non-page component
|
|
121
|
-
|
|
122
|
-
**File:** `src/features/users/components/user-card.tsx:3`
|
|
123
|
-
|
|
124
|
-
**Current code:**
|
|
125
|
-
```tsx
|
|
126
|
-
export default function UserCard({ user }: { user: User }) {
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**Suggested fix:**
|
|
130
|
-
```tsx
|
|
131
|
-
export function UserCard({ user }: { user: User }) {
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**Why:** Named exports are refactor-safe — IDEs track renames automatically. Default exports are anonymous in imports (`import X from './user-card'` allows `X` to be anything).
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
### [HIGH] Type defined manually instead of deriving from Zod schema
|
|
139
|
-
|
|
140
|
-
**File:** `src/features/users/types/user.types.ts:1`
|
|
141
|
-
|
|
142
|
-
**Current code:**
|
|
143
|
-
```ts
|
|
144
|
-
export type User = {
|
|
145
|
-
id: string;
|
|
146
|
-
name: string;
|
|
147
|
-
email: string;
|
|
148
|
-
role: 'admin' | 'user';
|
|
149
|
-
};
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Suggested fix:**
|
|
153
|
-
```ts
|
|
154
|
-
// user.schemas.ts — single source of truth
|
|
155
|
-
export const userSchema = z.object({
|
|
156
|
-
id: z.string(),
|
|
157
|
-
name: z.string(),
|
|
158
|
-
email: z.string().email(),
|
|
159
|
-
role: z.enum(['admin', 'user']),
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// user.types.ts — derived, never written manually
|
|
163
|
-
export type User = z.infer<typeof userSchema>;
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Why:** Manually written types drift from API responses. `z.infer<>` ensures the TypeScript type is always in sync with the runtime validation.
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
### [MEDIUM] Deep path import instead of feature index
|
|
171
|
-
|
|
172
|
-
**File:** `src/app/(dashboard)/users/page.tsx:2`
|
|
173
|
-
|
|
174
|
-
**Current code:**
|
|
175
|
-
```tsx
|
|
176
|
-
import { UserList } from '@/features/users/components/user-list';
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
**Suggested fix:**
|
|
180
|
-
```tsx
|
|
181
|
-
import { UserList } from '@/features/users';
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
**Why:** Deep path imports expose internal structure. When `user-list.tsx` moves or is renamed, every consumer breaks. The feature index is the stable public API.
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
### [MEDIUM] Query key not using factory pattern
|
|
189
|
-
|
|
190
|
-
**File:** `src/features/users/hooks/use-users.ts:8`
|
|
191
|
-
|
|
192
|
-
**Current code:**
|
|
193
|
-
```ts
|
|
194
|
-
return useQuery({
|
|
195
|
-
queryKey: ['users'],
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**Suggested fix:**
|
|
199
|
-
```ts
|
|
200
|
-
// query-keys.ts
|
|
201
|
-
export const userKeys = {
|
|
202
|
-
all: ['users'] as const,
|
|
203
|
-
lists: () => [...userKeys.all, 'list'] as const,
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// use-users.ts
|
|
207
|
-
return useQuery({
|
|
208
|
-
queryKey: userKeys.lists(),
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**Why:** Hardcoded `['users']` cannot be selectively invalidated. `queryClient.invalidateQueries({ queryKey: userKeys.lists() })` correctly invalidates only list queries, not detail queries with the same prefix.
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
### [LOW] Test uses fireEvent instead of userEvent
|
|
216
|
-
|
|
217
|
-
**File:** `src/features/users/components/user-card.test.tsx:14`
|
|
218
|
-
|
|
219
|
-
**Current code:**
|
|
220
|
-
```tsx
|
|
221
|
-
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
**Suggested fix:**
|
|
225
|
-
```tsx
|
|
226
|
-
await userEvent.click(screen.getByRole('button', { name: /edit/i }));
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
**Why:** `fireEvent.click` dispatches a synthetic event. `userEvent.click` simulates the full browser interaction sequence (pointerdown, mousedown, focus, click, etc.) — closer to real user behaviour.
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## Top Priorities
|
|
234
|
-
|
|
235
|
-
1. `UserList.tsx:1` — CRITICAL missing `'use client'` (2 min fix)
|
|
236
|
-
2. `user-list.tsx:8` — CRITICAL useEffect fetch → useQuery (15 min)
|
|
237
|
-
3. `UserList.tsx` — HIGH rename to `user-list.tsx` (2 min)
|
|
238
|
-
4. `user-card.tsx:3` — HIGH named export (2 min)
|
|
239
|
-
5. `user.types.ts:1` — HIGH derive from Zod (10 min)
|
|
240
|
-
|
|
241
|
-
---
|
|
242
|
-
|
|
243
|
-
## Passed Checks ✅
|
|
244
|
-
|
|
245
|
-
- `components/ui/` files untouched
|
|
246
|
-
- `zodResolver` correctly wired in `user-form.tsx`
|
|
247
|
-
- `isPending` used (v5 correct — not `isLoading`)
|
|
248
|
-
- `loading.tsx` and `error.tsx` present in `app/(dashboard)/users/`
|
|
249
|
-
- Feature has `index.ts` re-exporting public API
|
|
250
|
-
- Test file co-located with component
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
*MORPH-SPEC by Polymorphism Tech*
|
|
1
|
+
# Code Review Report — User Management Feature
|
|
2
|
+
|
|
3
|
+
> Example of a well-structured Next.js code review output. Filled-in reference — not a template.
|
|
4
|
+
|
|
5
|
+
**Scope:** `src/features/users/` + `src/app/(dashboard)/users/page.tsx`
|
|
6
|
+
**Date:** 2026-02-23
|
|
7
|
+
**Reviewer:** code-review-nextjs skill
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Automated Scan Results
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
$ node scripts/scan-nextjs.mjs src/features/users/
|
|
15
|
+
|
|
16
|
+
CRITICAL (1)
|
|
17
|
+
features/users/components/UserList.tsx:1 — React hooks used (useState) without 'use client'
|
|
18
|
+
|
|
19
|
+
HIGH/MEDIUM (2)
|
|
20
|
+
features/users/components/UserList.tsx — PascalCase filename (should be user-list.tsx)
|
|
21
|
+
features/users/components/user-form.tsx — 'use client' directive present but no interactivity detected
|
|
22
|
+
|
|
23
|
+
Summary: 8 files scanned | 3 issues (1 critical, 2 high/medium)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Findings by Severity
|
|
29
|
+
|
|
30
|
+
| Severity | Count |
|
|
31
|
+
|----------|-------|
|
|
32
|
+
| CRITICAL | 2 |
|
|
33
|
+
| HIGH | 3 |
|
|
34
|
+
| MEDIUM | 2 |
|
|
35
|
+
| LOW | 1 |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### [CRITICAL] Missing 'use client' on interactive component
|
|
40
|
+
|
|
41
|
+
**File:** `src/features/users/components/UserList.tsx:1`
|
|
42
|
+
|
|
43
|
+
**Current code:**
|
|
44
|
+
```tsx
|
|
45
|
+
import { useState } from 'react';
|
|
46
|
+
|
|
47
|
+
export function UserList() {
|
|
48
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
49
|
+
return <div onClick={() => setSelected('test')}>...</div>;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Suggested fix:**
|
|
54
|
+
```tsx
|
|
55
|
+
'use client';
|
|
56
|
+
|
|
57
|
+
import { useState } from 'react';
|
|
58
|
+
// ... rest unchanged
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Why:** React hooks (`useState`) require `'use client'`. Without it, Next.js treats this as a Server Component and the `useState` call fails at runtime.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### [CRITICAL] useEffect for data fetching
|
|
66
|
+
|
|
67
|
+
**File:** `src/features/users/components/user-list.tsx:8`
|
|
68
|
+
|
|
69
|
+
**Current code:**
|
|
70
|
+
```tsx
|
|
71
|
+
'use client';
|
|
72
|
+
import { useEffect, useState } from 'react';
|
|
73
|
+
|
|
74
|
+
export function UserList() {
|
|
75
|
+
const [users, setUsers] = useState([]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
fetch('/api/users').then(r => r.json()).then(setUsers);
|
|
78
|
+
}, []);
|
|
79
|
+
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Suggested fix:**
|
|
84
|
+
```tsx
|
|
85
|
+
// Option A — Server Component (no 'use client' needed)
|
|
86
|
+
// app/(dashboard)/users/page.tsx
|
|
87
|
+
async function getUsers() {
|
|
88
|
+
const res = await fetch(`${process.env.API_URL}/api/users`, { next: { revalidate: 30 } });
|
|
89
|
+
return res.json();
|
|
90
|
+
}
|
|
91
|
+
export default async function UsersPage() {
|
|
92
|
+
const users = await getUsers();
|
|
93
|
+
return <UserList initialData={users} />;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Option B — TanStack Query (when client interactivity needed)
|
|
97
|
+
'use client';
|
|
98
|
+
import { useUsers } from '@/features/users/hooks/use-users';
|
|
99
|
+
export function UserList() {
|
|
100
|
+
const { data: users = [], isLoading } = useUsers();
|
|
101
|
+
if (isLoading) return <div>Loading...</div>;
|
|
102
|
+
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Why:** `useEffect` + `useState` for fetching: (1) doubles renders, (2) no caching, (3) no SSR, (4) no error/loading states, (5) can't be cancelled. TanStack Query or Server Components solve all five.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### [HIGH] PascalCase file name
|
|
111
|
+
|
|
112
|
+
**File:** `src/features/users/components/UserList.tsx`
|
|
113
|
+
|
|
114
|
+
**Suggested fix:** Rename to `user-list.tsx`
|
|
115
|
+
|
|
116
|
+
**Why:** Linux servers are case-sensitive. `UserList.tsx` and `user-list.tsx` are different files on the server but the same on macOS/Windows, causing import failures in production.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### [HIGH] Default export on non-page component
|
|
121
|
+
|
|
122
|
+
**File:** `src/features/users/components/user-card.tsx:3`
|
|
123
|
+
|
|
124
|
+
**Current code:**
|
|
125
|
+
```tsx
|
|
126
|
+
export default function UserCard({ user }: { user: User }) {
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Suggested fix:**
|
|
130
|
+
```tsx
|
|
131
|
+
export function UserCard({ user }: { user: User }) {
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Why:** Named exports are refactor-safe — IDEs track renames automatically. Default exports are anonymous in imports (`import X from './user-card'` allows `X` to be anything).
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### [HIGH] Type defined manually instead of deriving from Zod schema
|
|
139
|
+
|
|
140
|
+
**File:** `src/features/users/types/user.types.ts:1`
|
|
141
|
+
|
|
142
|
+
**Current code:**
|
|
143
|
+
```ts
|
|
144
|
+
export type User = {
|
|
145
|
+
id: string;
|
|
146
|
+
name: string;
|
|
147
|
+
email: string;
|
|
148
|
+
role: 'admin' | 'user';
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Suggested fix:**
|
|
153
|
+
```ts
|
|
154
|
+
// user.schemas.ts — single source of truth
|
|
155
|
+
export const userSchema = z.object({
|
|
156
|
+
id: z.string(),
|
|
157
|
+
name: z.string(),
|
|
158
|
+
email: z.string().email(),
|
|
159
|
+
role: z.enum(['admin', 'user']),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// user.types.ts — derived, never written manually
|
|
163
|
+
export type User = z.infer<typeof userSchema>;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Why:** Manually written types drift from API responses. `z.infer<>` ensures the TypeScript type is always in sync with the runtime validation.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### [MEDIUM] Deep path import instead of feature index
|
|
171
|
+
|
|
172
|
+
**File:** `src/app/(dashboard)/users/page.tsx:2`
|
|
173
|
+
|
|
174
|
+
**Current code:**
|
|
175
|
+
```tsx
|
|
176
|
+
import { UserList } from '@/features/users/components/user-list';
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Suggested fix:**
|
|
180
|
+
```tsx
|
|
181
|
+
import { UserList } from '@/features/users';
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Why:** Deep path imports expose internal structure. When `user-list.tsx` moves or is renamed, every consumer breaks. The feature index is the stable public API.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### [MEDIUM] Query key not using factory pattern
|
|
189
|
+
|
|
190
|
+
**File:** `src/features/users/hooks/use-users.ts:8`
|
|
191
|
+
|
|
192
|
+
**Current code:**
|
|
193
|
+
```ts
|
|
194
|
+
return useQuery({
|
|
195
|
+
queryKey: ['users'],
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Suggested fix:**
|
|
199
|
+
```ts
|
|
200
|
+
// query-keys.ts
|
|
201
|
+
export const userKeys = {
|
|
202
|
+
all: ['users'] as const,
|
|
203
|
+
lists: () => [...userKeys.all, 'list'] as const,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// use-users.ts
|
|
207
|
+
return useQuery({
|
|
208
|
+
queryKey: userKeys.lists(),
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Why:** Hardcoded `['users']` cannot be selectively invalidated. `queryClient.invalidateQueries({ queryKey: userKeys.lists() })` correctly invalidates only list queries, not detail queries with the same prefix.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### [LOW] Test uses fireEvent instead of userEvent
|
|
216
|
+
|
|
217
|
+
**File:** `src/features/users/components/user-card.test.tsx:14`
|
|
218
|
+
|
|
219
|
+
**Current code:**
|
|
220
|
+
```tsx
|
|
221
|
+
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Suggested fix:**
|
|
225
|
+
```tsx
|
|
226
|
+
await userEvent.click(screen.getByRole('button', { name: /edit/i }));
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Why:** `fireEvent.click` dispatches a synthetic event. `userEvent.click` simulates the full browser interaction sequence (pointerdown, mousedown, focus, click, etc.) — closer to real user behaviour.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Top Priorities
|
|
234
|
+
|
|
235
|
+
1. `UserList.tsx:1` — CRITICAL missing `'use client'` (2 min fix)
|
|
236
|
+
2. `user-list.tsx:8` — CRITICAL useEffect fetch → useQuery (15 min)
|
|
237
|
+
3. `UserList.tsx` — HIGH rename to `user-list.tsx` (2 min)
|
|
238
|
+
4. `user-card.tsx:3` — HIGH named export (2 min)
|
|
239
|
+
5. `user.types.ts:1` — HIGH derive from Zod (10 min)
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Passed Checks ✅
|
|
244
|
+
|
|
245
|
+
- `components/ui/` files untouched
|
|
246
|
+
- `zodResolver` correctly wired in `user-form.tsx`
|
|
247
|
+
- `isPending` used (v5 correct — not `isLoading`)
|
|
248
|
+
- `loading.tsx` and `error.tsx` present in `app/(dashboard)/users/`
|
|
249
|
+
- Feature has `index.ts` re-exporting public API
|
|
250
|
+
- Test file co-located with component
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -42,17 +42,19 @@ Use the resolved CLI prefix (e.g. `npx morph-spec`) for ALL bash commands in the
|
|
|
42
42
|
|
|
43
43
|
## Step 1 — Required Plugins
|
|
44
44
|
|
|
45
|
-
Check for required Claude Code plugins: **superpowers** and **
|
|
45
|
+
Check for required Claude Code plugins: **superpowers**, **context7**, and **playwright**.
|
|
46
46
|
|
|
47
47
|
Read `~/.claude/plugins/installed_plugins.json` and check for:
|
|
48
48
|
- `superpowers@claude-plugins-official`
|
|
49
49
|
- `context7@claude-plugins-official`
|
|
50
|
+
- `playwright@claude-plugins-official`
|
|
50
51
|
|
|
51
52
|
For each missing plugin, run:
|
|
52
53
|
|
|
53
54
|
```bash
|
|
54
55
|
claude plugin install superpowers@claude-plugins-official
|
|
55
56
|
claude plugin install context7@claude-plugins-official
|
|
57
|
+
claude plugin install playwright@claude-plugins-official
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
**If the command succeeds:** `✓ {plugin} installed. Restart Claude Code after /morph-init completes.`
|
|
@@ -66,7 +68,7 @@ Run manually and restart Claude Code:
|
|
|
66
68
|
Then re-run /morph-init.
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
All three plugins are required. Do not continue if any is missing.
|
|
70
72
|
|
|
71
73
|
---
|
|
72
74
|
|
|
@@ -151,6 +153,89 @@ Do **not** ask about technology already confirmed by file evidence.
|
|
|
151
153
|
|
|
152
154
|
---
|
|
153
155
|
|
|
156
|
+
## Step 4.5 — Scaffold Design System
|
|
157
|
+
|
|
158
|
+
Check if the project already has a design system file:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
Read: .morph/context/design-system.md
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**If the file EXISTS with non-TODO content in the Aesthetic Direction section:**
|
|
165
|
+
→ Skip this step. Print: `✓ Design system found — .morph/context/design-system.md`
|
|
166
|
+
|
|
167
|
+
**If the file does NOT EXIST or is empty:**
|
|
168
|
+
|
|
169
|
+
1. **Copy the template:**
|
|
170
|
+
```bash
|
|
171
|
+
cp .morph/framework/templates/context/design-system.md .morph/context/design-system.md
|
|
172
|
+
```
|
|
173
|
+
*(The template is at `framework/templates/context/design-system.md` in the installed framework)*
|
|
174
|
+
|
|
175
|
+
2. **Ask 3 quick questions** using `AskUserQuestion`:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"questions": [
|
|
180
|
+
{
|
|
181
|
+
"header": "Design System Setup",
|
|
182
|
+
"question": "Let's set up your project's design system. What is the aesthetic direction for [ProjectName]?",
|
|
183
|
+
"multiSelect": false,
|
|
184
|
+
"options": [
|
|
185
|
+
{ "label": "Minimal Refined", "description": "Luxurious and precise — elegant serif + light sans, monochromatic + 1 sharp accent" },
|
|
186
|
+
{ "label": "Editorial", "description": "Magazine-grade, opinionated — strong display font + high contrast, dramatic reveals" },
|
|
187
|
+
{ "label": "Soft Professional", "description": "Warm and trustworthy — rounded sans + warm pastels, gentle micro-interactions" },
|
|
188
|
+
{ "label": "Industrial", "description": "Technical and direct — monospace/condensed + neutral colors, minimal motion" },
|
|
189
|
+
{ "label": "Modern Luxury", "description": "Exclusive and refined — italic display + dark background + gold/copper accent" },
|
|
190
|
+
{ "label": "Skip for now", "description": "Leave as TODO — fill in manually later" }
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
3. **If user chose a direction (not "Skip for now")**, ask a follow-up:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"questions": [
|
|
202
|
+
{
|
|
203
|
+
"header": "Brand Color",
|
|
204
|
+
"question": "What is your approximate dominant/primary color? (You can adjust exact values later)",
|
|
205
|
+
"multiSelect": false,
|
|
206
|
+
"options": [
|
|
207
|
+
{ "label": "Deep blue/navy", "description": "Professional, trustworthy" },
|
|
208
|
+
{ "label": "Rich green", "description": "Growth, sustainability" },
|
|
209
|
+
{ "label": "Warm orange/red", "description": "Energy, urgency" },
|
|
210
|
+
{ "label": "Cool purple/violet", "description": "Creative, premium" },
|
|
211
|
+
{ "label": "True black/dark", "description": "Minimal, premium, editorial" },
|
|
212
|
+
{ "label": "I'll specify manually", "description": "Leave as TODO" }
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
4. **Fill in the design-system.md** with user responses:
|
|
220
|
+
|
|
221
|
+
| User answered | What to update |
|
|
222
|
+
|--------------|----------------|
|
|
223
|
+
| Direction chosen | Replace `[Minimal Refined / Editorial / ...]` with chosen direction. Update Font Pair with the correct fonts from this mapping: Minimal Refined → Cormorant Garamond + DM Sans; Editorial → Playfair Display + Source Sans 3; Soft Professional → Nunito + Inter; Industrial → JetBrains Mono + IBM Plex Sans; Modern Luxury → Bodoni Moda + Sora |
|
|
224
|
+
| Color chosen | Replace `--color-primary: #TODO` with a reasonable hex for that color family (deep blue → #1e3a5f, rich green → #065f46, warm orange → #c2410c, purple → #6d28d9, black → #0a0a0a). Add a note: "Adjust exact values to match your brand" |
|
|
225
|
+
| "Skip for now" | Leave all TODOs in place |
|
|
226
|
+
|
|
227
|
+
5. **Update the Changelog** in design-system.md:
|
|
228
|
+
Add entry: `- [today's date] [morph-init] Initial scaffold from morph-init`
|
|
229
|
+
|
|
230
|
+
6. Print confirmation:
|
|
231
|
+
```
|
|
232
|
+
✓ Design system scaffolded — .morph/context/design-system.md
|
|
233
|
+
Direction: [chosen direction or "TODO — fill in manually"]
|
|
234
|
+
Edit to refine exact token values before the UI/UX phase.
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
154
239
|
## Step 5 — Generate `context/README.md`
|
|
155
240
|
|
|
156
241
|
Write `.morph/context/README.md`:
|
|
@@ -358,7 +443,7 @@ For Node.js CLI projects, update config:
|
|
|
358
443
|
Before printing the summary, check `~/.claude/settings.local.json` for `env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`. If set to `"1"`, mark Agent Teams as enabled; otherwise show the warning.
|
|
359
444
|
|
|
360
445
|
```
|
|
361
|
-
✓ Plugins: superpowers ✓ context7 ✓
|
|
446
|
+
✓ Plugins: superpowers ✓ context7 ✓ playwright ✓
|
|
362
447
|
✓ Infrastructure: .morph/ structure, hooks, agents, rules, skills
|
|
363
448
|
✓ Agents: [N] total ([T1] orchestrators, [T2] domain leaders, [S] specialists)
|
|
364
449
|
✓ context/README.md generated
|