@yuaone/core 0.3.2 → 0.4.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.
Files changed (129) hide show
  1. package/dist/agent-loop.d.ts +62 -0
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +705 -18
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/background-agent.d.ts +110 -0
  6. package/dist/background-agent.d.ts.map +1 -0
  7. package/dist/background-agent.js +255 -0
  8. package/dist/background-agent.js.map +1 -0
  9. package/dist/coding-standards.d.ts +45 -0
  10. package/dist/coding-standards.d.ts.map +1 -0
  11. package/dist/coding-standards.js +1152 -0
  12. package/dist/coding-standards.js.map +1 -0
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +2 -6
  15. package/dist/constants.js.map +1 -1
  16. package/dist/context-manager.d.ts +6 -0
  17. package/dist/context-manager.d.ts.map +1 -1
  18. package/dist/context-manager.js +23 -4
  19. package/dist/context-manager.js.map +1 -1
  20. package/dist/index.d.ts +28 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +26 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/llm-client.d.ts +8 -3
  25. package/dist/llm-client.d.ts.map +1 -1
  26. package/dist/llm-client.js +64 -13
  27. package/dist/llm-client.js.map +1 -1
  28. package/dist/plugin-auto-loader.d.ts +108 -0
  29. package/dist/plugin-auto-loader.d.ts.map +1 -0
  30. package/dist/plugin-auto-loader.js +743 -0
  31. package/dist/plugin-auto-loader.js.map +1 -0
  32. package/dist/plugin-registry.d.ts +112 -0
  33. package/dist/plugin-registry.d.ts.map +1 -0
  34. package/dist/plugin-registry.js +319 -0
  35. package/dist/plugin-registry.js.map +1 -0
  36. package/dist/plugin-types.d.ts +388 -0
  37. package/dist/plugin-types.d.ts.map +1 -0
  38. package/dist/plugin-types.js +8 -0
  39. package/dist/plugin-types.js.map +1 -0
  40. package/dist/plugin-validator.d.ts +54 -0
  41. package/dist/plugin-validator.d.ts.map +1 -0
  42. package/dist/plugin-validator.js +129 -0
  43. package/dist/plugin-validator.js.map +1 -0
  44. package/dist/repo-knowledge-graph.d.ts +112 -0
  45. package/dist/repo-knowledge-graph.d.ts.map +1 -0
  46. package/dist/repo-knowledge-graph.js +561 -0
  47. package/dist/repo-knowledge-graph.js.map +1 -0
  48. package/dist/role-registry.js +1 -1
  49. package/dist/role-registry.js.map +1 -1
  50. package/dist/self-debug-loop.d.ts +257 -0
  51. package/dist/self-debug-loop.d.ts.map +1 -0
  52. package/dist/self-debug-loop.js +870 -0
  53. package/dist/self-debug-loop.js.map +1 -0
  54. package/dist/skill-learner.d.ts +136 -0
  55. package/dist/skill-learner.d.ts.map +1 -0
  56. package/dist/skill-learner.js +382 -0
  57. package/dist/skill-learner.js.map +1 -0
  58. package/dist/skill-loader.d.ts +90 -0
  59. package/dist/skill-loader.d.ts.map +1 -0
  60. package/dist/skill-loader.js +309 -0
  61. package/dist/skill-loader.js.map +1 -0
  62. package/dist/specialist-registry.d.ts +132 -0
  63. package/dist/specialist-registry.d.ts.map +1 -0
  64. package/dist/specialist-registry.js +413 -0
  65. package/dist/specialist-registry.js.map +1 -0
  66. package/dist/sub-agent-prompts.d.ts +45 -0
  67. package/dist/sub-agent-prompts.d.ts.map +1 -0
  68. package/dist/sub-agent-prompts.js +177 -0
  69. package/dist/sub-agent-prompts.js.map +1 -0
  70. package/dist/sub-agent-router.d.ts +75 -0
  71. package/dist/sub-agent-router.d.ts.map +1 -0
  72. package/dist/sub-agent-router.js +174 -0
  73. package/dist/sub-agent-router.js.map +1 -0
  74. package/dist/sub-agent.d.ts +48 -0
  75. package/dist/sub-agent.d.ts.map +1 -1
  76. package/dist/sub-agent.js +108 -5
  77. package/dist/sub-agent.js.map +1 -1
  78. package/dist/system-prompt.d.ts +26 -0
  79. package/dist/system-prompt.d.ts.map +1 -1
  80. package/dist/system-prompt.js +177 -7
  81. package/dist/system-prompt.js.map +1 -1
  82. package/dist/task-classifier.d.ts +25 -1
  83. package/dist/task-classifier.d.ts.map +1 -1
  84. package/dist/task-classifier.js +171 -1
  85. package/dist/task-classifier.js.map +1 -1
  86. package/dist/tool-planner.d.ts +160 -0
  87. package/dist/tool-planner.d.ts.map +1 -0
  88. package/dist/tool-planner.js +501 -0
  89. package/dist/tool-planner.js.map +1 -0
  90. package/dist/types.d.ts +1 -1
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/world-state.d.ts.map +1 -1
  93. package/dist/world-state.js +8 -1
  94. package/dist/world-state.js.map +1 -1
  95. package/package.json +2 -1
  96. package/plugins/git/patterns/branch-patterns.json +101 -0
  97. package/plugins/git/patterns/commit-patterns.json +186 -0
  98. package/plugins/git/plugin.yaml +128 -0
  99. package/plugins/git/skills/branch-strategy.md +172 -0
  100. package/plugins/git/skills/commit-conv.md +178 -0
  101. package/plugins/git/skills/conflict-resolve.md +159 -0
  102. package/plugins/git/skills/history-clean.md +199 -0
  103. package/plugins/git/skills/pr-review.md +196 -0
  104. package/plugins/git/strategies/conflict-resolve.json +244 -0
  105. package/plugins/git/strategies/release-flow.json +292 -0
  106. package/plugins/git/validators/rules.json +348 -0
  107. package/plugins/react/patterns/anti-patterns.json +88 -0
  108. package/plugins/react/patterns/components.json +80 -0
  109. package/plugins/react/patterns/hooks.json +72 -0
  110. package/plugins/react/plugin.yaml +229 -0
  111. package/plugins/react/skills/bugfix.md +208 -0
  112. package/plugins/react/skills/component-gen.md +206 -0
  113. package/plugins/react/skills/hook-extract.md +208 -0
  114. package/plugins/react/skills/ssr.md +256 -0
  115. package/plugins/react/skills/test.md +273 -0
  116. package/plugins/react/strategies/build-fix.json +43 -0
  117. package/plugins/react/strategies/hook-loop-fix.json +36 -0
  118. package/plugins/react/strategies/hydration-fix.json +42 -0
  119. package/plugins/react/validators/rules.json +92 -0
  120. package/plugins/typescript/patterns/best-practices.json +25 -0
  121. package/plugins/typescript/patterns/common-errors.json +32 -0
  122. package/plugins/typescript/plugin.yaml +74 -0
  123. package/plugins/typescript/skills/debug.md +23 -0
  124. package/plugins/typescript/skills/migration.md +24 -0
  125. package/plugins/typescript/skills/refactor.md +22 -0
  126. package/plugins/typescript/skills/strict-mode.md +23 -0
  127. package/plugins/typescript/strategies/strict-migration.json +37 -0
  128. package/plugins/typescript/strategies/type-error-fix.json +37 -0
  129. package/plugins/typescript/validators/rules.json +28 -0
