codeforge-dev 1.5.7 → 1.7.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 (80) hide show
  1. package/.devcontainer/.env +2 -1
  2. package/.devcontainer/CHANGELOG.md +55 -9
  3. package/.devcontainer/CLAUDE.md +65 -15
  4. package/.devcontainer/README.md +67 -6
  5. package/.devcontainer/config/keybindings.json +5 -0
  6. package/.devcontainer/config/main-system-prompt.md +63 -2
  7. package/.devcontainer/config/settings.json +25 -6
  8. package/.devcontainer/devcontainer.json +23 -7
  9. package/.devcontainer/features/README.md +21 -7
  10. package/.devcontainer/features/ccburn/README.md +60 -0
  11. package/.devcontainer/features/ccburn/devcontainer-feature.json +38 -0
  12. package/.devcontainer/features/ccburn/install.sh +174 -0
  13. package/.devcontainer/features/ccstatusline/README.md +22 -21
  14. package/.devcontainer/features/ccstatusline/devcontainer-feature.json +1 -1
  15. package/.devcontainer/features/ccstatusline/install.sh +48 -16
  16. package/.devcontainer/features/claude-code/config/settings.json +60 -24
  17. package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +1 -1
  18. package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +1 -1
  19. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/__pycache__/format-on-stop.cpython-314.pyc +0 -0
  20. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-on-stop.py +21 -6
  21. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/__pycache__/lint-file.cpython-314.pyc +0 -0
  22. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +7 -10
  23. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/REVIEW-RUBRIC.md +440 -0
  24. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/architect.md +190 -0
  25. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/bash-exec.md +173 -0
  26. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/claude-guide.md +155 -0
  27. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/dependency-analyst.md +248 -0
  28. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/doc-writer.md +233 -0
  29. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/explorer.md +235 -0
  30. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/generalist.md +125 -0
  31. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/git-archaeologist.md +242 -0
  32. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/migrator.md +195 -0
  33. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/perf-profiler.md +265 -0
  34. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/refactorer.md +209 -0
  35. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/researcher.md +195 -0
  36. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/security-auditor.md +289 -0
  37. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/spec-writer.md +284 -0
  38. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/statusline-config.md +188 -0
  39. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/agents/test-writer.md +245 -0
  40. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/hooks/hooks.json +12 -0
  41. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/guard-readonly-bash.cpython-314.pyc +0 -0
  42. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/redirect-builtin-agents.cpython-314.pyc +0 -0
  43. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/skill-suggester.cpython-314.pyc +0 -0
  44. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/syntax-validator.cpython-314.pyc +0 -0
  45. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/verify-no-regression.cpython-314.pyc +0 -0
  46. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/__pycache__/verify-tests-pass.cpython-314.pyc +0 -0
  47. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/guard-readonly-bash.py +611 -0
  48. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/redirect-builtin-agents.py +83 -0
  49. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/skill-suggester.py +85 -2
  50. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/syntax-validator.py +9 -4
  51. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/verify-no-regression.py +221 -0
  52. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/scripts/verify-tests-pass.py +176 -0
  53. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/claude-agent-sdk/SKILL.md +599 -0
  54. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/claude-agent-sdk/references/sdk-typescript-reference.md +954 -0
  55. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/SKILL.md +276 -0
  56. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/references/advanced-commands.md +332 -0
  57. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/git-forensics/references/investigation-playbooks.md +319 -0
  58. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/SKILL.md +341 -0
  59. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/references/interpreting-results.md +235 -0
  60. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/performance-profiling/references/tool-commands.md +395 -0
  61. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/SKILL.md +344 -0
  62. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/references/safe-transformations.md +247 -0
  63. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/refactoring-patterns/references/smell-catalog.md +332 -0
  64. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/SKILL.md +277 -0
  65. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/references/owasp-patterns.md +269 -0
  66. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/security-checklist/references/secrets-patterns.md +253 -0
  67. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/SKILL.md +288 -0
  68. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/references/criteria-patterns.md +245 -0
  69. package/.devcontainer/plugins/devs-marketplace/plugins/code-directive/skills/specification-writing/references/ears-templates.md +239 -0
  70. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/__pycache__/guard-protected.cpython-314.pyc +0 -0
  71. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +40 -39
  72. package/.devcontainer/scripts/setup-aliases.sh +10 -20
  73. package/.devcontainer/scripts/setup-config.sh +2 -0
  74. package/.devcontainer/scripts/setup-plugins.sh +38 -46
  75. package/.devcontainer/scripts/setup-projects.sh +175 -0
  76. package/.devcontainer/scripts/setup-symlink-claude.sh +36 -0
  77. package/.devcontainer/scripts/setup-update-claude.sh +11 -8
  78. package/.devcontainer/scripts/setup.sh +4 -2
  79. package/package.json +1 -1
  80. package/.devcontainer/scripts/setup-irie-claude.sh +0 -32
