red64-cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -0
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/CompleteStep.d.ts.map +1 -1
- package/dist/components/init/CompleteStep.js +2 -2
- package/dist/components/init/CompleteStep.js.map +1 -1
- package/dist/components/init/TestCheckStep.d.ts +16 -0
- package/dist/components/init/TestCheckStep.d.ts.map +1 -0
- package/dist/components/init/TestCheckStep.js +120 -0
- package/dist/components/init/TestCheckStep.js.map +1 -0
- package/dist/components/init/index.d.ts +1 -0
- package/dist/components/init/index.d.ts.map +1 -1
- package/dist/components/init/index.js +1 -0
- package/dist/components/init/index.js.map +1 -1
- package/dist/components/init/types.d.ts +9 -0
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +69 -6
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/ListScreen.d.ts.map +1 -1
- package/dist/components/screens/ListScreen.js +28 -3
- package/dist/components/screens/ListScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +212 -13
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/components/ui/ArtifactsSidebar.d.ts +19 -0
- package/dist/components/ui/ArtifactsSidebar.d.ts.map +1 -0
- package/dist/components/ui/ArtifactsSidebar.js +51 -0
- package/dist/components/ui/ArtifactsSidebar.js.map +1 -0
- package/dist/components/ui/FeatureSidebar.d.ts.map +1 -1
- package/dist/components/ui/FeatureSidebar.js +1 -1
- package/dist/components/ui/FeatureSidebar.js.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/services/ClaudeErrorDetector.js +3 -3
- package/dist/services/ClaudeErrorDetector.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/ProjectDetector.d.ts +28 -0
- package/dist/services/ProjectDetector.d.ts.map +1 -0
- package/dist/services/ProjectDetector.js +236 -0
- package/dist/services/ProjectDetector.js.map +1 -0
- package/dist/services/TestRunner.d.ts +46 -0
- package/dist/services/TestRunner.d.ts.map +1 -0
- package/dist/services/TestRunner.js +85 -0
- package/dist/services/TestRunner.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/.red64/settings/templates/specs/gap-analysis.md +163 -0
- package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
- package/framework/agents/claude/.claude/agents/red64/validate-gap.md +13 -7
- package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
- package/framework/agents/claude/.claude/commands/red64/validate-gap.md +4 -0
- package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
- package/framework/agents/codex/.codex/agents/red64/validate-gap.md +13 -7
- package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
- package/framework/agents/codex/.codex/commands/red64/validate-gap.md +4 -0
- package/framework/stacks/generic/feedback.md +80 -0
- package/framework/stacks/nextjs/accessibility.md +437 -0
- package/framework/stacks/nextjs/api.md +431 -0
- package/framework/stacks/nextjs/coding-style.md +282 -0
- package/framework/stacks/nextjs/commenting.md +226 -0
- package/framework/stacks/nextjs/components.md +411 -0
- package/framework/stacks/nextjs/conventions.md +333 -0
- package/framework/stacks/nextjs/css.md +310 -0
- package/framework/stacks/nextjs/error-handling.md +442 -0
- package/framework/stacks/nextjs/feedback.md +124 -0
- package/framework/stacks/nextjs/migrations.md +332 -0
- package/framework/stacks/nextjs/models.md +362 -0
- package/framework/stacks/nextjs/queries.md +410 -0
- package/framework/stacks/nextjs/responsive.md +338 -0
- package/framework/stacks/nextjs/tech-stack.md +177 -0
- package/framework/stacks/nextjs/test-writing.md +475 -0
- package/framework/stacks/nextjs/validation.md +467 -0
- package/framework/stacks/python/api.md +468 -0
- package/framework/stacks/python/authentication.md +342 -0
- package/framework/stacks/python/code-quality.md +283 -0
- package/framework/stacks/python/code-refactoring.md +315 -0
- package/framework/stacks/python/coding-style.md +462 -0
- package/framework/stacks/python/conventions.md +399 -0
- package/framework/stacks/python/error-handling.md +512 -0
- package/framework/stacks/python/feedback.md +92 -0
- package/framework/stacks/python/implement-ai-llm.md +468 -0
- package/framework/stacks/python/migrations.md +388 -0
- package/framework/stacks/python/models.md +399 -0
- package/framework/stacks/python/python.md +232 -0
- package/framework/stacks/python/queries.md +451 -0
- package/framework/stacks/python/structure.md +245 -58
- package/framework/stacks/python/tech.md +92 -35
- package/framework/stacks/python/testing.md +380 -0
- package/framework/stacks/python/validation.md +471 -0
- package/framework/stacks/rails/authentication.md +176 -0
- package/framework/stacks/rails/code-quality.md +287 -0
- package/framework/stacks/rails/code-refactoring.md +299 -0
- package/framework/stacks/rails/feedback.md +130 -0
- package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
- package/framework/stacks/rails/rails.md +301 -0
- package/framework/stacks/rails/rails8-best-practices.md +498 -0
- package/framework/stacks/rails/rails8-css.md +573 -0
- package/framework/stacks/rails/structure.md +140 -0
- package/framework/stacks/rails/tech.md +108 -0
- package/framework/stacks/react/code-quality.md +521 -0
- package/framework/stacks/react/components.md +625 -0
- package/framework/stacks/react/data-fetching.md +586 -0
- package/framework/stacks/react/feedback.md +110 -0
- package/framework/stacks/react/forms.md +694 -0
- package/framework/stacks/react/performance.md +640 -0
- package/framework/stacks/react/product.md +22 -9
- package/framework/stacks/react/state-management.md +472 -0
- package/framework/stacks/react/structure.md +351 -44
- package/framework/stacks/react/tech.md +219 -30
- package/framework/stacks/react/testing.md +690 -0
- package/package.json +1 -1
- package/framework/stacks/node/product.md +0 -27
- package/framework/stacks/node/structure.md +0 -82
- package/framework/stacks/node/tech.md +0 -63
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# Code Refactoring Patterns
|
|
2
|
+
|
|
3
|
+
Project memory for safe code refactoring strategies in Python.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Refactoring Philosophy
|
|
8
|
+
|
|
9
|
+
1. **Safety first**: Tests pass before and after every change
|
|
10
|
+
2. **Small steps**: Incremental changes that can be reverted independently
|
|
11
|
+
3. **Preserve behavior**: Refactoring changes structure, not functionality
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Extraction Patterns
|
|
16
|
+
|
|
17
|
+
### Service Extraction
|
|
18
|
+
|
|
19
|
+
Extract business logic from route handlers when it spans multiple concerns:
|
|
20
|
+
|
|
21
|
+
**Pattern**: Route Handler to Service
|
|
22
|
+
```python
|
|
23
|
+
# Before: Fat route handler
|
|
24
|
+
@router.post("/users/{user_id}/content")
|
|
25
|
+
async def create_content(
|
|
26
|
+
user_id: int,
|
|
27
|
+
data: CreateContentRequest,
|
|
28
|
+
db: AsyncSession = Depends(get_db),
|
|
29
|
+
):
|
|
30
|
+
user = await db.get(User, user_id)
|
|
31
|
+
if not user:
|
|
32
|
+
raise HTTPException(404)
|
|
33
|
+
content = Content(title=data.title, body=data.body, user_id=user.id)
|
|
34
|
+
db.add(content)
|
|
35
|
+
await db.commit()
|
|
36
|
+
await notify_subscribers(user, content)
|
|
37
|
+
await update_search_index(content)
|
|
38
|
+
return ContentResponse.model_validate(content)
|
|
39
|
+
|
|
40
|
+
# After: Thin handler, extracted service
|
|
41
|
+
@router.post("/users/{user_id}/content")
|
|
42
|
+
async def create_content(
|
|
43
|
+
user_id: int,
|
|
44
|
+
data: CreateContentRequest,
|
|
45
|
+
service: ContentService = Depends(get_content_service),
|
|
46
|
+
):
|
|
47
|
+
result = await service.create(user_id=user_id, data=data)
|
|
48
|
+
if not result.is_ok:
|
|
49
|
+
raise HTTPException(400, detail=result.error)
|
|
50
|
+
return ContentResponse.model_validate(result.value)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Protocol Extraction
|
|
54
|
+
|
|
55
|
+
Extract shared behavior into protocols when multiple implementations exist:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# Before: Concrete dependency
|
|
59
|
+
class ContentService:
|
|
60
|
+
def __init__(self, db: AsyncSession):
|
|
61
|
+
self._db = db
|
|
62
|
+
|
|
63
|
+
async def get(self, id: int) -> Content:
|
|
64
|
+
return await self._db.get(Content, id)
|
|
65
|
+
|
|
66
|
+
# After: Protocol-based abstraction
|
|
67
|
+
from typing import Protocol
|
|
68
|
+
|
|
69
|
+
class ContentRepository(Protocol):
|
|
70
|
+
async def get(self, id: int) -> Content | None: ...
|
|
71
|
+
async def save(self, content: Content) -> Content: ...
|
|
72
|
+
|
|
73
|
+
class SQLContentRepository:
|
|
74
|
+
def __init__(self, db: AsyncSession) -> None:
|
|
75
|
+
self._db = db
|
|
76
|
+
|
|
77
|
+
async def get(self, id: int) -> Content | None:
|
|
78
|
+
return await self._db.get(Content, id)
|
|
79
|
+
|
|
80
|
+
async def save(self, content: Content) -> Content:
|
|
81
|
+
self._db.add(content)
|
|
82
|
+
await self._db.flush()
|
|
83
|
+
return content
|
|
84
|
+
|
|
85
|
+
class ContentService:
|
|
86
|
+
def __init__(self, repo: ContentRepository) -> None:
|
|
87
|
+
self._repo = repo
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Strategy Pattern
|
|
91
|
+
|
|
92
|
+
Use when type-based conditionals grow:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# Before: Growing if/elif chain
|
|
96
|
+
def process_source(source: Source) -> str:
|
|
97
|
+
if source.type == "youtube":
|
|
98
|
+
# 30 lines
|
|
99
|
+
elif source.type == "web":
|
|
100
|
+
# 25 lines
|
|
101
|
+
elif source.type == "rss":
|
|
102
|
+
# 20 lines
|
|
103
|
+
|
|
104
|
+
# After: Strategy registry
|
|
105
|
+
from typing import Protocol
|
|
106
|
+
|
|
107
|
+
class Extractor(Protocol):
|
|
108
|
+
async def extract(self, source: Source) -> ExtractedContent: ...
|
|
109
|
+
|
|
110
|
+
EXTRACTORS: dict[str, type[Extractor]] = {
|
|
111
|
+
"youtube": YoutubeExtractor,
|
|
112
|
+
"web": WebExtractor,
|
|
113
|
+
"rss": RssExtractor,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async def process_source(source: Source) -> ExtractedContent:
|
|
117
|
+
extractor_cls = EXTRACTORS.get(source.type)
|
|
118
|
+
if not extractor_cls:
|
|
119
|
+
raise ValueError(f"Unknown source type: {source.type}")
|
|
120
|
+
return await extractor_cls().extract(source)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Dependency Injection for Testability
|
|
126
|
+
|
|
127
|
+
### Constructor Injection
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
class NotificationService:
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
email_client: EmailClient | None = None,
|
|
134
|
+
event_bus: EventBus | None = None,
|
|
135
|
+
) -> None:
|
|
136
|
+
self._email = email_client or SmtpEmailClient()
|
|
137
|
+
self._events = event_bus or RedisEventBus()
|
|
138
|
+
|
|
139
|
+
# Production: uses defaults
|
|
140
|
+
service = NotificationService()
|
|
141
|
+
|
|
142
|
+
# Test: inject mocks
|
|
143
|
+
service = NotificationService(
|
|
144
|
+
email_client=FakeEmailClient(),
|
|
145
|
+
event_bus=FakeEventBus(),
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### FastAPI Dependency Overrides
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# In tests: override dependencies
|
|
153
|
+
from app.dependencies import get_user_service
|
|
154
|
+
|
|
155
|
+
app.dependency_overrides[get_user_service] = lambda: mock_service
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Pydantic Model Refactoring
|
|
161
|
+
|
|
162
|
+
### Flatten Nested Logic into Models
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# Before: Validation scattered in service
|
|
166
|
+
class ContentService:
|
|
167
|
+
async def create(self, title: str, body: str, tags: list[str]):
|
|
168
|
+
if not title.strip():
|
|
169
|
+
raise ValueError("Title required")
|
|
170
|
+
if len(tags) > 10:
|
|
171
|
+
raise ValueError("Too many tags")
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
# After: Validation in Pydantic model
|
|
175
|
+
class CreateContentRequest(BaseModel):
|
|
176
|
+
title: str = Field(..., min_length=1, max_length=200)
|
|
177
|
+
body: str = Field(default="")
|
|
178
|
+
tags: list[str] = Field(default_factory=list, max_length=10)
|
|
179
|
+
|
|
180
|
+
@field_validator("title")
|
|
181
|
+
@classmethod
|
|
182
|
+
def strip_title(cls, v: str) -> str:
|
|
183
|
+
return v.strip()
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Extract Shared Fields with Base Models
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
class TimestampMixin(BaseModel):
|
|
190
|
+
created_at: datetime
|
|
191
|
+
updated_at: datetime
|
|
192
|
+
|
|
193
|
+
class UserResponse(TimestampMixin):
|
|
194
|
+
id: int
|
|
195
|
+
email: str
|
|
196
|
+
name: str
|
|
197
|
+
|
|
198
|
+
class ContentResponse(TimestampMixin):
|
|
199
|
+
id: int
|
|
200
|
+
title: str
|
|
201
|
+
status: str
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Safe Refactoring Workflow
|
|
207
|
+
|
|
208
|
+
### Step 1: Ensure Test Coverage
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Check coverage for the module being refactored
|
|
212
|
+
uv run pytest tests/unit/services/test_content_service.py --cov=src/app/services/content_service
|
|
213
|
+
|
|
214
|
+
# Add characterization tests if missing
|
|
215
|
+
def test_current_create_behavior():
|
|
216
|
+
"""Document existing behavior before refactoring."""
|
|
217
|
+
...
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Step 2: Small, Reversible Changes
|
|
221
|
+
|
|
222
|
+
**Good**: One concept per commit
|
|
223
|
+
```
|
|
224
|
+
commit 1: Extract ContentRepository protocol
|
|
225
|
+
commit 2: Implement SQLContentRepository
|
|
226
|
+
commit 3: Update ContentService to use repository
|
|
227
|
+
commit 4: Update dependency injection wiring
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Step 3: Verify After Each Change
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
uv run pytest tests/ -x # Stop on first failure
|
|
234
|
+
uv run mypy src/ # Type safety preserved
|
|
235
|
+
uv run ruff check . # Style maintained
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Refactoring Signals
|
|
241
|
+
|
|
242
|
+
### When to Extract
|
|
243
|
+
|
|
244
|
+
| Signal | Action |
|
|
245
|
+
|--------|--------|
|
|
246
|
+
| Route handler > 15 lines | Extract to service |
|
|
247
|
+
| Service > 200 lines | Split by responsibility |
|
|
248
|
+
| if/elif > 3 branches on type | Strategy pattern |
|
|
249
|
+
| Same 5+ lines in multiple places | Extract to utility/mixin |
|
|
250
|
+
| External API call in service | Extract to client class |
|
|
251
|
+
| Complex query logic | Extract to repository |
|
|
252
|
+
|
|
253
|
+
### When NOT to Extract
|
|
254
|
+
|
|
255
|
+
- Single-use logic that fits naturally in one place
|
|
256
|
+
- Simple CRUD without business rules
|
|
257
|
+
- Premature abstraction (wait for 3+ uses)
|
|
258
|
+
- Adding layers just for the sake of layers
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Common Refactoring Targets
|
|
263
|
+
|
|
264
|
+
### Fat Services
|
|
265
|
+
|
|
266
|
+
Split by responsibility:
|
|
267
|
+
```
|
|
268
|
+
content_service.py (500 lines) ->
|
|
269
|
+
content_service.py # CRUD operations
|
|
270
|
+
content_publisher.py # Publishing logic
|
|
271
|
+
content_enricher.py # AI enrichment
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Complex Conditionals
|
|
275
|
+
|
|
276
|
+
Replace with:
|
|
277
|
+
- Guard clauses (early returns)
|
|
278
|
+
- Strategy/registry patterns
|
|
279
|
+
- `match` statements (Python 3.10+)
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
# Pattern: match statement for clean branching
|
|
283
|
+
match source.type:
|
|
284
|
+
case "youtube":
|
|
285
|
+
return await extract_youtube(source)
|
|
286
|
+
case "web":
|
|
287
|
+
return await extract_web(source)
|
|
288
|
+
case _:
|
|
289
|
+
raise ValueError(f"Unknown type: {source.type}")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Refactoring Checklist
|
|
295
|
+
|
|
296
|
+
Before starting:
|
|
297
|
+
- [ ] Tests pass for affected code
|
|
298
|
+
- [ ] Understand current behavior fully
|
|
299
|
+
- [ ] Plan incremental steps
|
|
300
|
+
|
|
301
|
+
During:
|
|
302
|
+
- [ ] One logical change per commit
|
|
303
|
+
- [ ] Tests pass after each step
|
|
304
|
+
- [ ] Types check after each step
|
|
305
|
+
- [ ] No behavior changes (unless intentional)
|
|
306
|
+
|
|
307
|
+
After:
|
|
308
|
+
- [ ] Full test suite passes
|
|
309
|
+
- [ ] mypy passes
|
|
310
|
+
- [ ] Ruff passes
|
|
311
|
+
- [ ] Manual smoke test of affected features
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
_Refactoring improves code structure while preserving behavior. Small, typed, tested steps over large rewrites._
|