claude-dev-env 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/agents/agent-writer.md +157 -0
- package/agents/clasp-deployment-orchestrator.md +609 -0
- package/agents/clean-coder.md +295 -0
- package/agents/code-quality-agent.md +40 -0
- package/agents/code-standards-agent.md +93 -0
- package/agents/config-centralizer.md +686 -0
- package/agents/config-extraction-agent.md +225 -0
- package/agents/doc-orchestrator.md +47 -0
- package/agents/docs-agent.md +112 -0
- package/agents/docx-agent.md +211 -0
- package/agents/git-commit-crafter.md +100 -0
- package/agents/magic-value-eliminator-agent.md +72 -0
- package/agents/mandatory-agent-workflow-agent.md +88 -0
- package/agents/parallel-workflow-coordinator.md +779 -0
- package/agents/pdf-agent.md +302 -0
- package/agents/plan-executor.md +226 -0
- package/agents/pr-description-writer.md +87 -0
- package/agents/project-context-loader.md +238 -0
- package/agents/project-docs-analyzer.md +54 -0
- package/agents/project-structure-organizer-agent.md +72 -0
- package/agents/readability-review-agent.md +76 -0
- package/agents/refactoring-specialist.md +69 -0
- package/agents/right-sized-engineer.md +129 -0
- package/agents/session-continuity-manager.md +53 -0
- package/agents/skill-to-agent-converter.md +371 -0
- package/agents/skill-writer-agent.md +470 -0
- package/agents/stub-detector-agent.md +140 -0
- package/agents/tdd-test-writer.md +62 -0
- package/agents/test-data-builder.md +68 -0
- package/agents/tooling-builder.md +78 -0
- package/agents/user-docs-writer.md +67 -0
- package/agents/validation-expert.md +71 -0
- package/agents/workflow-visual-documenter.md +82 -0
- package/agents/xlsx-agent.md +169 -0
- package/bin/install.mjs +256 -0
- package/commands/commit.md +28 -0
- package/commands/docupdate.md +322 -0
- package/commands/implement.md +102 -0
- package/commands/initialize.md +91 -0
- package/commands/plan.md +63 -0
- package/commands/pr-comments.md +47 -0
- package/commands/readability-review.md +20 -0
- package/commands/review-plan.md +7 -0
- package/commands/right-size.md +15 -0
- package/commands/stubcheck.md +89 -0
- package/commands/sum.md +30 -0
- package/docs/CODE_RULES.md +186 -0
- package/docs/DJANGO_PATTERNS.md +80 -0
- package/docs/REACT_PATTERNS.md +185 -0
- package/docs/TEST_QUALITY.md +104 -0
- package/hooks/advisory/migration-safety-advisor.py +49 -0
- package/hooks/advisory/refactor-guard.py +205 -0
- package/hooks/blocking/block-main-commit.py +168 -0
- package/hooks/blocking/code-rules-enforcer.py +549 -0
- package/hooks/blocking/destructive-command-blocker.py +107 -0
- package/hooks/blocking/docker-settings-guard.py +44 -0
- package/hooks/blocking/hedging-language-blocker.py +130 -0
- package/hooks/blocking/parallel-task-blocker.py +69 -0
- package/hooks/blocking/pr-description-enforcer.py +87 -0
- package/hooks/blocking/pyautogui-scroll-blocker.py +74 -0
- package/hooks/blocking/sensitive-file-protector.py +70 -0
- package/hooks/blocking/tdd-enforcer.py +62 -0
- package/hooks/blocking/test-preflight-check.py +343 -0
- package/hooks/blocking/write-existing-file-blocker.py +63 -0
- package/hooks/git-hooks/post-commit.py +103 -0
- package/hooks/github-action/test_workflow.py +33 -0
- package/hooks/hooks.json +246 -0
- package/hooks/lifecycle/config-change-guard.py +84 -0
- package/hooks/lifecycle/session-end-cleanup.py +59 -0
- package/hooks/notification/attention-needed-notify.py +63 -0
- package/hooks/notification/claude-notification-handler.py +59 -0
- package/hooks/notification/notification_utils.py +206 -0
- package/hooks/rewrite-plugin-paths.py +116 -0
- package/hooks/session/bulk-edit-reminder.py +30 -0
- package/hooks/session/code-rules-reminder.py +97 -0
- package/hooks/session/compact-context-reinject.py +39 -0
- package/hooks/session/hook-structure-context.py +140 -0
- package/hooks/session/plugin-data-dir-cleanup.py +39 -0
- package/hooks/validation/code-style-validator.py +145 -0
- package/hooks/validation/e2e-test-validator.py +142 -0
- package/hooks/validation/hook-format-validator.py +66 -0
- package/hooks/validation/mypy_validator.py +180 -0
- package/hooks/validators/README.md +125 -0
- package/hooks/validators/VALIDATION_REPORT.md +287 -0
- package/hooks/validators/__init__.py +19 -0
- package/hooks/validators/abbreviation_checks.py +82 -0
- package/hooks/validators/code_quality_checks.py +133 -0
- package/hooks/validators/comment_checks.py +188 -0
- package/hooks/validators/file_structure_checks.py +182 -0
- package/hooks/validators/git_checks.py +107 -0
- package/hooks/validators/health_check.py +214 -0
- package/hooks/validators/magic_value_checks.py +81 -0
- package/hooks/validators/mypy_integration.py +52 -0
- package/hooks/validators/output_formatter.py +266 -0
- package/hooks/validators/pr_reference_checks.py +72 -0
- package/hooks/validators/python_antipattern_checks.py +110 -0
- package/hooks/validators/python_style_checks.py +364 -0
- package/hooks/validators/react_checks.py +90 -0
- package/hooks/validators/ruff_integration.py +80 -0
- package/hooks/validators/run_all_validators.py +772 -0
- package/hooks/validators/security_checks.py +135 -0
- package/hooks/validators/test_abbreviation_checks.py +76 -0
- package/hooks/validators/test_bad.tsx +7 -0
- package/hooks/validators/test_code_quality_checks.py +129 -0
- package/hooks/validators/test_file_structure_checks.py +307 -0
- package/hooks/validators/test_files/01_basic_component.tsx +10 -0
- package/hooks/validators/test_files/02_component_without_react.tsx +10 -0
- package/hooks/validators/test_files/03_pure_component.tsx +10 -0
- package/hooks/validators/test_files/04_pure_component_import.tsx +10 -0
- package/hooks/validators/test_files/05_typescript_generics.tsx +14 -0
- package/hooks/validators/test_files/06_typescript_two_generics.tsx +18 -0
- package/hooks/validators/test_files/07_multiline_declaration.tsx +11 -0
- package/hooks/validators/test_files/08_error_boundary_valid.tsx +14 -0
- package/hooks/validators/test_files/09_error_boundary_with_other_class.tsx +20 -0
- package/hooks/validators/test_files/10_inheritance_chain.tsx +16 -0
- package/hooks/validators/test_files/11_ts_file.ts +10 -0
- package/hooks/validators/test_files/12_non_react_class.tsx +14 -0
- package/hooks/validators/test_files/13_functional_component.tsx +8 -0
- package/hooks/validators/test_files/14_indented_class.tsx +13 -0
- package/hooks/validators/test_files/15_getDerivedStateFromError.tsx +14 -0
- package/hooks/validators/test_files/16_mixed_components.tsx +20 -0
- package/hooks/validators/test_files/EXECUTIVE_SUMMARY.md +175 -0
- package/hooks/validators/test_files/TEST_RESULTS_TABLE.txt +60 -0
- package/hooks/validators/test_files/VALIDATION_REPORT.md +201 -0
- package/hooks/validators/test_files/async_views.py +23 -0
- package/hooks/validators/test_files/async_with_imports.py +14 -0
- package/hooks/validators/test_files/bad_inline_imports.py +37 -0
- package/hooks/validators/test_files/management/commands/cmd_01_no_debug_check.py +10 -0
- package/hooks/validators/test_files/management/commands/cmd_02_proper_debug_check.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_03_debug_check_with_return.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_04_imported_DEBUG.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_05_debug_check_in_helper.py +16 -0
- package/hooks/validators/test_files/management/commands/cmd_06_debug_check_late.py +22 -0
- package/hooks/validators/test_files/management/commands/cmd_07_positive_debug_check.py +15 -0
- package/hooks/validators/test_files/management/commands/cmd_08_debug_with_and.py +14 -0
- package/hooks/validators/test_files/not_management_command.py +10 -0
- package/hooks/validators/test_files/skip_decorators/test_01_simple_skip.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_02_pytest_skipif.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_03_unittest_skipIf.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_04_skip_with_parens.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_05_xfail.py +7 -0
- package/hooks/validators/test_files/skip_decorators/test_06_custom_skip.py +11 -0
- package/hooks/validators/test_files/skip_decorators/test_07_capital_Skip.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_08_skipUnless.py +7 -0
- package/hooks/validators/test_files/skip_decorators/test_09_pytest_mark_skip_simple.py +7 -0
- package/hooks/validators/test_files/test_async_functions.py +45 -0
- package/hooks/validators/test_files/test_purecomponent/PureComponentExample.tsx +7 -0
- package/hooks/validators/test_files/test_purecomponent/ReactPureComponentExample.tsx +7 -0
- package/hooks/validators/test_git_checks.py +295 -0
- package/hooks/validators/test_good.tsx +5 -0
- package/hooks/validators/test_health_check.py +57 -0
- package/hooks/validators/test_magic_value_checks.py +63 -0
- package/hooks/validators/test_mypy_integration.py +27 -0
- package/hooks/validators/test_output_formatter.py +150 -0
- package/hooks/validators/test_pr_reference_checks.py +41 -0
- package/hooks/validators/test_python_antipattern_checks.py +113 -0
- package/hooks/validators/test_python_style_checks.py +439 -0
- package/hooks/validators/test_react_checks.py +213 -0
- package/hooks/validators/test_results.txt +25 -0
- package/hooks/validators/test_ruff_integration.py +27 -0
- package/hooks/validators/test_run_all_validators.py +228 -0
- package/hooks/validators/test_run_all_validators_integration.py +48 -0
- package/hooks/validators/test_safety_checks.py +243 -0
- package/hooks/validators/test_security_checks.py +105 -0
- package/hooks/validators/test_test_safety_checks.py +321 -0
- package/hooks/validators/test_todo_checks.py +39 -0
- package/hooks/validators/test_type_safety_checks.py +85 -0
- package/hooks/validators/test_useless_test_checks.py +55 -0
- package/hooks/validators/test_validator_base.py +26 -0
- package/hooks/validators/test_verify_paths.py +34 -0
- package/hooks/validators/todo_checks.py +59 -0
- package/hooks/validators/type_safety_checks.py +101 -0
- package/hooks/validators/useless_test_checks.py +92 -0
- package/hooks/validators/validator_base.py +19 -0
- package/hooks/validators/verify_paths.py +57 -0
- package/hooks/workflow/auto-formatter.py +114 -0
- package/hooks/workflow/investigation-tracker-reset.py +46 -0
- package/package.json +30 -0
- package/rules/agent-spawn-protocol.md +47 -0
- package/rules/cleanup-temp-files.md +27 -0
- package/rules/code-reviews.md +11 -0
- package/rules/code-standards.md +43 -0
- package/rules/conservative-action.md +20 -0
- package/rules/context7.md +12 -0
- package/rules/explore-thoroughly.md +27 -0
- package/rules/git-workflow.md +42 -0
- package/rules/parallel-tools.md +23 -0
- package/rules/research-mode.md +23 -0
- package/rules/right-sized-engineering.md +28 -0
- package/rules/tdd.md +7 -0
- package/rules/testing.md +12 -0
- package/skills/agent-prompt/SKILL.md +102 -0
- package/skills/anthropic-plan/SKILL.md +107 -0
- package/skills/everything-search/SKILL.md +144 -0
- package/skills/ingest/SKILL.md +40 -0
- package/skills/npm-creator/SKILL.md +183 -0
- package/skills/pr-review-responder/EXAMPLES.md +590 -0
- package/skills/pr-review-responder/PRINCIPLES.md +539 -0
- package/skills/pr-review-responder/README.md +209 -0
- package/skills/pr-review-responder/SKILL.md +202 -0
- package/skills/pr-review-responder/TESTING.md +407 -0
- package/skills/pr-review-responder/scripts/respond_to_reviews.py +376 -0
- package/skills/pr-review-responder/update_skill.py +297 -0
- package/skills/prompt-generator/REFERENCE.md +150 -0
- package/skills/prompt-generator/SKILL.md +154 -0
- package/skills/readability-review/SKILL.md +127 -0
- package/skills/recall/SKILL.md +27 -0
- package/skills/remember/SKILL.md +63 -0
- package/skills/rule-audit/SKILL.md +307 -0
- package/skills/rule-creator/SKILL.md +150 -0
- package/skills/skill-writer/REFERENCE.md +246 -0
- package/skills/skill-writer/SKILL.md +270 -0
- package/skills/tdd-team/SKILL.md +128 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Django Patterns Reference
|
|
2
|
+
|
|
3
|
+
> Load this file when working with Django models, views, templates, or migrations.
|
|
4
|
+
|
|
5
|
+
## Model Patterns
|
|
6
|
+
|
|
7
|
+
**NEVER:**
|
|
8
|
+
|
|
9
|
+
- **Create separate user models** - Extend Django's User with OneToOneField
|
|
10
|
+
- Example: `UserProfile(user=OneToOneField(User, related_name='profile'))`
|
|
11
|
+
- See: https://docs.djangoproject.com/en/5.2/topics/auth/customizing/#extending-the-existing-user-model
|
|
12
|
+
|
|
13
|
+
- **Use confusing model/field names** - Match semantics & avoid conflicts
|
|
14
|
+
- "Settings" = user preferences (preferences, settings, display options), "UserProfile" = user metadata
|
|
15
|
+
- `custom_name` avoids conflict with User.username
|
|
16
|
+
|
|
17
|
+
- **Break model fields across multiple lines** - Match the style of other fields in the model
|
|
18
|
+
- Bad: Multi-line ForeignKey definition
|
|
19
|
+
- Good: `evolves_from = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='evolutions')`
|
|
20
|
+
- Consistency within the model class matters more than arbitrary line length rules
|
|
21
|
+
|
|
22
|
+
- **Circumvent Django ORM for database operations** - Always use Django models for DB access
|
|
23
|
+
- Django ORM handles migrations, relationships, and abstractions under the hood
|
|
24
|
+
- Bypassing it creates inconsistencies with migrations and model state
|
|
25
|
+
- For DB operations: Use Django models, views, management commands
|
|
26
|
+
- For non-DB tools: Use pure Python scripts (file processing, API calls, asset uploads)
|
|
27
|
+
|
|
28
|
+
## Template Patterns
|
|
29
|
+
|
|
30
|
+
**NEVER:**
|
|
31
|
+
|
|
32
|
+
- **Put view-specific template code in base.html** - Only in the template that uses it
|
|
33
|
+
- Example: `{% if layout %}` in base.html when only home view defines layout
|
|
34
|
+
- If variable is always present in specific template, no conditional needed
|
|
35
|
+
|
|
36
|
+
## Production Migrations (Post-Launch ONLY)
|
|
37
|
+
|
|
38
|
+
**CRITICAL:** After launch, model changes MUST be backwards-compatible.
|
|
39
|
+
|
|
40
|
+
**Why:** Manual migration takes 2+ minutes. New code runs against old schema during this window.
|
|
41
|
+
|
|
42
|
+
### Safe vs Unsafe Changes
|
|
43
|
+
|
|
44
|
+
| Safe | Unsafe |
|
|
45
|
+
|------|--------|
|
|
46
|
+
| Nullable fields | Removing fields |
|
|
47
|
+
| Fields with defaults | Renaming fields |
|
|
48
|
+
| New models | Non-null without defaults |
|
|
49
|
+
| | Type changes |
|
|
50
|
+
|
|
51
|
+
### Migration Strategy
|
|
52
|
+
|
|
53
|
+
1. Deploy code working with both schemas
|
|
54
|
+
2. Run migration (2+ min)
|
|
55
|
+
3. Verify production
|
|
56
|
+
4. Deploy cleanup if needed
|
|
57
|
+
|
|
58
|
+
*During development: manual migration is fine, no big deal.*
|
|
59
|
+
|
|
60
|
+
## API Contract Changes (Post-Launch)
|
|
61
|
+
|
|
62
|
+
**CRITICAL:** After launch, API contract changes MUST be backwards-compatible.
|
|
63
|
+
|
|
64
|
+
**Why:** Users may have old client code cached. Old clients must work with new API.
|
|
65
|
+
|
|
66
|
+
### Safe vs Unsafe Changes
|
|
67
|
+
|
|
68
|
+
| Safe | Unsafe |
|
|
69
|
+
|------|--------|
|
|
70
|
+
| New optional fields | Removing fields |
|
|
71
|
+
| New endpoints | Renaming fields |
|
|
72
|
+
| Additive changes | Changing field types |
|
|
73
|
+
| | Removing endpoints |
|
|
74
|
+
|
|
75
|
+
### API Evolution Strategy
|
|
76
|
+
|
|
77
|
+
1. Add new fields/endpoints alongside old ones
|
|
78
|
+
2. Deploy frontend that handles both old and new
|
|
79
|
+
3. Deprecate old fields (but keep working)
|
|
80
|
+
4. Remove old fields only after all clients updated
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# React Patterns Reference
|
|
2
|
+
|
|
3
|
+
> Load this file when working with React components, state management, hooks, or testing.
|
|
4
|
+
|
|
5
|
+
## Component Patterns
|
|
6
|
+
|
|
7
|
+
**NEVER:**
|
|
8
|
+
|
|
9
|
+
- **Create wrapper components for single use** - Use the component directly
|
|
10
|
+
- Example: `<ButtonWrapper><Button /></ButtonWrapper>` when Button is only used once
|
|
11
|
+
- Ask: "Is this wrapper adding any value?"
|
|
12
|
+
|
|
13
|
+
- **Put page-specific logic in shared components** - Keep shared components pure
|
|
14
|
+
- Example: `if (pathname === '/home')` inside a shared Header component
|
|
15
|
+
- Shared components should be context-agnostic
|
|
16
|
+
|
|
17
|
+
- **Use class components for new code** - Always use functional components with hooks
|
|
18
|
+
- Exception: Error boundaries still require class components (React limitation)
|
|
19
|
+
|
|
20
|
+
- **Create HOCs when hooks work** - Prefer custom hooks over HOCs
|
|
21
|
+
- Example: `withAuth(Component)` -> `useAuth()` hook
|
|
22
|
+
- HOCs add wrapper layers; hooks are composable
|
|
23
|
+
|
|
24
|
+
- **Prop drill more than 2 levels** - Use Context or state management
|
|
25
|
+
- Example: `<Parent data={x}><Child data={x}><GrandChild data={x} /></Child></Parent>`
|
|
26
|
+
- Solution: Create context or lift state appropriately
|
|
27
|
+
|
|
28
|
+
## State Management Patterns
|
|
29
|
+
|
|
30
|
+
**NEVER:**
|
|
31
|
+
|
|
32
|
+
- **Store derived state** - Calculate on render
|
|
33
|
+
- Bad: `const [fullName, setFullName] = useState(first + ' ' + last)`
|
|
34
|
+
- Good: `const fullName = first + ' ' + last`
|
|
35
|
+
|
|
36
|
+
- **Mutate state directly** - Always create new references
|
|
37
|
+
- Bad: `items.push(newItem); setItems(items)`
|
|
38
|
+
- Good: `setItems([...items, newItem])`
|
|
39
|
+
|
|
40
|
+
- **Use useState for complex state logic** - Use useReducer
|
|
41
|
+
- When: Multiple sub-values, next state depends on previous, complex update logic
|
|
42
|
+
- Example: Form with many fields, shopping cart, multi-step wizard
|
|
43
|
+
|
|
44
|
+
- **Create global state for local concerns** - Colocate state
|
|
45
|
+
- If only one component uses the state, keep it in that component
|
|
46
|
+
- Lift state only when siblings need to share
|
|
47
|
+
|
|
48
|
+
- **Forget to memoize expensive calculations** - Use useMemo
|
|
49
|
+
- Bad: `const sorted = items.sort((a, b) => ...)` on every render
|
|
50
|
+
- Good: `const sorted = useMemo(() => items.sort(...), [items])`
|
|
51
|
+
|
|
52
|
+
## Hooks Patterns
|
|
53
|
+
|
|
54
|
+
**NEVER:**
|
|
55
|
+
|
|
56
|
+
- **Call hooks conditionally** - Hooks must be at top level
|
|
57
|
+
- Bad: `if (condition) { const [x, setX] = useState() }`
|
|
58
|
+
- Good: Always call hooks, use condition inside
|
|
59
|
+
|
|
60
|
+
- **Create effects without cleanup** - Return cleanup function when needed
|
|
61
|
+
- Subscriptions, timers, event listeners MUST be cleaned up
|
|
62
|
+
- Bad: `useEffect(() => { window.addEventListener(...) }, [])`
|
|
63
|
+
- Good: `useEffect(() => { window.addEventListener(...); return () => window.removeEventListener(...) }, [])`
|
|
64
|
+
|
|
65
|
+
- **Use empty dependency arrays incorrectly** - Include all dependencies
|
|
66
|
+
- Bad: `useEffect(() => { fetchData(userId) }, [])` (missing userId)
|
|
67
|
+
- Good: `useEffect(() => { fetchData(userId) }, [userId])`
|
|
68
|
+
- Use ESLint exhaustive-deps rule
|
|
69
|
+
|
|
70
|
+
- **Create new functions in render without useCallback** - Memoize callbacks passed to children
|
|
71
|
+
- Bad: `<Child onClick={() => handleClick(id)} />`
|
|
72
|
+
- Good: `const handleChildClick = useCallback(() => handleClick(id), [id])`
|
|
73
|
+
|
|
74
|
+
- **Overuse useMemo/useCallback** - Only optimize when needed
|
|
75
|
+
- Memoization has cost; don't wrap everything
|
|
76
|
+
- Profile first, then optimize specific bottlenecks
|
|
77
|
+
|
|
78
|
+
## Component Organization
|
|
79
|
+
|
|
80
|
+
**File structure:**
|
|
81
|
+
```
|
|
82
|
+
components/
|
|
83
|
+
ComponentName/
|
|
84
|
+
ComponentName.tsx # Component logic
|
|
85
|
+
ComponentName.test.tsx # Tests (colocated)
|
|
86
|
+
index.ts # Public export
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**NEVER:**
|
|
90
|
+
|
|
91
|
+
- **Mix business logic with UI** - Separate concerns
|
|
92
|
+
- Custom hooks for business logic: `useTaskManager()`
|
|
93
|
+
- Components for UI: `<TaskList tasks={tasks} />`
|
|
94
|
+
|
|
95
|
+
- **Create god components** - Split into focused components
|
|
96
|
+
- If component > 200 lines, likely doing too much
|
|
97
|
+
- Extract sub-components or custom hooks
|
|
98
|
+
|
|
99
|
+
- **Export internal components** - Only export what's needed
|
|
100
|
+
- Internal helpers stay private to the component folder
|
|
101
|
+
- Only `index.ts` defines the public API
|
|
102
|
+
|
|
103
|
+
## Code Standards
|
|
104
|
+
|
|
105
|
+
- **Complete type annotations** - All props, state, and return types defined
|
|
106
|
+
- **No `any` types** - Use generics, union types, or `unknown` with type guards
|
|
107
|
+
- **No `@ts-ignore` or `@ts-expect-error`** without clear justification
|
|
108
|
+
- **Functional components only** - No class components (except error boundaries)
|
|
109
|
+
- **Custom hooks for logic** - Separate business logic from UI
|
|
110
|
+
- **Props interfaces** - Define explicit Props type for every component
|
|
111
|
+
- **Immutable updates** - Never mutate state or props
|
|
112
|
+
- **Small components** - Target 50-100 lines, max 200 lines
|
|
113
|
+
- **Max 2 nesting levels** - Extract sub-components
|
|
114
|
+
- **No inline styles** - Use CSS modules, styled-components, or Tailwind
|
|
115
|
+
|
|
116
|
+
## Testing Patterns
|
|
117
|
+
|
|
118
|
+
- **Use React Testing Library** - Not Enzyme
|
|
119
|
+
- **Query by accessibility** - Role, label, text (not test-id as first choice)
|
|
120
|
+
- **Test user flows** - Render, interact, assert on visible changes
|
|
121
|
+
- **Mock API boundaries** - Not internal functions
|
|
122
|
+
- **Colocate tests** - `Component.test.tsx` next to `Component.tsx`
|
|
123
|
+
|
|
124
|
+
**Testing anti-patterns:**
|
|
125
|
+
|
|
126
|
+
- **Snapshot test everything** - Only for stable, visual components
|
|
127
|
+
- **Mock too much** - Prefer integration tests
|
|
128
|
+
- **Test implementation details** - Test what user experiences
|
|
129
|
+
- **Forget async handling** - Use waitFor, findBy queries
|
|
130
|
+
|
|
131
|
+
## Example: God Component Refactoring
|
|
132
|
+
|
|
133
|
+
**Bad:**
|
|
134
|
+
```tsx
|
|
135
|
+
function TaskDashboard() {
|
|
136
|
+
const [tasks, setTasks] = useState([]);
|
|
137
|
+
const [filter, setFilter] = useState('all');
|
|
138
|
+
const [sortBy, setSortBy] = useState('date');
|
|
139
|
+
// ... 200 more lines of logic
|
|
140
|
+
return (
|
|
141
|
+
<div>{/* 150 lines of JSX */}</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Good:**
|
|
147
|
+
```tsx
|
|
148
|
+
function TaskDashboard() {
|
|
149
|
+
const { tasks, filter, sortBy, actions } = useTaskManager();
|
|
150
|
+
return (
|
|
151
|
+
<div>
|
|
152
|
+
<TaskFilters filter={filter} onFilterChange={actions.setFilter} />
|
|
153
|
+
<TaskList tasks={tasks} sortBy={sortBy} />
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function useTaskManager() {
|
|
159
|
+
const [tasks, setTasks] = useState<Task[]>([]);
|
|
160
|
+
const [filter, setFilter] = useState<Filter>('all');
|
|
161
|
+
// ... focused logic
|
|
162
|
+
return { tasks, filter, sortBy, actions };
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Example: Derived State
|
|
167
|
+
|
|
168
|
+
**Bad:**
|
|
169
|
+
```tsx
|
|
170
|
+
const [items, setItems] = useState<Item[]>([]);
|
|
171
|
+
const [filteredItems, setFilteredItems] = useState<Item[]>([]);
|
|
172
|
+
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
setFilteredItems(items.filter(i => i.active));
|
|
175
|
+
}, [items]);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Good:**
|
|
179
|
+
```tsx
|
|
180
|
+
const [items, setItems] = useState<Item[]>([]);
|
|
181
|
+
const filteredItems = useMemo(
|
|
182
|
+
() => items.filter(i => i.active),
|
|
183
|
+
[items]
|
|
184
|
+
);
|
|
185
|
+
```
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Test Quality Reference
|
|
2
|
+
|
|
3
|
+
> Load this file when writing tests, reviewing test code, or setting up test infrastructure.
|
|
4
|
+
|
|
5
|
+
## Test Infrastructure Anti-Patterns
|
|
6
|
+
|
|
7
|
+
**CRITICAL: Test helpers get over-engineered MORE than any other code.**
|
|
8
|
+
|
|
9
|
+
**ALWAYS:**
|
|
10
|
+
- Single file
|
|
11
|
+
- Simple functions
|
|
12
|
+
- Minimal abstractions
|
|
13
|
+
- Pragmatic approach
|
|
14
|
+
|
|
15
|
+
**NEVER:**
|
|
16
|
+
- Multi-file packages
|
|
17
|
+
- Cache classes
|
|
18
|
+
- Abstractions "for future"
|
|
19
|
+
- Rigid constraints
|
|
20
|
+
|
|
21
|
+
**Pre-check (MANDATORY):**
|
|
22
|
+
- [ ] ONE file?
|
|
23
|
+
- [ ] Functions not classes?
|
|
24
|
+
- [ ] Solving problem not building infrastructure?
|
|
25
|
+
- [ ] Junior-dev-understandable?
|
|
26
|
+
|
|
27
|
+
## Delete Useless Tests
|
|
28
|
+
|
|
29
|
+
**Tests must add value.** Delete these types of tests:
|
|
30
|
+
|
|
31
|
+
### NEVER test:
|
|
32
|
+
|
|
33
|
+
**Function existence** - "Testing that the function exists doesn't add value. If the function doesn't exist, the code will not run."
|
|
34
|
+
- Bad: `test_public_api_exports_download_function()` only verified `callable(func)`
|
|
35
|
+
- Action: DELETE
|
|
36
|
+
|
|
37
|
+
**Constant values** - "It's silly to test that constant values have not changed."
|
|
38
|
+
- Bad: `assert CACHE_DIR == "cache"`
|
|
39
|
+
- Action: DELETE
|
|
40
|
+
|
|
41
|
+
**Duplicate coverage** - "Isn't this test case the same as test_X?"
|
|
42
|
+
- Action: DELETE redundant tests
|
|
43
|
+
|
|
44
|
+
## Test Dependencies MUST FAIL
|
|
45
|
+
|
|
46
|
+
"The tests should fail if they can't run. Missing system dependencies should make the test fail."
|
|
47
|
+
|
|
48
|
+
- **NEVER** use `@skip_if_missing_dependency` or similar skip decorators
|
|
49
|
+
- Tests FAIL with clear error -> forces installation
|
|
50
|
+
|
|
51
|
+
## Core Testing Principles
|
|
52
|
+
|
|
53
|
+
- **Behavior-driven** - Verify expected behavior, not implementation
|
|
54
|
+
- **Test through public API** - Never examine internals
|
|
55
|
+
- **100% coverage via business behavior** - Not implementation coverage
|
|
56
|
+
- **No 1:1 mapping** - Test files test behavior, not individual implementation files
|
|
57
|
+
- **Tests document behavior** - Tests are the spec
|
|
58
|
+
|
|
59
|
+
## React Testing Patterns
|
|
60
|
+
|
|
61
|
+
### ALWAYS:
|
|
62
|
+
|
|
63
|
+
**Test behavior, not implementation** - What user sees/does, not internal state
|
|
64
|
+
- Bad: `expect(component.state.isOpen).toBe(true)`
|
|
65
|
+
- Good: `expect(screen.getByRole('dialog')).toBeVisible()`
|
|
66
|
+
|
|
67
|
+
**Use Testing Library queries correctly** - Priority order:
|
|
68
|
+
1. `getByRole` (best)
|
|
69
|
+
2. `getByLabelText`
|
|
70
|
+
3. `getByText`
|
|
71
|
+
4. `getByTestId` (last resort)
|
|
72
|
+
|
|
73
|
+
- Bad: `getByTestId('submit-button')` as first choice
|
|
74
|
+
- Good: `getByRole('button', { name: /submit/i })`
|
|
75
|
+
|
|
76
|
+
**Test user interactions**
|
|
77
|
+
- Use `userEvent` over `fireEvent` (more realistic)
|
|
78
|
+
- `await userEvent.click(button)` then assert result
|
|
79
|
+
|
|
80
|
+
### NEVER:
|
|
81
|
+
|
|
82
|
+
**Snapshot test everything** - Only for stable, visual components
|
|
83
|
+
- Snapshots break on any change, creating noise
|
|
84
|
+
- Reserve for design system components, icons
|
|
85
|
+
|
|
86
|
+
**Mock too much** - Prefer integration tests
|
|
87
|
+
- Bad: Mock every hook and function
|
|
88
|
+
- Good: Render with real hooks, mock only API calls
|
|
89
|
+
|
|
90
|
+
**Test implementation details** - Test what user experiences
|
|
91
|
+
- Bad: Assert on state values, hook return values
|
|
92
|
+
- Good: Assert on rendered output, user-visible behavior
|
|
93
|
+
|
|
94
|
+
**Forget async handling** - Use waitFor, findBy queries
|
|
95
|
+
- Bad: Expect immediately after async action
|
|
96
|
+
- Good: `await waitFor(() => expect(...))` or `await screen.findByText(...)`
|
|
97
|
+
|
|
98
|
+
## Test File Organization
|
|
99
|
+
|
|
100
|
+
- **Use React Testing Library** - Not Enzyme
|
|
101
|
+
- **Query by accessibility** - Role, label, text (not test-id as first choice)
|
|
102
|
+
- **Test user flows** - Render, interact, assert on visible changes
|
|
103
|
+
- **Mock API boundaries** - Not internal functions
|
|
104
|
+
- **Colocate tests** - `Component.test.tsx` next to `Component.tsx`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Advisory hook: warn when Django migrations contain unsafe operations."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
MIGRATION_PATH_PATTERN = re.compile(r"[/\\]migrations[/\\]\d{4}_\w+\.py$")
|
|
9
|
+
UNSAFE_OPERATIONS = ["RemoveField", "RenameField", "DeleteModel", "RenameModel"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
try:
|
|
14
|
+
hook_input = json.load(sys.stdin)
|
|
15
|
+
except json.JSONDecodeError:
|
|
16
|
+
sys.exit(0)
|
|
17
|
+
|
|
18
|
+
tool_input = hook_input.get("tool_input", {})
|
|
19
|
+
file_path = tool_input.get("file_path", "")
|
|
20
|
+
|
|
21
|
+
if not MIGRATION_PATH_PATTERN.search(file_path):
|
|
22
|
+
sys.exit(0)
|
|
23
|
+
|
|
24
|
+
content = tool_input.get("content", "") or tool_input.get("new_string", "")
|
|
25
|
+
found_unsafe = [op for op in UNSAFE_OPERATIONS if op in content]
|
|
26
|
+
|
|
27
|
+
if found_unsafe:
|
|
28
|
+
operations = ", ".join(found_unsafe)
|
|
29
|
+
print(
|
|
30
|
+
json.dumps(
|
|
31
|
+
{
|
|
32
|
+
"hookSpecificOutput": {
|
|
33
|
+
"hookEventName": "PreToolUse",
|
|
34
|
+
"permissionDecision": "ask",
|
|
35
|
+
"permissionDecisionReason": (
|
|
36
|
+
f"MIGRATION SAFETY: Contains {operations}. "
|
|
37
|
+
"Post-launch, model changes MUST be backwards-compatible. "
|
|
38
|
+
"Verify this won't break running instances during deployment."
|
|
39
|
+
),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Refactor guard - blocks edits that rename/restructure existing code not in the git diff.
|
|
4
|
+
|
|
5
|
+
Detects when an Edit tool call is modifying existing code (renaming variables,
|
|
6
|
+
functions, restructuring) rather than writing new code or replacing wholesale.
|
|
7
|
+
|
|
8
|
+
Only fires for Edit operations (not Write, which creates/replaces entire files).
|
|
9
|
+
"""
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
REFACTOR_BYPASS_TOKEN_PATH = Path.home() / ".claude" / ".refactor-bypass-token"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_git_diff_added_lines(file_path: str) -> set[str]:
|
|
22
|
+
"""Get the set of added lines (stripped) from git diff for a file."""
|
|
23
|
+
added_lines: set[str] = set()
|
|
24
|
+
try:
|
|
25
|
+
result = subprocess.run(
|
|
26
|
+
["git", "diff", "HEAD", "--", file_path],
|
|
27
|
+
capture_output=True,
|
|
28
|
+
text=True,
|
|
29
|
+
timeout=5,
|
|
30
|
+
)
|
|
31
|
+
for line in result.stdout.split("\n"):
|
|
32
|
+
if line.startswith("+") and not line.startswith("+++"):
|
|
33
|
+
added_lines.add(line[1:].strip())
|
|
34
|
+
|
|
35
|
+
staged_result = subprocess.run(
|
|
36
|
+
["git", "diff", "--staged", "--", file_path],
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
timeout=5,
|
|
40
|
+
)
|
|
41
|
+
for line in staged_result.stdout.split("\n"):
|
|
42
|
+
if line.startswith("+") and not line.startswith("+++"):
|
|
43
|
+
added_lines.add(line[1:].strip())
|
|
44
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
45
|
+
return set()
|
|
46
|
+
return added_lines
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_new_file(file_path: str) -> bool:
|
|
50
|
+
"""Check if file is untracked (entirely new)."""
|
|
51
|
+
try:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
["git", "ls-files", "--others", "--exclude-standard", "--", file_path],
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
timeout=5,
|
|
57
|
+
)
|
|
58
|
+
return bool(result.stdout.strip())
|
|
59
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def is_hook_infrastructure(file_path: str) -> bool:
|
|
64
|
+
"""Check if file is a Claude Code hook."""
|
|
65
|
+
path_lower = file_path.lower().replace("\\", "/")
|
|
66
|
+
return "/.claude/" in path_lower
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def extract_identifiers(code: str) -> set[str]:
|
|
70
|
+
"""Extract meaningful identifiers (variable/function/class names) from code."""
|
|
71
|
+
identifier_pattern = re.compile(r'\b([a-zA-Z_][a-zA-Z0-9_]{2,})\b')
|
|
72
|
+
identifiers = set(identifier_pattern.findall(code))
|
|
73
|
+
python_keywords = {
|
|
74
|
+
"def", "class", "return", "import", "from", "if", "elif", "else",
|
|
75
|
+
"for", "while", "try", "except", "finally", "with", "as", "yield",
|
|
76
|
+
"raise", "pass", "break", "continue", "and", "or", "not", "in",
|
|
77
|
+
"is", "lambda", "None", "True", "False", "self", "cls", "async",
|
|
78
|
+
"await", "global", "nonlocal", "assert", "del", "print", "len",
|
|
79
|
+
"range", "list", "dict", "set", "str", "int", "float", "bool",
|
|
80
|
+
"type", "isinstance", "hasattr", "getattr", "setattr", "super",
|
|
81
|
+
"property", "staticmethod", "classmethod", "abstractmethod",
|
|
82
|
+
"Optional", "Union", "List", "Dict", "Set", "Tuple", "Any",
|
|
83
|
+
}
|
|
84
|
+
return identifiers - python_keywords
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def is_refactor_edit(old_string: str, new_string: str) -> Optional[str]:
|
|
88
|
+
"""Detect if an edit is a rename/refactor rather than a functional change.
|
|
89
|
+
|
|
90
|
+
Returns a description of the refactor if detected, None otherwise.
|
|
91
|
+
"""
|
|
92
|
+
old_lines = [line.strip() for line in old_string.strip().split("\n") if line.strip()]
|
|
93
|
+
new_lines = [line.strip() for line in new_string.strip().split("\n") if line.strip()]
|
|
94
|
+
|
|
95
|
+
if not old_lines or not new_lines:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
if abs(len(old_lines) - len(new_lines)) > max(len(old_lines) // 2, 3):
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
old_identifiers = extract_identifiers(old_string)
|
|
102
|
+
new_identifiers = extract_identifiers(new_string)
|
|
103
|
+
|
|
104
|
+
removed_identifiers = old_identifiers - new_identifiers
|
|
105
|
+
added_identifiers = new_identifiers - old_identifiers
|
|
106
|
+
|
|
107
|
+
if not removed_identifiers or not added_identifiers:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
old_no_ids = old_string
|
|
111
|
+
new_no_ids = new_string
|
|
112
|
+
for identifier in old_identifiers | new_identifiers:
|
|
113
|
+
old_no_ids = old_no_ids.replace(identifier, "ID")
|
|
114
|
+
new_no_ids = new_no_ids.replace(identifier, "ID")
|
|
115
|
+
|
|
116
|
+
old_structure = re.sub(r'\s+', ' ', old_no_ids.strip())
|
|
117
|
+
new_structure = re.sub(r'\s+', ' ', new_no_ids.strip())
|
|
118
|
+
|
|
119
|
+
if old_structure == new_structure:
|
|
120
|
+
renamed = []
|
|
121
|
+
for old_id in sorted(removed_identifiers):
|
|
122
|
+
for new_id in sorted(added_identifiers):
|
|
123
|
+
if old_id.lower().replace("_", "") == new_id.lower().replace("_", ""):
|
|
124
|
+
renamed.append(f"{old_id} -> {new_id}")
|
|
125
|
+
break
|
|
126
|
+
old_words = set(re.findall(r'[a-z]+|[A-Z][a-z]*', old_id))
|
|
127
|
+
new_words = set(re.findall(r'[a-z]+|[A-Z][a-z]*', new_id))
|
|
128
|
+
if old_words and new_words and len(old_words & new_words) >= len(old_words) * 0.5:
|
|
129
|
+
renamed.append(f"{old_id} -> {new_id}")
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
if renamed:
|
|
133
|
+
return f"Renaming detected: {', '.join(renamed[:3])}"
|
|
134
|
+
|
|
135
|
+
if len(removed_identifiers) >= 2 and len(added_identifiers) >= 2:
|
|
136
|
+
return f"Multiple identifiers changed with same structure: removed {sorted(removed_identifiers)[:3]}, added {sorted(added_identifiers)[:3]}"
|
|
137
|
+
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def is_bypass_approved() -> bool:
|
|
142
|
+
"""Check if user explicitly approved a refactor bypass via one-time token file.
|
|
143
|
+
|
|
144
|
+
The token file is deleted after a single use, so each refactor
|
|
145
|
+
requires fresh explicit approval from the user.
|
|
146
|
+
"""
|
|
147
|
+
if REFACTOR_BYPASS_TOKEN_PATH.exists():
|
|
148
|
+
REFACTOR_BYPASS_TOKEN_PATH.unlink()
|
|
149
|
+
return True
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main() -> None:
|
|
154
|
+
try:
|
|
155
|
+
input_data = json.load(sys.stdin)
|
|
156
|
+
except json.JSONDecodeError:
|
|
157
|
+
sys.exit(0)
|
|
158
|
+
|
|
159
|
+
tool_name = input_data.get("tool_name", "")
|
|
160
|
+
if tool_name != "Edit":
|
|
161
|
+
sys.exit(0)
|
|
162
|
+
|
|
163
|
+
if is_bypass_approved():
|
|
164
|
+
sys.exit(0)
|
|
165
|
+
|
|
166
|
+
tool_input = input_data.get("tool_input", {})
|
|
167
|
+
file_path = tool_input.get("file_path", "")
|
|
168
|
+
old_string = tool_input.get("old_string", "")
|
|
169
|
+
new_string = tool_input.get("new_string", "")
|
|
170
|
+
|
|
171
|
+
if not file_path or not old_string or not new_string:
|
|
172
|
+
sys.exit(0)
|
|
173
|
+
|
|
174
|
+
if is_hook_infrastructure(file_path):
|
|
175
|
+
sys.exit(0)
|
|
176
|
+
|
|
177
|
+
if is_new_file(file_path):
|
|
178
|
+
sys.exit(0)
|
|
179
|
+
|
|
180
|
+
refactor_description = is_refactor_edit(old_string, new_string)
|
|
181
|
+
if not refactor_description:
|
|
182
|
+
sys.exit(0)
|
|
183
|
+
|
|
184
|
+
diff_added_lines = get_git_diff_added_lines(file_path)
|
|
185
|
+
|
|
186
|
+
old_lines_stripped = {line.strip() for line in old_string.split("\n") if line.strip()}
|
|
187
|
+
old_lines_in_diff = old_lines_stripped & diff_added_lines
|
|
188
|
+
|
|
189
|
+
if old_lines_in_diff and len(old_lines_in_diff) >= len(old_lines_stripped) * 0.5:
|
|
190
|
+
sys.exit(0)
|
|
191
|
+
|
|
192
|
+
result = {
|
|
193
|
+
"hookSpecificOutput": {
|
|
194
|
+
"hookEventName": "PreToolUse",
|
|
195
|
+
"permissionDecision": "allow",
|
|
196
|
+
"additionalContext": f"[HOOK ADVISORY] Refactor guard — {refactor_description} in {file_path}. Only modify lines already changed in the current git diff. Ask the user for explicit approval first. If the user approves, create the bypass token then retry.",
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
print(json.dumps(result))
|
|
200
|
+
sys.stdout.flush()
|
|
201
|
+
sys.exit(0)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
main()
|