@@ -0,0 +1,344 @@
1
+ ---
2
+ name: refactoring-patterns
3
+ description: >-
4
+ This skill should be used when the user asks to "refactor this code",
5
+ "clean up this function", "extract a method", "reduce code duplication",
6
+ "fix code smells", "simplify this class", "break up this large function",
7
+ "apply design patterns", "remove dead code", "improve code structure",
8
+ or discusses code smells, refactoring techniques, extract function,
9
+ inline variable, feature envy, god class, or behavior-preserving
10
+ transformations.
11
+ version: 0.1.0
12
+ ---
13
+
14
+ # Refactoring Patterns
15
+
16
+ ## Mental Model
17
+
18
+ Refactoring is **behavior-preserving transformation** -- you change the structure of code without changing what it does. The key word is *preserving*. Every refactoring step must leave the system in a working state. If you can't verify the behavior hasn't changed, you're not refactoring -- you're rewriting.
19
+
20
+ Tests are the safety net. Before touching any code, confirm that tests pass. After every transformation, run tests again. If there are no tests, write characterization tests first -- tests that capture the current behavior, even if that behavior is buggy. Fix bugs separately from refactoring; mixing the two makes both harder to verify.
21
+
22
+ The refactoring cycle is:
23
+ 1. **Identify** a code smell (a surface indicator of a deeper structural problem)
24
+ 2. **Choose** the appropriate transformation
25
+ 3. **Apply** the transformation in the smallest possible step
26
+ 4. **Verify** tests still pass
27
+ 5. **Commit** before moving to the next smell
28
+
29
+ Small steps are critical. A refactoring that touches 15 files in one commit is a rewrite wearing a disguise. Each commit should be independently revertable.
30
+
31
+ ---
32
+
33
+ ## Code Smell Catalog
34
+
35
+ Code smells are heuristics, not rules. A method with 30 lines might be perfectly clear; a method with 8 lines might hide a design problem. Use these as starting points for investigation, not automatic triggers for refactoring.
36
+
37
+ ### Long Method
38
+ **Detection:** Method exceeds ~20 lines, or you need to scroll to see the whole thing, or you find yourself writing comments to separate logical sections.
39
+ **Root cause:** The method has accumulated responsibilities over time.
40
+ **Fix:** Extract Function -- pull each commented section or logical block into a named function.
41
+
42
+ ### Feature Envy
43
+ **Detection:** A method accesses data from another object more than its own. Count the dots -- `order.customer.address.city` suggests the method belongs elsewhere.
44
+ **Root cause:** Behavior is in the wrong class.
45
+ **Fix:** Move Method to the class whose data it uses most.
46
+
47
+ ### Data Clump
48
+ **Detection:** The same group of parameters appears together in multiple function signatures (`x, y, z` or `host, port, protocol`).
49
+ **Root cause:** A missing abstraction -- these values form a concept that deserves its own type.
50
+ **Fix:** Introduce Parameter Object or extract a dataclass/type.
51
+
52
+ ### Primitive Obsession
53
+ **Detection:** Using `str` for email, `int` for currency, `dict` for structured data. Business rules are scattered across validation checks.
54
+ **Root cause:** Domain concepts are represented as raw primitives instead of types.
55
+ **Fix:** Replace Primitive with Value Object -- create a type that encapsulates the value and its constraints.
56
+
57
+ ### God Class
58
+ **Detection:** A class with 500+ lines, 20+ methods, or a name like `Manager`, `Handler`, `Processor`, `Utils`. It knows about everything and everything depends on it.
59
+ **Root cause:** Single Responsibility Principle violation accumulated over time.
60
+ **Fix:** Extract Class -- identify clusters of related fields and methods, pull each cluster into its own class.
61
+
62
+ ### Shotgun Surgery
63
+ **Detection:** A single conceptual change requires editing 5+ files in the same way. Adding a new status enum value means updating the model, serializer, validator, template, and two tests.
64
+ **Root cause:** A concept is spread across too many places without a central abstraction.
65
+ **Fix:** Move Method / Move Field to consolidate the scattered logic, or introduce a registry/dispatch pattern.
66
+
67
+ ### Divergent Change
68
+ **Detection:** One class changes for multiple unrelated reasons -- the `User` class changes when auth logic changes AND when profile rendering changes.
69
+ **Root cause:** The class has multiple responsibilities that change at different rates.
70
+ **Fix:** Extract Class to separate the responsibilities.
71
+
72
+ ### Message Chain
73
+ **Detection:** A chain of calls like `a.getB().getC().getD().doSomething()`. Each link in the chain is a coupling point.
74
+ **Root cause:** Client code knows too much about the object graph.
75
+ **Fix:** Hide Delegate -- provide a method on the first object that reaches through the chain internally.
76
+
77
+ > **Deep dive:** See `references/smell-catalog.md` for the full catalog with before/after code examples for each smell.
78
+
79
+ ---
80
+
81
+ ## Safe Refactoring Steps
82
+
83
+ Each transformation below is mechanical and behavior-preserving when applied correctly. Follow the steps exactly.
84
+
85
+ ### Extract Function
86
+ **When:** A block of code inside a method can be grouped under a descriptive name.
87
+
88
+ ```python
89
+ from dataclasses import dataclass
90
+
91
+ @dataclass
92
+ class Order:
93
+ items: list
94
+ total: float
95
+ customer: "Customer"
96
+
97
+ # Before
98
+ def process_order(order: Order) -> None:
99
+ # validate
100
+ if not order.items:
101
+ raise ValueError("Empty order")
102
+ if order.total < 0:
103
+ raise ValueError("Negative total")
104
+ # apply discount
105
+ if order.customer.is_premium:
106
+ order.total *= 0.9
107
+
108
+ # After
109
+ def process_order(order: Order) -> None:
110
+ validate_order(order)
111
+ apply_premium_discount(order)
112
+
113
+ def validate_order(order: Order) -> None:
114
+ if not order.items:
115
+ raise ValueError("Empty order")
116
+ if order.total < 0:
117
+ raise ValueError("Negative total")
118
+
119
+ def apply_premium_discount(order: Order) -> None:
120
+ if order.customer.is_premium:
121
+ order.total *= 0.9
122
+ ```
123
+
124
+ ### Inline Variable
125
+ **When:** A temporary variable adds no clarity and is used only once.
126
+
127
+ ```typescript
128
+ // Before
129
+ const basePrice = order.quantity * order.itemPrice;
130
+ return basePrice;
131
+
132
+ // After
133
+ return order.quantity * order.itemPrice;
134
+ ```
135
+
136
+ ### Replace Conditional with Polymorphism
137
+ **When:** A switch or if-chain selects behavior based on a type field.
138
+
139
+ ```python
140
+ import math
141
+ from dataclasses import dataclass
142
+
143
+ # Before
144
+ def calculate_area(shape):
145
+ if shape.type == "circle":
146
+ return math.pi * shape.radius ** 2
147
+ elif shape.type == "rectangle":
148
+ return shape.width * shape.height
149
+
150
+ # After
151
+ @dataclass
152
+ class Circle:
153
+ radius: float
154
+
155
+ def area(self) -> float:
156
+ return math.pi * self.radius ** 2
157
+
158
+ @dataclass
159
+ class Rectangle:
160
+ width: float
161
+ height: float
162
+
163
+ def area(self) -> float:
164
+ return self.width * self.height
165
+ ```
166
+
167
+ ### Introduce Parameter Object
168
+ **When:** Multiple parameters travel together across function boundaries.
169
+
170
+ ```python
171
+ from dataclasses import dataclass
172
+
173
+ # Before
174
+ def search(query: str, page: int, per_page: int, sort_by: str, order: str): ...
175
+ def count(query: str, sort_by: str, order: str): ...
176
+
177
+ # After
178
+ @dataclass
179
+ class SearchParams:
180
+ query: str
181
+ page: int = 1
182
+ per_page: int = 20
183
+ sort_by: str = "relevance"
184
+ order: str = "desc"
185
+
186
+ def search(params: SearchParams): ...
187
+ def count(params: SearchParams): ...
188
+ ```
189
+
190
+ ### Pull Up Method
191
+ **When:** A method in a subclass is identical across all subclasses.
192
+
193
+ ```python
194
+ # Before: duplicated method in both subclasses
195
+ class SalariedEmployee(Employee):
196
+ def annual_cost(self) -> float:
197
+ return self.salary * 1.3 # 30% benefits overhead
198
+
199
+ class HourlyEmployee(Employee):
200
+ def annual_cost(self) -> float:
201
+ return self.salary * 1.3 # identical logic
202
+
203
+ # After: pulled up to parent
204
+ class Employee:
205
+ def annual_cost(self) -> float:
206
+ return self.salary * 1.3
207
+ ```
208
+
209
+ ### Push Down Method
210
+ **When:** A method in the parent is only relevant to one subclass.
211
+
212
+ ```python
213
+ # Before: method in parent used by only one subclass
214
+ class Employee:
215
+ def commission_rate(self) -> float: # only SalesRep uses this
216
+ return 0.05
217
+
218
+ # After: pushed down to the relevant subclass
219
+ class SalesRep(Employee):
220
+ def commission_rate(self) -> float:
221
+ return 0.05
222
+ ```
223
+
224
+ > **Deep dive:** See `references/safe-transformations.md` for step-by-step transformation recipes with pre-conditions, mechanical steps, and post-conditions.
225
+
226
+ ---
227
+
228
+ ## Language-Specific Patterns
229
+
230
+ ### Python
231
+
232
+ **Dataclasses over raw dicts:**
233
+ ```python
234
+ from dataclasses import dataclass
235
+
236
+ # Before: dict with implicit schema
237
+ user = {"name": "Alice", "email": "alice@example.com", "role": "admin"}
238
+
239
+ # After: explicit, typed, with defaults
240
+ @dataclass
241
+ class User:
242
+ name: str
243
+ email: str
244
+ role: str = "viewer"
245
+ ```
246
+
247
+ **Comprehensions over manual loops:**
248
+ ```python
249
+ # Before
250
+ result = []
251
+ for item in items:
252
+ if item.is_active():
253
+ result.append(item.name)
254
+
255
+ # After
256
+ result = [item.name for item in items if item.is_active()]
257
+ ```
258
+
259
+ **Context managers over try/finally:**
260
+ ```python
261
+ # Before
262
+ f = open("data.txt")
263
+ try:
264
+ data = f.read()
265
+ finally:
266
+ f.close()
267
+
268
+ # After
269
+ with open("data.txt") as f:
270
+ data = f.read()
271
+ ```
272
+
273
+ ### TypeScript
274
+
275
+ **Type narrowing over type assertions:**
276
+ ```typescript
277
+ // Before: unsafe assertion
278
+ const user = data as User;
279
+
280
+ // After: runtime narrowing
281
+ function isUser(data: unknown): data is User {
282
+ return typeof data === "object" && data !== null && "email" in data;
283
+ }
284
+ if (isUser(data)) {
285
+ console.log(data.email); // safely narrowed
286
+ }
287
+ ```
288
+
289
+ **Discriminated unions over type fields:**
290
+ ```typescript
291
+ // Before: stringly-typed
292
+ interface Shape { type: string; radius?: number; width?: number; height?: number; }
293
+
294
+ // After: discriminated union
295
+ type Shape =
296
+ | { kind: "circle"; radius: number }
297
+ | { kind: "rectangle"; width: number; height: number };
298
+ ```
299
+
300
+ ---
301
+
302
+ ## Verification Protocol
303
+
304
+ Every refactoring session follows this protocol:
305
+
306
+ 1. **Run all tests** before starting. If tests fail, fix them first -- don't refactor broken code.
307
+ 2. **Make one transformation** at a time. Extract one function, rename one variable, move one method.
308
+ 3. **Run tests** after every transformation. If tests fail, revert immediately -- don't debug a failed refactoring.
309
+ 4. **Commit** each passing transformation. Atomic commits make bisecting easy if something breaks later.
310
+ 5. **Review the diff** before pushing. Does each change preserve behavior? Are there unintended side effects?
311
+
312
+ ```bash
313
+ # The refactoring loop
314
+ git stash # save any unrelated work
315
+ pytest # green? proceed. red? fix first.
316
+
317
+ # ... make ONE transformation ...
318
+
319
+ pytest # green? commit. red? git checkout -- .
320
+ git add -p # stage only the refactoring
321
+ git commit -m "refactor: extract validate_order from process_order"
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Ambiguity Policy
327
+
328
+ These defaults apply when the user does not specify a preference. State the assumption when making a choice:
329
+
330
+ - **Scope:** Default to the smallest transformation that addresses the identified smell. Do not chain multiple refactorings without testing between each step.
331
+ - **Test requirement:** If no tests exist, write characterization tests before refactoring. Do not refactor untested code.
332
+ - **Naming:** When extracting a function, name it after what it does, not how it does it. Prefer verbs (`validate_order`) over nouns (`order_validation`).
333
+ - **Depth:** Default to one level of extraction. If the extracted function itself has smells, address them in a separate commit.
334
+ - **Language idioms:** Prefer language-native patterns (Python dataclasses over hand-rolled `__init__`, TypeScript discriminated unions over type assertions).
335
+ - **Commit granularity:** One refactoring transformation per commit. Multiple files in one commit is fine if they're part of the same transformation.
336
+
337
+ ---
338
+
339
+ ## Reference Files
340
+
341
+ | File | Contents |
342
+ |------|----------|
343
+ | `references/smell-catalog.md` | Full catalog of code smells with detection heuristics, before/after code examples, and recommended transformations |
344
+ | `references/safe-transformations.md` | Step-by-step transformation recipes with pre-conditions, mechanical steps, post-conditions, and rollback instructions |
@@ -0,0 +1,247 @@
1
+ # Safe Transformation Recipes
2
+
3
+ Step-by-step recipes for behavior-preserving refactorings. Each recipe has pre-conditions, mechanical steps, and post-conditions.
4
+
5
+ ## Contents
6
+
7
+ - [Extract Function](#extract-function)
8
+ - [Inline Variable](#inline-variable)
9
+ - [Replace Conditional with Polymorphism](#replace-conditional-with-polymorphism)
10
+ - [Introduce Parameter Object](#introduce-parameter-object)
11
+ - [Move Method](#move-method)
12
+ - [Pull Up Method](#pull-up-method)
13
+ - [Push Down Method](#push-down-method)
14
+ - [Rollback Protocol](#rollback-protocol)
15
+
16
+ ---
17
+
18
+ ## Extract Function
19
+
20
+ ### Pre-conditions
21
+ - [ ] Tests pass before starting
22
+ - [ ] The code block to extract has clear inputs and outputs
23
+ - [ ] The block does not modify local variables used later in the parent function (or you handle the return)
24
+
25
+ ### Steps
26
+
27
+ 1. **Identify** the block of code to extract. Note all variables it reads (inputs) and all variables it modifies (outputs).
28
+ 2. **Create** a new function with a descriptive name. Use the inputs as parameters.
29
+ 3. **Copy** the code block into the new function.
30
+ 4. **Return** any outputs that the parent function needs.
31
+ 5. **Replace** the original block with a call to the new function.
32
+ 6. **Run tests.**
33
+
34
+ ```python
35
+ # Step 1: Identify inputs (items, tax_rate) and outputs (total)
36
+ def process(items, tax_rate):
37
+ # --- begin extract ---
38
+ subtotal = sum(i.price for i in items)
39
+ total = subtotal * (1 + tax_rate)
40
+ # --- end extract ---
41
+ save(total)
42
+
43
+ # Steps 2-5: Extract
44
+ def calculate_total(items, tax_rate):
45
+ subtotal = sum(i.price for i in items)
46
+ return subtotal * (1 + tax_rate)
47
+
48
+ def process(items, tax_rate):
49
+ total = calculate_total(items, tax_rate)
50
+ save(total)
51
+ ```
52
+
53
+ ### Post-conditions
54
+ - [ ] Tests still pass
55
+ - [ ] The new function has no side effects that differ from the original block
56
+ - [ ] The parent function produces identical results
57
+
58
+ ---
59
+
60
+ ## Inline Variable
61
+
62
+ ### Pre-conditions
63
+ - [ ] The variable is assigned once and used once
64
+ - [ ] The variable name does not add meaningful clarity over the expression itself
65
+ - [ ] The expression has no side effects (calling it once vs. zero times must be equivalent)
66
+
67
+ ### Steps
68
+
69
+ 1. **Verify** the variable is used exactly once after assignment.
70
+ 2. **Replace** the variable reference with the expression.
71
+ 3. **Remove** the variable assignment.
72
+ 4. **Run tests.**
73
+
74
+ ```typescript
75
+ // Before
76
+ const isEligible = user.age >= 18 && user.hasVerifiedEmail;
77
+ if (isEligible) { ... }
78
+
79
+ // After (only if isEligible doesn't add clarity)
80
+ if (user.age >= 18 && user.hasVerifiedEmail) { ... }
81
+ ```
82
+
83
+ ### Post-conditions
84
+ - [ ] Tests still pass
85
+ - [ ] No change in behavior (expression was pure)
86
+
87
+ ---
88
+
89
+ ## Replace Conditional with Polymorphism
90
+
91
+ ### Pre-conditions
92
+ - [ ] A switch/if-chain selects behavior based on a type indicator
93
+ - [ ] The type indicator has a stable set of values (not unbounded user input)
94
+ - [ ] Each branch has enough logic to justify its own method/class
95
+
96
+ ### Steps
97
+
98
+ 1. **Create** a base class or interface with the polymorphic method.
99
+ 2. **Create** a subclass for each branch of the conditional.
100
+ 3. **Move** the branch logic into the corresponding subclass method.
101
+ 4. **Replace** the conditional with a method call on the object.
102
+ 5. **Update** object creation to instantiate the correct subclass.
103
+ 6. **Run tests.**
104
+
105
+ ```python
106
+ # Before
107
+ def calculate_pay(employee):
108
+ if employee.type == "hourly":
109
+ return employee.hours * employee.rate
110
+ elif employee.type == "salaried":
111
+ return employee.annual_salary / 26
112
+ elif employee.type == "contractor":
113
+ return employee.hours * employee.rate * 1.2
114
+
115
+ # After
116
+ class HourlyEmployee:
117
+ def calculate_pay(self):
118
+ return self.hours * self.rate
119
+
120
+ class SalariedEmployee:
121
+ def calculate_pay(self):
122
+ return self.annual_salary / 26
123
+
124
+ class Contractor:
125
+ def calculate_pay(self):
126
+ return self.hours * self.rate * 1.2
127
+ ```
128
+
129
+ ### Post-conditions
130
+ - [ ] Tests still pass
131
+ - [ ] Adding a new type requires adding a new class, not modifying existing code
132
+ - [ ] The conditional is completely removed
133
+
134
+ ---
135
+
136
+ ## Introduce Parameter Object
137
+
138
+ ### Pre-conditions
139
+ - [ ] 3+ parameters appear together in multiple function signatures
140
+ - [ ] The parameters have a logical relationship (they represent a concept)
141
+
142
+ ### Steps
143
+
144
+ 1. **Create** a class/dataclass/type with the grouped parameters as fields.
145
+ 2. **Add** the new type as a parameter to ONE function.
146
+ 3. **Update** callers of that function to construct the object.
147
+ 4. **Run tests.**
148
+ 5. **Repeat** for the remaining functions, one at a time.
149
+ 6. **Move** any logic that operates on the grouped parameters into the new class.
150
+
151
+ ```python
152
+ from dataclasses import dataclass
153
+ from datetime import datetime
154
+
155
+ # Step 1: Create the type
156
+ @dataclass
157
+ class DateRange:
158
+ start: datetime
159
+ end: datetime
160
+
161
+ def contains(self, dt: datetime) -> bool:
162
+ return self.start <= dt <= self.end
163
+
164
+ def days(self) -> int:
165
+ return (self.end - self.start).days
166
+
167
+ # Steps 2-5: Update functions one at a time
168
+ def query_logs(date_range: DateRange, level: str) -> list[dict]: ...
169
+ def generate_report(date_range: DateRange, format: str) -> str: ...
170
+ ```
171
+
172
+ ### Post-conditions
173
+ - [ ] Tests still pass
174
+ - [ ] Each function that previously took the scattered parameters now takes the object
175
+ - [ ] Behavior that belongs to the grouped parameters has migrated into the new class
176
+
177
+ ---
178
+
179
+ ## Move Method
180
+
181
+ ### Pre-conditions
182
+ - [ ] A method accesses more data from another class than its own (Feature Envy)
183
+ - [ ] Moving the method would reduce coupling between the two classes
184
+
185
+ ### Steps
186
+
187
+ 1. **Copy** the method to the target class.
188
+ 2. **Adjust** the method to use `self` for the target class's data.
189
+ 3. **Add** parameters for any data it still needs from the source class.
190
+ 4. **Update** callers to call the method on the target object.
191
+ 5. **Remove** the method from the source class (or delegate to the target).
192
+ 6. **Run tests.**
193
+
194
+ ### Post-conditions
195
+ - [ ] Tests still pass
196
+ - [ ] The method no longer envies another class's data
197
+
198
+ ---
199
+
200
+ ## Pull Up Method
201
+
202
+ ### Pre-conditions
203
+ - [ ] Two or more subclasses have identical (or near-identical) methods
204
+ - [ ] The method uses only fields/methods available in the parent class
205
+
206
+ ### Steps
207
+
208
+ 1. **Verify** the methods are semantically identical (not just textually similar).
209
+ 2. **Ensure** any referenced fields exist in the parent class (move them up if needed).
210
+ 3. **Copy** the method to the parent class.
211
+ 4. **Remove** the method from all subclasses.
212
+ 5. **Run tests.**
213
+
214
+ ### Post-conditions
215
+ - [ ] Tests still pass
216
+ - [ ] No duplication of the method across subclasses
217
+
218
+ ---
219
+
220
+ ## Push Down Method
221
+
222
+ ### Pre-conditions
223
+ - [ ] A method in the parent class is only relevant to one subclass
224
+ - [ ] Other subclasses don't call or override the method
225
+
226
+ ### Steps
227
+
228
+ 1. **Copy** the method to the relevant subclass.
229
+ 2. **Remove** the method from the parent class.
230
+ 3. **Run tests.** If other subclasses call the method, compilation/runtime will fail -- that's a signal this transformation is wrong.
231
+
232
+ ### Post-conditions
233
+ - [ ] Tests still pass
234
+ - [ ] The parent class is simpler with one fewer responsibility
235
+
236
+ ---
237
+
238
+ ## Rollback Protocol
239
+
240
+ If tests fail after any transformation:
241
+
242
+ 1. **Do not debug.** The transformation was incorrect or incomplete.
243
+ 2. **Revert immediately:** `git checkout -- .` (if uncommitted) or `git revert HEAD` (if committed).
244
+ 3. **Analyze** why it failed: missing parameter, side effect, implicit dependency.
245
+ 4. **Retry** with a smaller transformation or different approach.
246
+
247
+ The cost of reverting is zero. The cost of debugging a broken refactoring compounds with every additional change.