@@ -0,0 +1,208 @@
1
+ # React Bugfix Skill
2
+
3
+ ## Identity
4
+ - domain: react
5
+ - type: bugfix
6
+ - confidence: 0.85
7
+ - persona: Senior React engineer with 8+ years of production debugging experience. Expert in React internals, reconciliation algorithm, fiber architecture, and common failure modes across CSR/SSR environments.
8
+
9
+ ## Known Error Patterns
10
+
11
+ ### 1. Hydration Mismatch
12
+ - **symptoms**:
13
+ - "Text content does not match server-rendered HTML"
14
+ - "Hydration failed because the initial UI does not match what was rendered on the server"
15
+ - "There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering"
16
+ - Visual flicker on page load
17
+ - Content changes immediately after hydration
18
+ - **causes**:
19
+ - Direct access to `window`, `document`, `localStorage` during render
20
+ - `Date.now()`, `Math.random()`, or locale-dependent formatting in render path
21
+ - Conditional rendering based on `typeof window !== 'undefined'`
22
+ - Browser extensions injecting DOM nodes
23
+ - Different data between server and client (stale cache, time zones)
24
+ - **strategy**:
25
+ 1. Move client-only code into `useEffect` so it runs only after hydration
26
+ 2. Use `next/dynamic` with `{ ssr: false }` for client-only components
27
+ 3. Use `suppressHydrationWarning` as a last resort (only for intentional mismatches like timestamps)
28
+ 4. Create a `useIsClient()` hook: `const [isClient, setIsClient] = useState(false); useEffect(() => setIsClient(true), []); return isClient;`
29
+ 5. For third-party components that access `window`, wrap in dynamic import
30
+ - **tools**: grep, file_read, file_edit, shell_exec
31
+ - **pitfalls**:
32
+ - Do NOT wrap everything in `useEffect` blindly -- this defeats SSR benefits
33
+ - `suppressHydrationWarning` only suppresses warnings, does not fix the actual mismatch
34
+ - Browser extensions (e.g., Grammarly, translation plugins) can cause hydration errors that are not your fault -- check if the issue reproduces in incognito mode
35
+
36
+ ### 2. useEffect Infinite Loop
37
+ - **symptoms**:
38
+ - "Maximum update depth exceeded"
39
+ - "Too many re-renders. React limits the number of renders to prevent an infinite loop"
40
+ - Browser tab freezing or crashing
41
+ - CPU spike in devtools
42
+ - Rapid state updates visible in React DevTools profiler
43
+ - **causes**:
44
+ - Missing dependency array: `useEffect(() => { setState(val) })` (runs every render)
45
+ - Object/array in dependency array: `useEffect(() => {}, [{ a: 1 }])` (new reference every render)
46
+ - Setting state that is also a dependency: `useEffect(() => { setCount(count + 1) }, [count])`
47
+ - Stale closure capturing outdated values, causing repeated updates
48
+ - Calling a function that creates a new reference on every render as a dependency
49
+ - **strategy**:
50
+ 1. Check if dependency array exists -- add `[]` for mount-only effects
51
+ 2. Identify object/array deps -- stabilize with `useMemo` or `useCallback`
52
+ 3. Use functional updater: `setCount(prev => prev + 1)` instead of `setCount(count + 1)`
53
+ 4. Extract primitive values: `const { id } = obj; useEffect(() => {}, [id])` instead of `[obj]`
54
+ 5. Use `useRef` for values that should not trigger re-renders
55
+ 6. Consider `useReducer` for complex state logic to avoid multiple interdependent effects
56
+ - **tools**: file_read, grep, file_edit
57
+ - **pitfalls**:
58
+ - Do NOT disable `eslint-plugin-react-hooks` exhaustive-deps rule
59
+ - Do NOT use `// eslint-disable-next-line react-hooks/exhaustive-deps` without understanding why
60
+ - An empty `[]` dependency array is only correct for true mount-only effects
61
+
62
+ ### 3. State Not Updating
63
+ - **symptoms**:
64
+ - UI does not reflect state change after `setState`
65
+ - `console.log` after `setState` shows old value
66
+ - State appears to "reset" or "revert"
67
+ - Conditional logic based on state executes wrong branch
68
+ - Multiple rapid updates only reflect the last one
69
+ - **causes**:
70
+ - Direct mutation: `state.items.push(newItem); setState(state)` (same reference, no re-render)
71
+ - Reading state immediately after `setState` (batched, asynchronous)
72
+ - Derived state anti-pattern: duplicating prop into state without sync
73
+ - Stale closure in event handler or timer callback
74
+ - React 18 automatic batching grouping updates unexpectedly
75
+ - **strategy**:
76
+ 1. Always create new references: `setState(prev => [...prev, newItem])`
77
+ 2. For objects: `setState(prev => ({ ...prev, [key]: value }))`
78
+ 3. Use functional updater for sequential updates: `setCount(prev => prev + 1)`
79
+ 4. For derived state, compute during render instead of syncing with `useEffect`
80
+ 5. Use `flushSync` from `react-dom` only if you absolutely need synchronous updates (rare)
81
+ 6. Consider Immer (`useImmer`) for deeply nested state mutations
82
+ - **tools**: file_read, file_edit
83
+ - **pitfalls**:
84
+ - Do NOT use `JSON.parse(JSON.stringify(state))` for deep cloning -- use structured clone or Immer
85
+ - Do NOT read state right after setting it and expect the new value
86
+ - Avoid `useEffect` to "sync" props to state -- this is almost always wrong
87
+
88
+ ### 4. Key Prop Issues
89
+ - **symptoms**:
90
+ - "Each child in a list should have a unique 'key' prop"
91
+ - List items losing input focus on re-render
92
+ - Animations not working correctly in lists
93
+ - Wrong items being updated or deleted in lists
94
+ - Component state being shared between different list items
95
+ - **causes**:
96
+ - Using array index as key: `items.map((item, i) => <Item key={i} />)`
97
+ - Missing key prop entirely
98
+ - Non-unique keys (duplicate IDs in data)
99
+ - Key changing on every render (e.g., `key={Math.random()}`)
100
+ - Key not reflecting item identity (using wrong field)
101
+ - **strategy**:
102
+ 1. Use a stable, unique identifier from data: `key={item.id}`
103
+ 2. If no ID exists, generate one when creating the item (not during render)
104
+ 3. For static lists that never reorder, index is acceptable
105
+ 4. Use `crypto.randomUUID()` at item creation time, not render time
106
+ 5. For compound keys: `key={\`${item.type}-${item.id}\`}`
107
+ - **tools**: grep, file_read, file_edit
108
+ - **pitfalls**:
109
+ - Index keys cause bugs when list is sorted, filtered, or items are inserted/removed
110
+ - `key={Math.random()}` forces remount on every render -- massive performance issue
111
+ - Keys must be stable across re-renders -- do NOT generate IDs in the render function
112
+
113
+ ### 5. Memory Leaks
114
+ - **symptoms**:
115
+ - "Can't perform a React state update on an unmounted component"
116
+ - Increasing memory usage over time (check Performance tab)
117
+ - Stale data appearing after navigation
118
+ - Event listeners firing for unmounted components
119
+ - WebSocket connections staying open after unmount
120
+ - **causes**:
121
+ - Missing cleanup function in `useEffect`
122
+ - Not aborting fetch requests on unmount
123
+ - Not removing event listeners (`window.addEventListener` without cleanup)
124
+ - Not clearing timers (`setInterval`, `setTimeout`)
125
+ - Subscriptions (WebSocket, EventSource) not closed on unmount
126
+ - **strategy**:
127
+ 1. Always return cleanup from `useEffect`:
128
+ ```
129
+ useEffect(() => {
130
+ const controller = new AbortController();
131
+ fetch(url, { signal: controller.signal });
132
+ return () => controller.abort();
133
+ }, [url]);
134
+ ```
135
+ 2. Use `AbortController` for all fetch calls
136
+ 3. Clear all timers: `return () => clearInterval(id);`
137
+ 4. Remove event listeners: `return () => window.removeEventListener('resize', handler);`
138
+ 5. Close WebSocket/EventSource connections in cleanup
139
+ 6. For React 18 Strict Mode double-mount, ensure cleanup is idempotent
140
+ - **tools**: grep, file_read, file_edit
141
+ - **pitfalls**:
142
+ - React 18 Strict Mode in dev intentionally double-mounts -- this exposes leaks, do not suppress it
143
+ - The "unmounted component" warning was removed in React 18 but the leak still exists
144
+ - `AbortError` from aborted fetch is expected -- catch and ignore it
145
+
146
+ ### 6. Conditional Hook Call
147
+ - **symptoms**:
148
+ - "React Hook is called conditionally. React Hooks must be called in the exact same order"
149
+ - "Rendered more hooks than during the previous render"
150
+ - "Rendered fewer hooks than expected"
151
+ - Cryptic errors after adding/removing hooks in conditionals
152
+ - **causes**:
153
+ - Hook inside `if` statement or ternary
154
+ - Hook after early return
155
+ - Hook inside loop or nested function
156
+ - Dynamic hook calls based on props
157
+ - **strategy**:
158
+ 1. Move all hooks to the top level of the component, before any conditionals
159
+ 2. Use the hook unconditionally, then conditionally use its result
160
+ 3. For conditional effects: `useEffect(() => { if (condition) { /* ... */ } }, [condition])`
161
+ 4. Split into separate components if hook logic is fundamentally conditional
162
+ 5. For dynamic lists of hooks, restructure to use a single hook with array state
163
+ - **tools**: file_read, file_edit
164
+ - **pitfalls**:
165
+ - Hooks rely on call order -- React uses position to match hooks between renders
166
+ - Even if a condition is "always true", the linter cannot verify it -- restructure instead
167
+ - Custom hooks are still hooks -- they follow the same rules
168
+
169
+ ### 7. Stale Closure
170
+ - **symptoms**:
171
+ - Event handler uses outdated state value
172
+ - `setTimeout`/`setInterval` callback sees initial state
173
+ - Click handler in a loop always captures last iteration value
174
+ - Async callback returns stale data after state changed
175
+ - **causes**:
176
+ - Closure captures the state value at the time of creation
177
+ - Event handler created in a previous render still references old state
178
+ - Timer callback captures stale variable
179
+ - **strategy**:
180
+ 1. Use functional updater: `setState(prev => prev + 1)`
181
+ 2. Use `useRef` to always have current value: `const countRef = useRef(count); countRef.current = count;`
182
+ 3. Use `useCallback` with correct deps to recreate handler when deps change
183
+ 4. For intervals, use a custom `useInterval` hook with ref-based callback
184
+ 5. In event listeners, re-attach when deps change via useEffect cleanup
185
+ - **tools**: file_read, file_edit, grep
186
+ - **pitfalls**:
187
+ - Adding state to useEffect deps to fix stale closure can introduce infinite loops -- use refs for read-only access
188
+ - `useRef` does not trigger re-renders -- if you need both current value and re-render, combine ref + state
189
+
190
+ ## Tool Sequence
191
+ 1. **grep** -- Search for error pattern in source files (`**/*.tsx`, `**/*.jsx`)
192
+ 2. **file_read** -- Read the identified file(s) to understand context
193
+ 3. **grep** -- Search for related patterns (hook usage, state definitions, effect dependencies)
194
+ 4. **file_read** -- Read related files (parent components, shared hooks, store)
195
+ 5. **file_edit** -- Apply the fix with minimal changes
196
+ 6. **shell_exec** -- Run `pnpm build` or `next build` to verify no build errors
197
+ 7. **shell_exec** -- Run tests if available (`pnpm test` or `vitest run`)
198
+ 8. **grep** -- Verify the error pattern no longer exists in build output
199
+
200
+ ## Validation Checklist
201
+ - [ ] Error message no longer appears in console/build output
202
+ - [ ] `pnpm build` (or `next build`) passes without errors
203
+ - [ ] Existing tests pass (`pnpm test`)
204
+ - [ ] No new TypeScript errors (`tsc --noEmit`)
205
+ - [ ] No new ESLint warnings from `react-hooks/exhaustive-deps`
206
+ - [ ] Component renders correctly in both SSR and CSR
207
+ - [ ] No performance regression (no unnecessary re-renders introduced)
208
+ - [ ] Fix does not break other components that depend on the modified code
@@ -0,0 +1,206 @@
1
+ # React Component Generator Skill
2
+
3
+ ## Identity
4
+ - domain: react
5
+ - type: generator
6
+ - confidence: 0.90
7
+ - persona: Senior React architect specializing in component design patterns, atomic design methodology, and TypeScript-first component APIs. Expert in building reusable, accessible, and testable component libraries.
8
+
9
+ ## Known Error Patterns
10
+
11
+ ### 1. Missing TypeScript Props Interface
12
+ - **symptoms**:
13
+ - Props typed as `any` or missing entirely
14
+ - No autocomplete for component props in IDE
15
+ - Runtime errors from unexpected prop types
16
+ - `Property does not exist on type` errors
17
+ - **causes**:
18
+ - Component created without explicit props type
19
+ - Using `React.FC` without generic parameter
20
+ - Props spread without type narrowing
21
+ - **strategy**:
22
+ 1. Define explicit interface for all props: `interface ButtonProps { ... }`
23
+ 2. Use discriminated unions for variant props
24
+ 3. Extend native HTML element props: `interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>`
25
+ 4. Export props interface for consumers
26
+ - **tools**: file_read, file_edit
27
+ - **pitfalls**:
28
+ - Avoid `React.FC` -- it implicitly includes `children` in older React types and has issues with generics
29
+ - Do NOT use `PropsWithChildren` unless the component truly accepts arbitrary children
30
+
31
+ ### 2. Prop Drilling
32
+ - **symptoms**:
33
+ - Same prop passed through 3+ component levels
34
+ - Intermediate components accept props they do not use
35
+ - Changing a prop signature requires editing many files
36
+ - `...rest` spread used to forward unknown props
37
+ - **causes**:
38
+ - Flat component hierarchy without composition
39
+ - Missing context or state management for shared data
40
+ - Over-reliance on top-down data flow
41
+ - **strategy**:
42
+ 1. Use compound component pattern with React context
43
+ 2. Use composition (children/render props) to skip levels
44
+ 3. Introduce React Context for widely-shared state
45
+ 4. Use Zustand/Jotai for cross-cutting application state
46
+ - **tools**: file_read, grep, file_edit
47
+ - **pitfalls**:
48
+ - Do NOT create a context for every piece of state -- only for truly cross-cutting concerns
49
+ - Context causes re-renders of all consumers -- split contexts by update frequency
50
+
51
+ ### 3. Missing forwardRef
52
+ - **symptoms**:
53
+ - `ref` prop not working on custom component
54
+ - Warning: "Function components cannot be given refs"
55
+ - Parent cannot access child DOM node for focus management
56
+ - Animation libraries cannot attach to component
57
+ - **causes**:
58
+ - Custom component does not use `forwardRef`
59
+ - ref attached to wrong inner element
60
+ - Generic component loses type information for ref
61
+ - **strategy**:
62
+ 1. Wrap with `React.forwardRef<HTMLElement, Props>((props, ref) => ...)`
63
+ 2. Attach ref to the outermost meaningful DOM element
64
+ 3. Use `useImperativeHandle` to expose a custom API
65
+ 4. For generic components, use the ref-forwarding generic pattern
66
+ - **tools**: file_read, file_edit
67
+ - **pitfalls**:
68
+ - In React 19+, `ref` is a regular prop -- `forwardRef` will be unnecessary
69
+ - `forwardRef` breaks generic inference -- use the function overload pattern for generic components
70
+
71
+ ### 4. Controlled vs Uncontrolled Confusion
72
+ - **symptoms**:
73
+ - "A component is changing an uncontrolled input to be controlled"
74
+ - Input value not updating on change
75
+ - Form reset not working
76
+ - Default value ignored after mount
77
+ - **causes**:
78
+ - Switching between `value` and `defaultValue`
79
+ - Initial `value` is `undefined` (uncontrolled) then becomes defined (controlled)
80
+ - Missing `onChange` handler with `value` prop
81
+ - **strategy**:
82
+ 1. Choose controlled or uncontrolled and be consistent
83
+ 2. For controlled: always pair `value` + `onChange`
84
+ 3. For uncontrolled: use `defaultValue` + `ref`
85
+ 4. Initialize state to empty string, not `undefined`: `useState('')`
86
+ 5. Build components that support both modes with internal state fallback
87
+ - **tools**: file_read, file_edit
88
+ - **pitfalls**:
89
+ - `value={undefined}` makes it uncontrolled -- use `value={state ?? ''}` to stay controlled
90
+ - `defaultValue` is only read on mount -- changes after mount are ignored
91
+
92
+ ### 5. Accessibility Violations
93
+ - **symptoms**:
94
+ - No keyboard navigation
95
+ - Screen reader cannot identify interactive elements
96
+ - Missing ARIA labels
97
+ - Focus trap not working in modals
98
+ - Color contrast failures
99
+ - **causes**:
100
+ - Using `div` with `onClick` instead of `button`
101
+ - Missing `aria-label` on icon buttons
102
+ - Custom components not forwarding ARIA props
103
+ - Missing focus management in dialogs
104
+ - **strategy**:
105
+ 1. Use semantic HTML elements (`button`, `nav`, `main`, `dialog`)
106
+ 2. Add `aria-label` to elements without visible text
107
+ 3. Implement keyboard handlers: `onKeyDown` for Enter/Space/Escape
108
+ 4. Use `role` attribute only when no semantic element exists
109
+ 5. Trap focus in modals with `inert` attribute or focus-trap library
110
+ - **tools**: file_read, file_edit, shell_exec
111
+ - **pitfalls**:
112
+ - Do NOT add `role="button"` to a `div` when you can just use `<button>`
113
+ - `tabIndex={0}` alone is not enough -- also need keyboard event handlers
114
+
115
+ ## Component Patterns
116
+
117
+ ### Atomic Design Hierarchy
118
+ ```
119
+ atoms/ -- Button, Input, Icon, Text, Badge
120
+ molecules/ -- SearchInput, FormField, Card, MenuItem
121
+ organisms/ -- Header, Sidebar, DataTable, Form
122
+ templates/ -- DashboardLayout, AuthLayout
123
+ pages/ -- HomePage, SettingsPage
124
+ ```
125
+
126
+ ### Compound Component Pattern
127
+ ```
128
+ <Select>
129
+ <Select.Trigger />
130
+ <Select.Content>
131
+ <Select.Item value="a">Option A</Select.Item>
132
+ <Select.Item value="b">Option B</Select.Item>
133
+ </Select.Content>
134
+ </Select>
135
+ ```
136
+ Uses React Context internally to share state between parent and children.
137
+
138
+ ### Render Props / Children as Function
139
+ ```
140
+ <DataFetcher url="/api/users">
141
+ {({ data, loading, error }) => (
142
+ loading ? <Spinner /> : <UserList users={data} />
143
+ )}
144
+ </DataFetcher>
145
+ ```
146
+ Useful for separating data logic from presentation.
147
+
148
+ ### Controlled + Uncontrolled Dual Mode
149
+ ```
150
+ function Input({ value: controlledValue, defaultValue, onChange, ...props }) {
151
+ const [internalValue, setInternalValue] = useState(defaultValue ?? '');
152
+ const isControlled = controlledValue !== undefined;
153
+ const value = isControlled ? controlledValue : internalValue;
154
+
155
+ const handleChange = (e) => {
156
+ if (!isControlled) setInternalValue(e.target.value);
157
+ onChange?.(e);
158
+ };
159
+
160
+ return <input value={value} onChange={handleChange} {...props} />;
161
+ }
162
+ ```
163
+
164
+ ### forwardRef + Generic Component
165
+ ```
166
+ interface ListProps<T> {
167
+ items: T[];
168
+ renderItem: (item: T) => ReactNode;
169
+ }
170
+
171
+ function ListInner<T>(props: ListProps<T>, ref: React.ForwardedRef<HTMLUListElement>) {
172
+ return (
173
+ <ul ref={ref}>
174
+ {props.items.map((item, i) => (
175
+ <li key={i}>{props.renderItem(item)}</li>
176
+ ))}
177
+ </ul>
178
+ );
179
+ }
180
+
181
+ const List = React.forwardRef(ListInner) as <T>(
182
+ props: ListProps<T> & React.RefAttributes<HTMLUListElement>
183
+ ) => ReactElement;
184
+ ```
185
+
186
+ ## Tool Sequence
187
+ 1. **file_read** -- Read existing component files to understand project patterns and conventions
188
+ 2. **grep** -- Search for import patterns, style approach (CSS modules, Tailwind, styled-components)
189
+ 3. **grep** -- Search for existing similar components to maintain consistency
190
+ 4. **file_edit** -- Create the component file with proper TypeScript types, props interface, and JSDoc
191
+ 5. **file_edit** -- Create barrel export (update `index.ts`)
192
+ 6. **file_edit** -- Create test file stub with basic render test
193
+ 7. **shell_exec** -- Run `tsc --noEmit` to verify types
194
+ 8. **shell_exec** -- Run `pnpm build` to verify integration
195
+
196
+ ## Validation Checklist
197
+ - [ ] Props interface is exported and fully typed (no `any`)
198
+ - [ ] Component has JSDoc description
199
+ - [ ] Default props use parameter defaults, not `defaultProps`
200
+ - [ ] `forwardRef` is used if component wraps a DOM element
201
+ - [ ] Semantic HTML elements used (not div-soup)
202
+ - [ ] ARIA attributes present for interactive elements
203
+ - [ ] Component handles all required variants/states
204
+ - [ ] Barrel export updated (`index.ts`)
205
+ - [ ] `tsc --noEmit` passes
206
+ - [ ] `pnpm build` passes
@@ -0,0 +1,208 @@
1
+ # React Hook Extraction Skill
2
+
3
+ ## Identity
4
+ - domain: react
5
+ - type: refactor
6
+ - confidence: 0.80
7
+ - persona: Senior React developer specializing in hook architecture, composition patterns, and separation of concerns. Expert in identifying extractable logic, creating testable custom hooks, and maintaining clean component boundaries.
8
+
9
+ ## Known Error Patterns
10
+
11
+ ### 1. God Component (Too Much Logic)
12
+ - **symptoms**:
13
+ - Component file exceeds 300 lines
14
+ - Multiple unrelated `useState`/`useEffect` pairs in one component
15
+ - Difficult to test individual behaviors
16
+ - Multiple concerns mixed (data fetching, form handling, animations)
17
+ - Code duplication across components for similar logic
18
+ - **causes**:
19
+ - Organic growth without refactoring
20
+ - All logic added directly to the component instead of hooks
21
+ - No separation between presentation and business logic
22
+ - **strategy**:
23
+ 1. Identify logical groups: state + effects that belong together
24
+ 2. Extract each group into a named custom hook: `useFormValidation`, `useDataFetch`
25
+ 3. Component becomes a thin shell: hooks + JSX
26
+ 4. Each hook should have a single responsibility
27
+ 5. Hooks can compose other hooks
28
+ - **tools**: file_read, file_edit, grep
29
+ - **pitfalls**:
30
+ - Do NOT extract a hook with 10+ return values -- split further
31
+ - Do NOT create `useComponent` hooks that mirror the component 1:1 -- find meaningful abstractions
32
+ - Hooks should be reusable, not just "code moved to another file"
33
+
34
+ ### 2. Duplicated State Logic
35
+ - **symptoms**:
36
+ - Same `useState` + `useEffect` pattern repeated in 3+ components
37
+ - Copy-paste of fetch/loading/error state management
38
+ - Identical form validation logic in multiple forms
39
+ - Similar timer/interval patterns across components
40
+ - **causes**:
41
+ - No shared hook library in the project
42
+ - Developers unaware of existing hooks
43
+ - Similar but slightly different requirements leading to copy-paste
44
+ - **strategy**:
45
+ 1. Grep for repeated patterns: `useState.*loading`, `useEffect.*fetch`
46
+ 2. Create a generalized hook with configuration parameters
47
+ 3. Use TypeScript generics for type-safe data hooks
48
+ 4. Document the hook's API and add it to a shared hooks directory
49
+ 5. Replace all duplicated instances with the new hook
50
+ - **tools**: grep, file_read, file_edit
51
+ - **pitfalls**:
52
+ - Do NOT over-generalize -- if two patterns share 60% but differ in critical ways, two separate hooks may be better
53
+ - Ensure the extracted hook handles all edge cases from every call site
54
+
55
+ ### 3. Tightly Coupled Effects
56
+ - **symptoms**:
57
+ - One `useEffect` does multiple unrelated things
58
+ - Dependency array contains unrelated values
59
+ - Changing one behavior requires modifying an unrelated effect
60
+ - Difficult to reason about when effects run
61
+ - **causes**:
62
+ - Multiple concerns combined in one effect for convenience
63
+ - "Just add it to the existing effect" mentality
64
+ - Unclear mental model of effect lifecycle
65
+ - **strategy**:
66
+ 1. Split into separate `useEffect` calls -- one per concern
67
+ 2. Group related state and effect into a custom hook
68
+ 3. Each effect should have a clear, single purpose
69
+ 4. Name the extracted hook after its purpose, not its implementation
70
+ - **tools**: file_read, file_edit
71
+ - **pitfalls**:
72
+ - Multiple effects are fine -- React handles them well
73
+ - Do NOT merge unrelated effects just to "reduce hook calls"
74
+ - Watch for effects that depend on each other -- these might need `useReducer` instead
75
+
76
+ ### 4. Untestable Component Logic
77
+ - **symptoms**:
78
+ - Testing requires full component render for logic-only assertions
79
+ - Mocking is excessive because logic is interleaved with rendering
80
+ - Cannot test edge cases without UI interaction
81
+ - Business logic cannot be reused in different UI contexts
82
+ - **causes**:
83
+ - Business logic lives inside the component body
84
+ - Data transformations done inline in JSX
85
+ - Side effects triggered by rendering, not by explicit calls
86
+ - **strategy**:
87
+ 1. Extract logic into a custom hook
88
+ 2. Test the hook with `renderHook` from `@testing-library/react`
89
+ 3. Hook returns a clean API: `{ data, loading, error, actions }`
90
+ 4. Component becomes a thin rendering layer -- easy to test separately
91
+ 5. Hook can be reused in different UI contexts (mobile, desktop, CLI)
92
+ - **tools**: file_read, file_edit, shell_exec
93
+ - **pitfalls**:
94
+ - `renderHook` is the correct way to test hooks -- do NOT call hooks outside React
95
+ - If a hook needs complex setup, consider if it is doing too much
96
+
97
+ ### 5. Hook Composition Failure
98
+ - **symptoms**:
99
+ - Custom hook re-implements logic that exists in another hook
100
+ - Deep hook call chains that are hard to debug
101
+ - Hooks with too many parameters (> 4)
102
+ - Return type is a large tuple or object with 8+ fields
103
+ - **causes**:
104
+ - Not composing existing hooks
105
+ - Trying to make one hook do everything
106
+ - Flat hook architecture instead of layered
107
+ - **strategy**:
108
+ 1. Build hooks in layers: primitive hooks -> domain hooks -> feature hooks
109
+ 2. Primitive: `useLocalStorage`, `useDebounce`, `useMediaQuery`
110
+ 3. Domain: `useAuth` (uses `useLocalStorage`), `useApi` (uses `useDebounce`)
111
+ 4. Feature: `useUserSearch` (uses `useAuth` + `useApi` + `useDebounce`)
112
+ 5. Each layer adds specific domain knowledge
113
+ - **tools**: file_read, grep, file_edit
114
+ - **pitfalls**:
115
+ - Avoid circular hook dependencies
116
+ - Keep primitive hooks truly primitive -- no business logic
117
+ - Document the hook layer architecture for the team
118
+
119
+ ## Extraction Rules
120
+
121
+ ### When to Extract
122
+ - Same logic appears in 2+ components
123
+ - Component has 3+ `useState` calls for unrelated concerns
124
+ - Component file exceeds 200 lines of logic (excluding JSX)
125
+ - An `useEffect` has 4+ dependencies from different concerns
126
+ - You need to test business logic independently from rendering
127
+
128
+ ### When NOT to Extract
129
+ - Logic is used in exactly one component and is simple (< 20 lines)
130
+ - Extraction would create a hook with only one `useState` and no effects
131
+ - The "hook" would just be a wrapper around a single function call
132
+ - Logic is purely presentational (use a utility function instead)
133
+
134
+ ### Naming Conventions
135
+ - `use<Resource><Action>`: `useUserFetch`, `useFormValidation`
136
+ - NOT `use<Component>Logic`: avoid `useHeaderLogic`, `useSidebarStuff`
137
+ - Return object, not tuple, if > 2 values: `{ data, loading, error, refetch }`
138
+ - File name matches hook name: `useUserFetch.ts`
139
+
140
+ ### State + Effect Grouping
141
+ Group these together in one hook:
142
+ 1. Related `useState` calls (e.g., `data`, `loading`, `error`)
143
+ 2. The `useEffect` that manages them (e.g., fetch call)
144
+ 3. Derived state (`useMemo`) from those states
145
+ 4. Event handlers (`useCallback`) that modify those states
146
+
147
+ ### Hook Composition Pattern
148
+ ```
149
+ // Layer 1: Primitive
150
+ function useDebounce<T>(value: T, delay: number): T { ... }
151
+
152
+ // Layer 2: Domain
153
+ function useSearchApi(query: string) {
154
+ const debouncedQuery = useDebounce(query, 300);
155
+ const [results, setResults] = useState([]);
156
+ useEffect(() => { /* fetch with debouncedQuery */ }, [debouncedQuery]);
157
+ return { results };
158
+ }
159
+
160
+ // Layer 3: Feature
161
+ function useUserSearch() {
162
+ const [query, setQuery] = useState('');
163
+ const { results } = useSearchApi(query);
164
+ const filtered = useMemo(() => filterActive(results), [results]);
165
+ return { query, setQuery, results: filtered };
166
+ }
167
+ ```
168
+
169
+ ### Testing Custom Hooks
170
+ ```
171
+ import { renderHook, act } from '@testing-library/react';
172
+ import { useCounter } from './useCounter';
173
+
174
+ test('should increment counter', () => {
175
+ const { result } = renderHook(() => useCounter(0));
176
+ act(() => { result.current.increment(); });
177
+ expect(result.current.count).toBe(1);
178
+ });
179
+
180
+ test('should handle async operations', async () => {
181
+ const { result } = renderHook(() => useDataFetch('/api/users'));
182
+ expect(result.current.loading).toBe(true);
183
+ await waitFor(() => expect(result.current.loading).toBe(false));
184
+ expect(result.current.data).toBeDefined();
185
+ });
186
+ ```
187
+
188
+ ## Tool Sequence
189
+ 1. **file_read** -- Read the target component to identify extractable logic groups
190
+ 2. **grep** -- Search for similar patterns across the codebase to identify reuse opportunities
191
+ 3. **grep** -- Check for existing hooks in `hooks/` or `utils/` that could be composed
192
+ 4. **file_edit** -- Create the new custom hook file with full TypeScript types
193
+ 5. **file_edit** -- Refactor the component to use the extracted hook
194
+ 6. **file_edit** -- Create test file for the hook using `renderHook`
195
+ 7. **shell_exec** -- Run `tsc --noEmit` to verify types
196
+ 8. **shell_exec** -- Run tests to verify behavior is preserved
197
+
198
+ ## Validation Checklist
199
+ - [ ] Hook has a single, clear responsibility
200
+ - [ ] Hook name follows `use<Resource><Action>` convention
201
+ - [ ] Return type is a typed object (not a large tuple)
202
+ - [ ] Hook is exported and documented with JSDoc
203
+ - [ ] Component is simpler after extraction (fewer lines, fewer concerns)
204
+ - [ ] All existing behavior is preserved (no regression)
205
+ - [ ] Hook test file exists with `renderHook` tests
206
+ - [ ] `tsc --noEmit` passes
207
+ - [ ] `pnpm build` passes
208
+ - [ ] No duplicated logic remains in the original component