claude-dev-env 1.34.0 → 1.35.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/agents/docs-agent.md +1 -1
- package/agents/project-docs-analyzer.md +0 -1
- package/agents/skill-to-agent-converter.md +0 -1
- package/commands/initialize.md +0 -1
- package/commands/readability-review.md +4 -4
- package/commands/review-plan.md +2 -4
- package/commands/stubcheck.md +1 -2
- package/hooks/blocking/code_rules_enforcer.py +250 -36
- package/hooks/blocking/test_code_rules_enforcer.py +91 -39
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +97 -0
- package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +137 -0
- package/hooks/blocking/test_code_rules_enforcer_config_path.py +0 -20
- package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +0 -18
- package/hooks/blocking/test_code_rules_enforcer_existence_checks.py +0 -18
- package/hooks/blocking/test_code_rules_enforcer_inline_literal_collections.py +155 -0
- package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +110 -0
- package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +0 -13
- package/hooks/blocking/test_code_rules_enforcer_skip_decorators.py +0 -26
- package/hooks/blocking/test_code_rules_enforcer_string_magic.py +234 -0
- package/package.json +1 -1
- package/skills/bugteam/PROMPTS.md +0 -39
- package/skills/bugteam/SKILL.md +17 -35
- package/skills/bugteam/reference/copilot-gap-analysis.md +12 -0
- package/skills/pr-converge/SKILL.md +185 -167
- package/agents/agent-writer.md +0 -157
- package/agents/config-centralizer.md +0 -686
- package/agents/config-extraction-agent.md +0 -225
- package/agents/doc-orchestrator.md +0 -47
- package/agents/docx-agent.md +0 -211
- package/agents/magic-value-eliminator-agent.md +0 -72
- package/agents/mandatory-agent-workflow-agent.md +0 -88
- package/agents/parallel-workflow-coordinator.md +0 -779
- package/agents/pdf-agent.md +0 -302
- package/agents/project-context-loader.md +0 -238
- package/agents/readability-review-agent.md +0 -76
- package/agents/refactoring-specialist.md +0 -69
- package/agents/right-sized-engineer.md +0 -129
- package/agents/session-continuity-manager.md +0 -53
- package/agents/stub-detector-agent.md +0 -140
- package/agents/tdd-test-writer.md +0 -62
- package/agents/test-data-builder.md +0 -68
- package/agents/tooling-builder.md +0 -78
- package/agents/validation-expert.md +0 -71
- package/agents/xlsx-agent.md +0 -169
|
@@ -1,686 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: config-centralizer
|
|
3
|
-
description: Eliminate scattered configuration and hardcoded values by extracting to centralized frozen dataclass configs. Use when refactoring projects with scattered config or magic values.
|
|
4
|
-
tools: Read, Write, Edit, Glob, Grep, Skill
|
|
5
|
-
model: sonnet
|
|
6
|
-
color: red
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# config-centralizer
|
|
10
|
-
|
|
11
|
-
## Purpose
|
|
12
|
-
|
|
13
|
-
Eliminate scattered configuration and hardcoded values by extracting them to centralized frozen dataclass configs. Ensures type-safe, immutable configuration with environment overlay support.
|
|
14
|
-
|
|
15
|
-
## When to Use
|
|
16
|
-
|
|
17
|
-
- Project has scattered configuration across multiple files
|
|
18
|
-
- Hardcoded values (URLs, timeouts, ports, magic numbers) throughout codebase
|
|
19
|
-
- Environment variables used inconsistently
|
|
20
|
-
- Need to centralize settings for maintainability
|
|
21
|
-
- Preparing code for deployment with environment-specific configs
|
|
22
|
-
- Improving code quality by eliminating magic values
|
|
23
|
-
|
|
24
|
-
## Invokes Skills
|
|
25
|
-
|
|
26
|
-
- **code-standards**: Enforces no magic values, immutability, type safety
|
|
27
|
-
- **magic-value-eliminator**: Extracts hardcoded values systematically
|
|
28
|
-
|
|
29
|
-
## Process
|
|
30
|
-
|
|
31
|
-
### 1. Scan for Configuration
|
|
32
|
-
|
|
33
|
-
**Identify scattered config:**
|
|
34
|
-
- Hardcoded values (URLs, ports, timeouts, API keys)
|
|
35
|
-
- Environment variables (os.getenv, os.environ)
|
|
36
|
-
- Module-level constants scattered across files
|
|
37
|
-
- Settings dictionaries
|
|
38
|
-
- JSON/YAML config files
|
|
39
|
-
|
|
40
|
-
**Search patterns:**
|
|
41
|
-
```python
|
|
42
|
-
# Hardcoded URLs
|
|
43
|
-
r'https?://[^\s"\']+'
|
|
44
|
-
|
|
45
|
-
# Numeric constants (potential magic values)
|
|
46
|
-
r'\b\d+\.\d+\b' # Decimals
|
|
47
|
-
r'\b\d{2,}\b' # Multi-digit integers
|
|
48
|
-
|
|
49
|
-
# Environment variables
|
|
50
|
-
r'os\.getenv|os\.environ'
|
|
51
|
-
|
|
52
|
-
# Common config patterns
|
|
53
|
-
r'TIMEOUT|DELAY|PORT|HOST|API_KEY|SECRET'
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Tools:**
|
|
57
|
-
- Grep for patterns across codebase
|
|
58
|
-
- Glob to find config files (*.json, *.yaml, *.ini)
|
|
59
|
-
- Read to analyze existing configuration
|
|
60
|
-
|
|
61
|
-
### 2. Generate Frozen Dataclass Config
|
|
62
|
-
|
|
63
|
-
**Structure:**
|
|
64
|
-
```python
|
|
65
|
-
from dataclasses import dataclass
|
|
66
|
-
from typing import Literal
|
|
67
|
-
|
|
68
|
-
@dataclass(frozen=True)
|
|
69
|
-
class DatabaseConfig:
|
|
70
|
-
host: str
|
|
71
|
-
port: int
|
|
72
|
-
database: str
|
|
73
|
-
timeout_seconds: int
|
|
74
|
-
|
|
75
|
-
@dataclass(frozen=True)
|
|
76
|
-
class APIConfig:
|
|
77
|
-
base_url: str
|
|
78
|
-
timeout_seconds: int
|
|
79
|
-
max_retries: int
|
|
80
|
-
rate_limit_per_minute: int
|
|
81
|
-
|
|
82
|
-
@dataclass(frozen=True)
|
|
83
|
-
class AppConfig:
|
|
84
|
-
database: DatabaseConfig
|
|
85
|
-
api: APIConfig
|
|
86
|
-
environment: Literal["dev", "staging", "prod"]
|
|
87
|
-
debug: bool
|
|
88
|
-
|
|
89
|
-
# Default configuration
|
|
90
|
-
DEFAULT_CONFIG = AppConfig(
|
|
91
|
-
database=DatabaseConfig(
|
|
92
|
-
host="localhost",
|
|
93
|
-
port=5432,
|
|
94
|
-
database="app_db",
|
|
95
|
-
timeout_seconds=30
|
|
96
|
-
),
|
|
97
|
-
api=APIConfig(
|
|
98
|
-
base_url="https://api.example.com",
|
|
99
|
-
timeout_seconds=10,
|
|
100
|
-
max_retries=3,
|
|
101
|
-
rate_limit_per_minute=60
|
|
102
|
-
),
|
|
103
|
-
environment="dev",
|
|
104
|
-
debug=True
|
|
105
|
-
)
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**Principles:**
|
|
109
|
-
- Frozen dataclasses (immutable)
|
|
110
|
-
- Type-safe (no Any types)
|
|
111
|
-
- Nested configs for organization
|
|
112
|
-
- Literal types for enums
|
|
113
|
-
- Sensible defaults
|
|
114
|
-
|
|
115
|
-
### 3. Migrate Code to Use Centralized Config
|
|
116
|
-
|
|
117
|
-
**Before:**
|
|
118
|
-
```python
|
|
119
|
-
import os
|
|
120
|
-
import requests
|
|
121
|
-
|
|
122
|
-
def fetch_data():
|
|
123
|
-
url = "https://api.example.com/data" # Hardcoded
|
|
124
|
-
timeout = 10 # Magic value
|
|
125
|
-
response = requests.get(url, timeout=timeout)
|
|
126
|
-
return response.json()
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**After:**
|
|
130
|
-
```python
|
|
131
|
-
from config import DEFAULT_CONFIG
|
|
132
|
-
|
|
133
|
-
def fetch_data():
|
|
134
|
-
response = requests.get(
|
|
135
|
-
f"{DEFAULT_CONFIG.api.base_url}/data",
|
|
136
|
-
timeout=DEFAULT_CONFIG.api.timeout_seconds
|
|
137
|
-
)
|
|
138
|
-
return response.json()
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
**Migration checklist:**
|
|
142
|
-
- Replace hardcoded values with config access
|
|
143
|
-
- Update all references systematically
|
|
144
|
-
- Maintain existing behavior (verify equivalence)
|
|
145
|
-
- Add type hints where missing
|
|
146
|
-
|
|
147
|
-
### 4. Type Safety
|
|
148
|
-
|
|
149
|
-
**Type-safe config access:**
|
|
150
|
-
```python
|
|
151
|
-
# IDE autocomplete works
|
|
152
|
-
config.api.base_url # Type: str
|
|
153
|
-
config.api.timeout_seconds # Type: int
|
|
154
|
-
|
|
155
|
-
# Type checker catches errors
|
|
156
|
-
config.api.timeout_seconds = "10" # Error: frozen dataclass
|
|
157
|
-
config.api.invalid_field # Error: no such attribute
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
**Benefits:**
|
|
161
|
-
- IDE autocomplete
|
|
162
|
-
- Type checker validation
|
|
163
|
-
- Runtime immutability
|
|
164
|
-
- Self-documenting
|
|
165
|
-
|
|
166
|
-
### 5. Environment Overlays
|
|
167
|
-
|
|
168
|
-
**Structure:**
|
|
169
|
-
```python
|
|
170
|
-
# config.py - base configuration
|
|
171
|
-
from dataclasses import dataclass, replace
|
|
172
|
-
|
|
173
|
-
@dataclass(frozen=True)
|
|
174
|
-
class AppConfig:
|
|
175
|
-
# ... fields ...
|
|
176
|
-
|
|
177
|
-
DEFAULT_CONFIG = AppConfig(...)
|
|
178
|
-
|
|
179
|
-
# Environment-specific overlays
|
|
180
|
-
DEV_CONFIG = DEFAULT_CONFIG # Use defaults
|
|
181
|
-
|
|
182
|
-
STAGING_CONFIG = replace(
|
|
183
|
-
DEFAULT_CONFIG,
|
|
184
|
-
environment="staging",
|
|
185
|
-
debug=False,
|
|
186
|
-
database=replace(DEFAULT_CONFIG.database, host="staging.db.example.com")
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
PROD_CONFIG = replace(
|
|
190
|
-
DEFAULT_CONFIG,
|
|
191
|
-
environment="prod",
|
|
192
|
-
debug=False,
|
|
193
|
-
database=replace(DEFAULT_CONFIG.database, host="prod.db.example.com"),
|
|
194
|
-
api=replace(DEFAULT_CONFIG.api, base_url="https://api.prod.example.com")
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
# Load based on environment
|
|
198
|
-
import os
|
|
199
|
-
ENV = os.getenv("APP_ENV", "dev")
|
|
200
|
-
|
|
201
|
-
if ENV == "staging":
|
|
202
|
-
CONFIG = STAGING_CONFIG
|
|
203
|
-
elif ENV == "prod":
|
|
204
|
-
CONFIG = PROD_CONFIG
|
|
205
|
-
else:
|
|
206
|
-
CONFIG = DEV_CONFIG
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
**Usage:**
|
|
210
|
-
```python
|
|
211
|
-
from config import CONFIG
|
|
212
|
-
|
|
213
|
-
# Automatically uses correct environment
|
|
214
|
-
response = requests.get(CONFIG.api.base_url)
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## Input
|
|
218
|
-
|
|
219
|
-
- **Project directory**: Root directory to scan
|
|
220
|
-
- **File list**: Specific files to analyze (optional)
|
|
221
|
-
- **Exclusions**: Files/directories to skip (tests, venv, etc.)
|
|
222
|
-
|
|
223
|
-
## Output
|
|
224
|
-
|
|
225
|
-
### Generated Files
|
|
226
|
-
|
|
227
|
-
**config.py** (150-300 lines):
|
|
228
|
-
- Frozen dataclass definitions
|
|
229
|
-
- Default configuration
|
|
230
|
-
- Environment overlays
|
|
231
|
-
- Config loading logic
|
|
232
|
-
|
|
233
|
-
**Example:**
|
|
234
|
-
```python
|
|
235
|
-
# config.py
|
|
236
|
-
from dataclasses import dataclass, replace
|
|
237
|
-
from typing import Literal
|
|
238
|
-
import os
|
|
239
|
-
|
|
240
|
-
@dataclass(frozen=True)
|
|
241
|
-
class WebAutomationConfig:
|
|
242
|
-
selenium_timeout_seconds: int
|
|
243
|
-
page_load_timeout_seconds: int
|
|
244
|
-
implicit_wait_seconds: int
|
|
245
|
-
max_retries: int
|
|
246
|
-
screenshot_on_failure: bool
|
|
247
|
-
|
|
248
|
-
@dataclass(frozen=True)
|
|
249
|
-
class ServiceConfig:
|
|
250
|
-
base_url: str
|
|
251
|
-
login_timeout_seconds: int
|
|
252
|
-
navigation_delay_seconds: float
|
|
253
|
-
|
|
254
|
-
@dataclass(frozen=True)
|
|
255
|
-
class AppConfig:
|
|
256
|
-
web_automation: WebAutomationConfig
|
|
257
|
-
service: ServiceConfig
|
|
258
|
-
environment: Literal["dev", "prod"]
|
|
259
|
-
debug: bool
|
|
260
|
-
|
|
261
|
-
DEFAULT_CONFIG = AppConfig(
|
|
262
|
-
web_automation=WebAutomationConfig(
|
|
263
|
-
selenium_timeout_seconds=30,
|
|
264
|
-
page_load_timeout_seconds=60,
|
|
265
|
-
implicit_wait_seconds=10,
|
|
266
|
-
max_retries=3,
|
|
267
|
-
screenshot_on_failure=True
|
|
268
|
-
),
|
|
269
|
-
service=ServiceConfig(
|
|
270
|
-
base_url="https://api.example.com",
|
|
271
|
-
login_timeout_seconds=30,
|
|
272
|
-
navigation_delay_seconds=0.5
|
|
273
|
-
),
|
|
274
|
-
environment="dev",
|
|
275
|
-
debug=True
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
PROD_CONFIG = replace(
|
|
279
|
-
DEFAULT_CONFIG,
|
|
280
|
-
environment="prod",
|
|
281
|
-
debug=False,
|
|
282
|
-
web_automation=replace(DEFAULT_CONFIG.web_automation, screenshot_on_failure=False)
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
ENV = os.getenv("APP_ENV", "dev")
|
|
286
|
-
CONFIG = PROD_CONFIG if ENV == "prod" else DEFAULT_CONFIG
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### Migration Report
|
|
290
|
-
|
|
291
|
-
**MIGRATION_REPORT.md**:
|
|
292
|
-
- Files scanned
|
|
293
|
-
- Hardcoded values found
|
|
294
|
-
- Values extracted to config
|
|
295
|
-
- Breaking changes (if any)
|
|
296
|
-
- Verification checklist
|
|
297
|
-
|
|
298
|
-
**Example:**
|
|
299
|
-
```markdown
|
|
300
|
-
# Config Centralization Migration Report
|
|
301
|
-
|
|
302
|
-
## Summary
|
|
303
|
-
- Files scanned: 23
|
|
304
|
-
- Hardcoded values found: 47
|
|
305
|
-
- Values extracted: 47
|
|
306
|
-
- Breaking changes: 0
|
|
307
|
-
|
|
308
|
-
## Extracted Values
|
|
309
|
-
|
|
310
|
-
### URLs (8)
|
|
311
|
-
- `https://api.example.com` → CONFIG.service.base_url
|
|
312
|
-
- `https://api.example.com/v2` → CONFIG.api.base_url
|
|
313
|
-
- ... (6 more)
|
|
314
|
-
|
|
315
|
-
### Timeouts (12)
|
|
316
|
-
- `30` (selenium timeout) → CONFIG.web_automation.selenium_timeout_seconds
|
|
317
|
-
- `60` (page load) → CONFIG.web_automation.page_load_timeout_seconds
|
|
318
|
-
- ... (10 more)
|
|
319
|
-
|
|
320
|
-
### Ports (3)
|
|
321
|
-
- `5432` (database) → CONFIG.database.port
|
|
322
|
-
- ... (2 more)
|
|
323
|
-
|
|
324
|
-
## Modified Files
|
|
325
|
-
|
|
326
|
-
1. `automation/orchestrator.py` (5 values extracted)
|
|
327
|
-
2. `automation/services/browser_service.py` (8 values extracted)
|
|
328
|
-
3. ... (21 more)
|
|
329
|
-
|
|
330
|
-
## Verification Checklist
|
|
331
|
-
|
|
332
|
-
- [ ] All tests pass
|
|
333
|
-
- [ ] Dev environment works
|
|
334
|
-
- [ ] Prod config reviewed
|
|
335
|
-
- [ ] No hardcoded values remain (run grep)
|
|
336
|
-
- [ ] Type checker passes (mypy)
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
## Examples
|
|
340
|
-
|
|
341
|
-
### Example 1: Web Automation Package
|
|
342
|
-
|
|
343
|
-
**Before:**
|
|
344
|
-
```python
|
|
345
|
-
# Scattered across multiple files
|
|
346
|
-
from playwright.sync_api import Page
|
|
347
|
-
|
|
348
|
-
class BrowserService:
|
|
349
|
-
def __init__(self, page: Page):
|
|
350
|
-
self.page = page
|
|
351
|
-
self.page.set_default_timeout(30000) # Magic value
|
|
352
|
-
|
|
353
|
-
def navigate(self, path: str):
|
|
354
|
-
base_url = "https://api.example.com" # Hardcoded
|
|
355
|
-
self.page.goto(f"{base_url}{path}")
|
|
356
|
-
self.page.wait_for_load_state("networkidle", timeout=60000) # Magic value
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
**After:**
|
|
360
|
-
```python
|
|
361
|
-
# config.py
|
|
362
|
-
from dataclasses import dataclass
|
|
363
|
-
|
|
364
|
-
@dataclass(frozen=True)
|
|
365
|
-
class BrowserConfig:
|
|
366
|
-
default_timeout_ms: int
|
|
367
|
-
page_load_timeout_ms: int
|
|
368
|
-
base_url: str
|
|
369
|
-
|
|
370
|
-
@dataclass(frozen=True)
|
|
371
|
-
class AppConfig:
|
|
372
|
-
browser: BrowserConfig
|
|
373
|
-
|
|
374
|
-
CONFIG = AppConfig(
|
|
375
|
-
browser=BrowserConfig(
|
|
376
|
-
default_timeout_ms=30000,
|
|
377
|
-
page_load_timeout_ms=60000,
|
|
378
|
-
base_url="https://api.example.com"
|
|
379
|
-
)
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
# browser_service.py
|
|
383
|
-
from playwright.sync_api import Page
|
|
384
|
-
from config import CONFIG
|
|
385
|
-
|
|
386
|
-
class BrowserService:
|
|
387
|
-
def __init__(self, page: Page):
|
|
388
|
-
self.page = page
|
|
389
|
-
self.page.set_default_timeout(CONFIG.browser.default_timeout_ms)
|
|
390
|
-
|
|
391
|
-
def navigate(self, path: str):
|
|
392
|
-
self.page.goto(f"{CONFIG.browser.base_url}{path}")
|
|
393
|
-
self.page.wait_for_load_state(
|
|
394
|
-
"networkidle",
|
|
395
|
-
timeout=CONFIG.browser.page_load_timeout_ms
|
|
396
|
-
)
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### Example 2: Django Settings Extraction
|
|
400
|
-
|
|
401
|
-
**Before:**
|
|
402
|
-
```python
|
|
403
|
-
# settings.py (scattered)
|
|
404
|
-
DATABASE_HOST = "localhost"
|
|
405
|
-
DATABASE_PORT = 5432
|
|
406
|
-
API_TIMEOUT = 10
|
|
407
|
-
CACHE_TTL = 3600
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
**After:**
|
|
411
|
-
```python
|
|
412
|
-
# config.py
|
|
413
|
-
from dataclasses import dataclass
|
|
414
|
-
|
|
415
|
-
@dataclass(frozen=True)
|
|
416
|
-
class DatabaseConfig:
|
|
417
|
-
host: str
|
|
418
|
-
port: int
|
|
419
|
-
|
|
420
|
-
@dataclass(frozen=True)
|
|
421
|
-
class APIConfig:
|
|
422
|
-
timeout_seconds: int
|
|
423
|
-
|
|
424
|
-
@dataclass(frozen=True)
|
|
425
|
-
class CacheConfig:
|
|
426
|
-
ttl_seconds: int
|
|
427
|
-
|
|
428
|
-
@dataclass(frozen=True)
|
|
429
|
-
class AppConfig:
|
|
430
|
-
database: DatabaseConfig
|
|
431
|
-
api: APIConfig
|
|
432
|
-
cache: CacheConfig
|
|
433
|
-
|
|
434
|
-
CONFIG = AppConfig(
|
|
435
|
-
database=DatabaseConfig(host="localhost", port=5432),
|
|
436
|
-
api=APIConfig(timeout_seconds=10),
|
|
437
|
-
cache=CacheConfig(ttl_seconds=3600)
|
|
438
|
-
)
|
|
439
|
-
|
|
440
|
-
# settings.py
|
|
441
|
-
from config import CONFIG
|
|
442
|
-
|
|
443
|
-
DATABASE_HOST = CONFIG.database.host
|
|
444
|
-
DATABASE_PORT = CONFIG.database.port
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
## Testing Strategy
|
|
448
|
-
|
|
449
|
-
### Test Config Access
|
|
450
|
-
|
|
451
|
-
```python
|
|
452
|
-
# test_config.py
|
|
453
|
-
from config import DEFAULT_CONFIG, PROD_CONFIG
|
|
454
|
-
|
|
455
|
-
def test_default_config_immutable():
|
|
456
|
-
"""Config should be immutable."""
|
|
457
|
-
with pytest.raises(FrozenInstanceError):
|
|
458
|
-
DEFAULT_CONFIG.debug = False
|
|
459
|
-
|
|
460
|
-
def test_config_types():
|
|
461
|
-
"""Config fields should have correct types."""
|
|
462
|
-
assert isinstance(DEFAULT_CONFIG.api.timeout_seconds, int)
|
|
463
|
-
assert isinstance(DEFAULT_CONFIG.api.base_url, str)
|
|
464
|
-
|
|
465
|
-
def test_prod_config_differences():
|
|
466
|
-
"""Prod config should differ from default where expected."""
|
|
467
|
-
assert DEFAULT_CONFIG.debug is True
|
|
468
|
-
assert PROD_CONFIG.debug is False
|
|
469
|
-
assert DEFAULT_CONFIG.environment == "dev"
|
|
470
|
-
assert PROD_CONFIG.environment == "prod"
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### Test Migration
|
|
474
|
-
|
|
475
|
-
```python
|
|
476
|
-
# test_migration.py
|
|
477
|
-
import subprocess
|
|
478
|
-
|
|
479
|
-
def test_no_hardcoded_urls():
|
|
480
|
-
"""No hardcoded URLs should remain after migration."""
|
|
481
|
-
result = subprocess.run(
|
|
482
|
-
["grep", "-r", "https://", "--include=*.py", "."],
|
|
483
|
-
capture_output=True,
|
|
484
|
-
text=True
|
|
485
|
-
)
|
|
486
|
-
# Should only find URLs in config.py and test fixtures
|
|
487
|
-
assert "config.py" in result.stdout or result.returncode != 0
|
|
488
|
-
|
|
489
|
-
def test_all_tests_pass():
|
|
490
|
-
"""All existing tests should pass after migration."""
|
|
491
|
-
result = subprocess.run(["pytest"], capture_output=True)
|
|
492
|
-
assert result.returncode == 0
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
## Success Criteria
|
|
496
|
-
|
|
497
|
-
### Code Quality
|
|
498
|
-
- [ ] All hardcoded values extracted to config
|
|
499
|
-
- [ ] Config is type-safe (no Any types)
|
|
500
|
-
- [ ] Config is immutable (frozen dataclasses)
|
|
501
|
-
- [ ] No magic values remain in codebase
|
|
502
|
-
- [ ] Type checker passes (mypy)
|
|
503
|
-
|
|
504
|
-
### Functionality
|
|
505
|
-
- [ ] All existing tests pass
|
|
506
|
-
- [ ] Application behavior unchanged
|
|
507
|
-
- [ ] Environment overlays work correctly
|
|
508
|
-
- [ ] Config access is ergonomic
|
|
509
|
-
|
|
510
|
-
### Documentation
|
|
511
|
-
- [ ] Migration report generated
|
|
512
|
-
- [ ] Config structure documented
|
|
513
|
-
- [ ] Environment setup instructions
|
|
514
|
-
- [ ] Rollback plan documented
|
|
515
|
-
|
|
516
|
-
## Common Patterns
|
|
517
|
-
|
|
518
|
-
### Pattern 1: API Configuration
|
|
519
|
-
|
|
520
|
-
```python
|
|
521
|
-
@dataclass(frozen=True)
|
|
522
|
-
class APIConfig:
|
|
523
|
-
base_url: str
|
|
524
|
-
timeout_seconds: int
|
|
525
|
-
max_retries: int
|
|
526
|
-
rate_limit_per_minute: int
|
|
527
|
-
headers: tuple[tuple[str, str], ...] # Immutable headers
|
|
528
|
-
|
|
529
|
-
def get_headers_dict(self) -> dict[str, str]:
|
|
530
|
-
return dict(self.headers)
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
### Pattern 2: Database Configuration
|
|
534
|
-
|
|
535
|
-
```python
|
|
536
|
-
@dataclass(frozen=True)
|
|
537
|
-
class DatabaseConfig:
|
|
538
|
-
host: str
|
|
539
|
-
port: int
|
|
540
|
-
database: str
|
|
541
|
-
username: str
|
|
542
|
-
password: str
|
|
543
|
-
pool_size: int
|
|
544
|
-
timeout_seconds: int
|
|
545
|
-
|
|
546
|
-
def get_connection_string(self) -> str:
|
|
547
|
-
return f"postgresql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}"
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
### Pattern 3: Feature Flags
|
|
551
|
-
|
|
552
|
-
```python
|
|
553
|
-
from typing import Literal
|
|
554
|
-
|
|
555
|
-
@dataclass(frozen=True)
|
|
556
|
-
class FeatureFlags:
|
|
557
|
-
new_ui_enabled: bool
|
|
558
|
-
experimental_feature: bool
|
|
559
|
-
maintenance_mode: bool
|
|
560
|
-
|
|
561
|
-
@dataclass(frozen=True)
|
|
562
|
-
class AppConfig:
|
|
563
|
-
features: FeatureFlags
|
|
564
|
-
environment: Literal["dev", "staging", "prod"]
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
## Integration with TDD
|
|
568
|
-
|
|
569
|
-
### Red: Write Failing Test
|
|
570
|
-
|
|
571
|
-
```python
|
|
572
|
-
def test_config_timeout_used():
|
|
573
|
-
"""Service should use config timeout, not hardcoded value."""
|
|
574
|
-
service = APIService()
|
|
575
|
-
assert service.timeout == CONFIG.api.timeout_seconds
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
### Green: Extract Hardcoded Value
|
|
579
|
-
|
|
580
|
-
```python
|
|
581
|
-
# Before
|
|
582
|
-
class APIService:
|
|
583
|
-
def __init__(self):
|
|
584
|
-
self.timeout = 10 # Hardcoded
|
|
585
|
-
|
|
586
|
-
# After
|
|
587
|
-
from config import CONFIG
|
|
588
|
-
|
|
589
|
-
class APIService:
|
|
590
|
-
def __init__(self):
|
|
591
|
-
self.timeout = CONFIG.api.timeout_seconds
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
### Refactor: Ensure No Magic Values Remain
|
|
595
|
-
|
|
596
|
-
```bash
|
|
597
|
-
# Search for potential magic values
|
|
598
|
-
grep -r "\b10\b" --include=*.py .
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
## Troubleshooting
|
|
602
|
-
|
|
603
|
-
### Issue: Circular Import
|
|
604
|
-
|
|
605
|
-
**Problem**: config.py imports modules that import config.py
|
|
606
|
-
|
|
607
|
-
**Solution**: Move config.py to top level, ensure no logic imports
|
|
608
|
-
|
|
609
|
-
```python
|
|
610
|
-
# config.py - ONLY dataclasses and constants
|
|
611
|
-
# NO imports of application code
|
|
612
|
-
from dataclasses import dataclass
|
|
613
|
-
|
|
614
|
-
@dataclass(frozen=True)
|
|
615
|
-
class Config:
|
|
616
|
-
# ...
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
### Issue: Need Mutable Config for Tests
|
|
620
|
-
|
|
621
|
-
**Problem**: Tests need to modify config
|
|
622
|
-
|
|
623
|
-
**Solution**: Use pytest fixtures with dataclass.replace
|
|
624
|
-
|
|
625
|
-
```python
|
|
626
|
-
import pytest
|
|
627
|
-
from dataclasses import replace
|
|
628
|
-
from config import DEFAULT_CONFIG
|
|
629
|
-
|
|
630
|
-
@pytest.fixture
|
|
631
|
-
def test_config():
|
|
632
|
-
return replace(
|
|
633
|
-
DEFAULT_CONFIG,
|
|
634
|
-
debug=True,
|
|
635
|
-
api=replace(DEFAULT_CONFIG.api, base_url="http://test.local")
|
|
636
|
-
)
|
|
637
|
-
|
|
638
|
-
def test_with_custom_config(test_config):
|
|
639
|
-
service = APIService(config=test_config)
|
|
640
|
-
assert service.base_url == "http://test.local"
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
### Issue: Environment Variables Not Loading
|
|
644
|
-
|
|
645
|
-
**Problem**: Environment variables ignored
|
|
646
|
-
|
|
647
|
-
**Solution**: Explicit environment variable loading
|
|
648
|
-
|
|
649
|
-
```python
|
|
650
|
-
import os
|
|
651
|
-
from dataclasses import dataclass, replace
|
|
652
|
-
|
|
653
|
-
@dataclass(frozen=True)
|
|
654
|
-
class Config:
|
|
655
|
-
api_key: str
|
|
656
|
-
|
|
657
|
-
def load_config() -> Config:
|
|
658
|
-
base = DEFAULT_CONFIG
|
|
659
|
-
|
|
660
|
-
# Override from environment
|
|
661
|
-
if api_key := os.getenv("API_KEY"):
|
|
662
|
-
base = replace(base, api_key=api_key)
|
|
663
|
-
|
|
664
|
-
return base
|
|
665
|
-
|
|
666
|
-
CONFIG = load_config()
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
## Code Standards Compliance
|
|
670
|
-
|
|
671
|
-
This agent enforces:
|
|
672
|
-
|
|
673
|
-
- **No magic values**: All configuration extracted
|
|
674
|
-
- **Type safety**: Frozen dataclasses with type hints
|
|
675
|
-
- **Immutability**: frozen=True on all config dataclasses
|
|
676
|
-
- **DRY**: Centralized config eliminates duplication
|
|
677
|
-
- **KISS**: Simple dataclass structure, no over-engineering
|
|
678
|
-
- **Small files**: config.py target 200-300 lines, split if larger
|
|
679
|
-
|
|
680
|
-
## Next Steps After Completion
|
|
681
|
-
|
|
682
|
-
1. **Verify**: Run all tests, ensure behavior unchanged
|
|
683
|
-
2. **Document**: Update README with config instructions
|
|
684
|
-
3. **Review**: Check for any remaining magic values
|
|
685
|
-
4. **Deploy**: Test with production config
|
|
686
|
-
5. **Monitor**: Ensure environment overlays work correctly
|