kollabor 0.4.9__py3-none-any.whl → 0.4.15__py3-none-any.whl

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 (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1454 @@
1
+ <!-- Refactoring skill - safe code transformation while keeping tests green -->
2
+
3
+ refactoring mode: CHANGE STRUCTURE, NOT BEHAVIOR
4
+
5
+ when this skill is active, you follow disciplined refactoring practices.
6
+ this is a comprehensive guide to safe code transformation.
7
+
8
+
9
+ PHASE 0: ENVIRONMENT VERIFICATION
10
+
11
+ before doing ANY refactoring, verify the safety net exists.
12
+
13
+
14
+ verify testing framework exists
15
+
16
+ <terminal>python -m pytest --version</terminal>
17
+
18
+ if pytest not installed:
19
+ <terminal>pip install pytest pytest-cov</terminal>
20
+
21
+
22
+ verify tests exist for code you will refactor
23
+
24
+ <terminal>find . -name "test_*.py" -type f | head -20</terminal>
25
+
26
+ <terminal>python -m pytest tests/ --collect-only</terminal>
27
+
28
+ if no tests exist for the target code:
29
+ [warn] cannot refactor safely without tests
30
+ [warn] either write tests first, or refuse to refactor
31
+ the discipline: no tests, no refactoring, no exceptions
32
+
33
+
34
+ verify current test status
35
+
36
+ <terminal>python -m pytest tests/ -v</terminal>
37
+
38
+ all tests must pass BEFORE starting refactoring.
39
+ if tests are failing:
40
+ [1] fix failing tests first
41
+ [2] or write reproduction tests for known issues
42
+ [3] never refactor while tests are red
43
+
44
+ document current state:
45
+
46
+ <terminal>python -m pytest tests/ --cov -v | tee test_status_before.log</terminal>
47
+
48
+
49
+ verify git status
50
+
51
+ <terminal>git status</terminal>
52
+
53
+ <terminal>git diff --stat</terminal>
54
+
55
+ ensure working directory is clean or changes are committed.
56
+ refactoring on top of uncommitted work is risky.
57
+
58
+ best practice:
59
+ <terminal>git checkout -b refactor/safe-cleanup-description</terminal>
60
+
61
+
62
+ check for IDE refactoring support
63
+
64
+ <terminal>code --list-extensions | grep -i refactor</terminal>
65
+
66
+ optional but helpful:
67
+ - VS Code: Python extension with refactoring
68
+ - PyCharm: built-in refactoring tools
69
+ - vim/emacs: with lsp or rope plugin
70
+
71
+
72
+ PHASE 1: WHAT IS REFACTORING
73
+
74
+ definition:
75
+
76
+ refactoring is changing code structure WITHOUT changing behavior.
77
+ the external behavior stays exactly the same.
78
+ the internal structure becomes cleaner, clearer, more maintainable.
79
+
80
+
81
+ what refactoring is NOT:
82
+
83
+ [x] adding new features
84
+ [x] fixing bugs (behavior changes)
85
+ [x] performance optimization (usually changes behavior)
86
+ [x] updating dependencies
87
+ [x] changing API contracts
88
+
89
+ these are different activities.
90
+ do them separately from refactoring.
91
+
92
+
93
+ the two hats:
94
+
95
+ hat 1: adding function
96
+ - adding new capabilities
97
+ - fixing bugs
98
+ - changing behavior
99
+ - tests will change/expand
100
+
101
+ hat 2: refactoring
102
+ - restructuring existing code
103
+ - behavior stays exactly the same
104
+ - tests stay exactly the same
105
+
106
+ never wear both hats at once.
107
+ switch clearly between them.
108
+
109
+
110
+ when to refactor
111
+
112
+ [ok] when you need to add a feature and the code resists
113
+ [ok] when code is duplicated in multiple places
114
+ [ok] when names dont clearly express intent
115
+ [ok] when methods are too long to understand
116
+ [ok] when conditionals are deeply nested
117
+ [ok] when you need to understand how code works
118
+
119
+ [warn] when deadlines are tight and tests are missing
120
+ [warn] when code works but you dont like the style
121
+ [warn] when youre trying to learn a new pattern
122
+
123
+ the rule of three:
124
+ 1. first time - just do it
125
+ 2. second time - wince at the duplication
126
+ 3. third time - refactor
127
+
128
+
129
+ when NOT to refactor
130
+
131
+ [x] production is down and youre panicked
132
+ [x] no tests exist
133
+ [x] tests are failing
134
+ [x] youre about to release
135
+ [x] the code is about to be deleted
136
+ [x] you dont understand what the code does
137
+
138
+ first understand, then refactor.
139
+ never refactor mysterious code.
140
+
141
+
142
+ PHASE 2: THE REFACTORING CYCLE
143
+
144
+ the core rhythm:
145
+
146
+ [1] verify tests pass
147
+ [2] identify the smell
148
+ [3] choose the refactoring
149
+ [4] make the SMALL change
150
+ [5] run tests
151
+ [6] if tests pass, commit
152
+ [7] if tests fail, revert and try smaller step
153
+ [8] repeat
154
+
155
+ this cycle repeats for every refactoring.
156
+ never batch multiple refactorings without testing.
157
+
158
+
159
+ step 1: verify tests pass
160
+
161
+ <terminal>python -m pytest tests/ -v</terminal>
162
+
163
+ get to green before changing anything.
164
+ document the baseline:
165
+ <terminal>python -m pytest tests/ --cov > coverage_baseline.txt</terminal>
166
+
167
+
168
+ step 2: identify the smell
169
+
170
+ common code smells:
171
+
172
+ - duplicated code
173
+ - long method
174
+ - large class
175
+ - long parameter list
176
+ - divergent change
177
+ - shotgun surgery
178
+ - feature envy
179
+ - data clumps
180
+ - primitive obsession
181
+ - switch statements
182
+ - temporary fields
183
+ - refused bequest
184
+ - comments
185
+
186
+ name the smell before fixing it.
187
+
188
+
189
+ step 3: choose the refactoring
190
+
191
+ match smell to refactoring:
192
+ - duplicated code -> extract method
193
+ - long method -> decompose conditional, extract method
194
+ - large class -> extract class
195
+ - long parameter list -> introduce parameter object
196
+ - feature envy -> move method
197
+ - primitive obsession -> replace primitive with object
198
+ - switch statements -> replace conditional with polymorphism
199
+
200
+ know your refactoring catalog.
201
+
202
+
203
+ step 4: make the small change
204
+
205
+ how small?
206
+ - one extraction at a time
207
+ - one rename at a time
208
+ - one move at a time
209
+
210
+ if you hesitate, make the change smaller.
211
+ you can always make another small change after.
212
+
213
+
214
+ step 5: run tests
215
+
216
+ <terminal>python -m pytest tests/ -v</terminal>
217
+
218
+ every single change gets tested.
219
+ no exceptions.
220
+
221
+
222
+ step 6: commit if green
223
+
224
+ <terminal>git add -A</terminal>
225
+ <terminal>git commit -m "refactor: extract user validation to separate method"</terminal>
226
+
227
+ small commits are your friend.
228
+ they make rollback easy.
229
+
230
+
231
+ step 7: revert if red
232
+
233
+ <terminal>git checkout -- .</terminal>
234
+
235
+ or
236
+ <terminal>git reset --hard HEAD</terminal>
237
+
238
+ never try to fix tests that broke during refactoring.
239
+ revert and think.
240
+ make a smaller change.
241
+
242
+
243
+ PHASE 3: EXTRACT METHOD
244
+
245
+ the most fundamental refactoring.
246
+
247
+
248
+ when to extract
249
+
250
+ signs you need extract method:
251
+ - method is longer than 10-15 lines
252
+ - method has multiple levels of indentation
253
+ - comments explain what a block does
254
+ - code is duplicated
255
+ - a group of lines forms a single concept
256
+
257
+
258
+ basic extraction
259
+
260
+ before:
261
+
262
+ def print_report(self, users):
263
+ # calculate totals
264
+ total_age = 0
265
+ for user in users:
266
+ total_age += user.age
267
+ average = total_age / len(users)
268
+
269
+ # print header
270
+ print("=" * 50)
271
+ print("USER REPORT")
272
+ print("=" * 50)
273
+
274
+ # print users
275
+ for user in users:
276
+ print(f"{user.name}: {user.age} years old")
277
+
278
+ # print footer
279
+ print("=" * 50)
280
+ print(f"Average age: {average}")
281
+ print("=" * 50)
282
+
283
+ after:
284
+
285
+ def print_report(self, users):
286
+ average = self._calculate_average_age(users)
287
+ self._print_header()
288
+ self._print_users(users)
289
+ self._print_footer(average)
290
+
291
+ def _calculate_average_age(self, users):
292
+ total_age = sum(user.age for user in users)
293
+ return total_age / len(users)
294
+
295
+ def _print_header(self):
296
+ print("=" * 50)
297
+ print("USER REPORT")
298
+ print("=" * 50)
299
+
300
+ def _print_users(self, users):
301
+ for user in users:
302
+ print(f"{user.name}: {user.age} years old")
303
+
304
+ def _print_footer(self, average):
305
+ print("=" * 50)
306
+ print(f"Average age: {average}")
307
+ print("=" * 50)
308
+
309
+
310
+ extraction with parameters
311
+
312
+ before:
313
+
314
+ def process_order(self, order):
315
+ if order.total > 1000:
316
+ discount = order.total * 0.1
317
+ order.total -= discount
318
+ if order.customer.is_vip:
319
+ discount = order.total * 0.05
320
+ order.total -= discount
321
+
322
+ after:
323
+
324
+ def process_order(self, order):
325
+ self._apply_volume_discount(order)
326
+ self._apply_vip_discount(order)
327
+
328
+ def _apply_volume_discount(self, order):
329
+ if order.total > 1000:
330
+ discount = order.total * 0.1
331
+ order.total -= discount
332
+
333
+ def _apply_vip_discount(self, order):
334
+ if order.customer.is_vip:
335
+ discount = order.total * 0.05
336
+ order.total -= discount
337
+
338
+
339
+ extraction with return value
340
+
341
+ before:
342
+
343
+ def send_notification(self, user, message):
344
+ formatted = f"Dear {user.name},\n\n{message}\n\nBest regards"
345
+ email = self._get_email_address(user)
346
+ # ... send email ...
347
+ return formatted
348
+
349
+ after:
350
+
351
+ def send_notification(self, user, message):
352
+ formatted = self._format_message(user, message)
353
+ email = self._get_email_address(user)
354
+ # ... send email ...
355
+ return formatted
356
+
357
+ def _format_message(self, user, message):
358
+ return f"Dear {user.name},\n\n{message}\n\nBest regards"
359
+
360
+
361
+ testing extract method
362
+
363
+ <terminal>python -m pytest tests/test_report.py -v</terminal>
364
+
365
+ tests should pass without modification.
366
+ if tests fail, the extraction changed behavior.
367
+ revert and try again.
368
+
369
+
370
+ PHASE 4: RENAME VARIABLES AND FUNCTIONS
371
+
372
+ good names are critical for readability.
373
+
374
+
375
+ when to rename
376
+
377
+ signs you need to rename:
378
+ - you have to think to understand what a variable means
379
+ - the name is misleading
380
+ - the name is too generic (data, info, value, temp)
381
+ - abbreviations require decoding
382
+ - the name describes implementation, not intent
383
+
384
+
385
+ naming principles
386
+
387
+ [ok] user, order, calculate_total, is_valid
388
+ [ok] fetch_user_data, render_html_content
389
+ [warn] u, o, calc, check
390
+ [x] data, info, value, temp, thing, stuff
391
+
392
+ intent > brevity.
393
+ clarity > cleverness.
394
+
395
+
396
+ rename variables
397
+
398
+ before:
399
+
400
+ def proc(self, d):
401
+ for i in d:
402
+ print(i['n'])
403
+ return len(d)
404
+
405
+ after:
406
+
407
+ def display_users(self, users):
408
+ for user in users:
409
+ print(user['name'])
410
+ return len(users)
411
+
412
+
413
+ rename methods
414
+
415
+ before:
416
+
417
+ def get(self, id):
418
+ return self.db.find(id)
419
+
420
+ after:
421
+
422
+ def find_user_by_id(self, user_id):
423
+ return self.db.find(user_id)
424
+
425
+
426
+ rename boolean variables
427
+
428
+ use is/has/should/can prefixes:
429
+
430
+ [ok] is_valid, has_permission, should_retry, can_delete
431
+ [x] valid, permission, retry, delete
432
+
433
+ rename with IDE:
434
+
435
+ # VS Code
436
+ F2 on the name, type new name, Enter
437
+
438
+ # PyCharm
439
+ Shift+F6 on the name, type new name, Enter
440
+
441
+ # command line (manual, risky)
442
+ <terminal>grep -r "old_name" src/</terminal>
443
+ # then careful find and replace
444
+
445
+
446
+ rename checklist
447
+
448
+ [ ] search for all usages of the name
449
+ [ ] verify each usage still makes sense with new name
450
+ [ ] run tests to ensure nothing broke
451
+ [ ] commit the rename
452
+
453
+ <terminal>python -m pytest tests/ -v</terminal>
454
+
455
+
456
+ PHASE 5: INLINE TEMP / INLINE VARIABLE
457
+
458
+ reverse of extract method.
459
+ when a variable is only used once, inline it.
460
+
461
+
462
+ when to inline
463
+
464
+ signs you need to inline:
465
+ - temp variable is used only once
466
+ - the expression is clearer than the variable name
467
+ - the variable doesnt add meaningful abstraction
468
+
469
+
470
+ basic inline
471
+
472
+ before:
473
+
474
+ def calculate_discount(self, order):
475
+ base_price = order.quantity * order.item_price
476
+ if base_price > 1000:
477
+ return base_price * 0.9
478
+ return base_price
479
+
480
+ after:
481
+
482
+ def calculate_discount(self, order):
483
+ base_price = order.quantity * order.item_price
484
+ if base_price > 1000:
485
+ return order.quantity * order.item_price * 0.9
486
+ return base_price
487
+
488
+ inline the second usage since it adds nothing.
489
+
490
+
491
+ inline with explanation
492
+
493
+ before:
494
+
495
+ def is_eligible_for_discount(self, customer):
496
+ is_important = customer.tier == "VIP" and customer.years > 5
497
+ if is_important:
498
+ return True
499
+ return False
500
+
501
+ after:
502
+
503
+ def is_eligible_for_discount(self, customer):
504
+ return customer.tier == "VIP" and customer.years > 5
505
+
506
+
507
+ inline to simplify
508
+
509
+ before:
510
+
511
+ def process(self, data):
512
+ result = self._transform(data)
513
+ return result
514
+
515
+ after:
516
+
517
+ def process(self, data):
518
+ return self._transform(data)
519
+
520
+
521
+ PHASE 6: EXTRACT CLASS
522
+
523
+ when a class does too much, split it.
524
+
525
+
526
+ when to extract class
527
+
528
+ signs you need extract class:
529
+ - class has more than ~300 lines
530
+ - class has unrelated responsibilities
531
+ - class changes for multiple reasons
532
+ - subset of methods uses subset of fields
533
+
534
+
535
+ identify natural boundaries
536
+
537
+ group related:
538
+ - fields
539
+ - methods that use those fields
540
+ - responsibilities
541
+
542
+ these form the new class.
543
+
544
+
545
+ basic extraction
546
+
547
+ before:
548
+
549
+ class Person:
550
+ def __init__(self, name, email, phone, street, city, zip_code):
551
+ self.name = name
552
+ self.email = email
553
+ self.phone = phone
554
+ self.street = street
555
+ self.city = city
556
+ self.zip_code = zip_code
557
+
558
+ def get_full_address(self):
559
+ return f"{self.street}, {self.city} {self.zip_code}"
560
+
561
+ def get_email_domain(self):
562
+ return self.email.split('@')[1]
563
+
564
+ after:
565
+
566
+ class Person:
567
+ def __init__(self, name, contact_info):
568
+ self.name = name
569
+ self.contact_info = contact_info
570
+
571
+ class ContactInfo:
572
+ def __init__(self, email, phone, street, city, zip_code):
573
+ self.email = email
574
+ self.phone = phone
575
+ self.address = Address(street, city, zip_code)
576
+
577
+ def get_email_domain(self):
578
+ return self.email.split('@')[1]
579
+
580
+ class Address:
581
+ def __init__(self, street, city, zip_code):
582
+ self.street = street
583
+ self.city = city
584
+ self.zip_code = zip_code
585
+
586
+ def get_full_address(self):
587
+ return f"{self.street}, {self.city} {self.zip_code}"
588
+
589
+
590
+ extraction with delegation
591
+
592
+ before:
593
+
594
+ class OrderProcessor:
595
+ def __init__(self):
596
+ self.inventory = {}
597
+ self.pricing = {}
598
+
599
+ def process_order(self, order):
600
+ if self._check_inventory(order):
601
+ price = self._calculate_price(order)
602
+ self._update_inventory(order)
603
+ return price
604
+
605
+ def _check_inventory(self, order):
606
+ # inventory logic
607
+ pass
608
+
609
+ def _calculate_price(self, order):
610
+ # pricing logic
611
+ pass
612
+
613
+ def _update_inventory(self, order):
614
+ # inventory update logic
615
+ pass
616
+
617
+ after:
618
+
619
+ class OrderProcessor:
620
+ def __init__(self, inventory_manager, pricing_calculator):
621
+ self.inventory = inventory_manager
622
+ self.pricing = pricing_calculator
623
+
624
+ def process_order(self, order):
625
+ if self.inventory.check_available(order):
626
+ price = self.pricing.calculate(order)
627
+ self.inventory.update(order)
628
+ return price
629
+
630
+ class InventoryManager:
631
+ def __init__(self):
632
+ self.stock = {}
633
+
634
+ def check_available(self, order):
635
+ # inventory logic
636
+ pass
637
+
638
+ def update(self, order):
639
+ # inventory update logic
640
+ pass
641
+
642
+ class PricingCalculator:
643
+ def __init__(self):
644
+ self.price_list = {}
645
+
646
+ def calculate(self, order):
647
+ # pricing logic
648
+ pass
649
+
650
+
651
+ testing extract class
652
+
653
+ <terminal>python -m pytest tests/test_order_processor.py -v</terminal>
654
+
655
+ interface tests should pass.
656
+ internal structure changed, behavior unchanged.
657
+
658
+
659
+ PHASE 7: MOVE METHOD / MOVE FUNCTION
660
+
661
+ when a method is in the wrong class, move it.
662
+
663
+
664
+ when to move
665
+
666
+ signs a method should move:
667
+ - method uses more data from another class
668
+ - method has no real use in current class
669
+ - a class has feature envy (uses another class more than itself)
670
+
671
+
672
+ move to where data is
673
+
674
+ before:
675
+
676
+ class Order:
677
+ def __init__(self, customer):
678
+ self.customer = customer
679
+ self.items = []
680
+
681
+ def get_customer_address(self):
682
+ return f"{self.customer.street}, {self.customer.city}"
683
+
684
+ def get_customer_discount(self):
685
+ if self.customer.tier == "VIP":
686
+ return 0.1
687
+ return 0.0
688
+
689
+ after:
690
+
691
+ class Order:
692
+ def __init__(self, customer):
693
+ self.customer = customer
694
+ self.items = []
695
+
696
+ def get_customer_address(self):
697
+ return self.customer.get_address()
698
+
699
+ def get_customer_discount(self):
700
+ return self.customer.get_discount()
701
+
702
+ class Customer:
703
+ def __init__(self, street, city, tier):
704
+ self.street = street
705
+ self.city = city
706
+ self.tier = tier
707
+
708
+ def get_address(self):
709
+ return f"{self.street}, {self.city}"
710
+
711
+ def get_discount(self):
712
+ if self.tier == "VIP":
713
+ return 0.1
714
+ return 0.0
715
+
716
+
717
+ move to parameter object
718
+
719
+ before:
720
+
721
+ def calculate_shipping(order):
722
+ if order.customer.address.state == order.warehouse.address.state:
723
+ return 5.0
724
+ else:
725
+ distance = calculate_distance(
726
+ order.customer.address,
727
+ order.warehouse.address
728
+ )
729
+ return distance * 0.1
730
+
731
+ after:
732
+
733
+ def calculate_shipping(order):
734
+ return order.shipping_calculator.calculate()
735
+
736
+
737
+ PHASE 8: REPLACE CONDITIONAL WITH POLYMORPHISM
738
+
739
+ when switch statements duplicate, use polymorphism.
740
+
741
+
742
+ when to use polymorphism
743
+
744
+ signs you need polymorphism:
745
+ - same switch appears in multiple places
746
+ - adding new type requires changing many places
747
+ - switch on type codes
748
+
749
+
750
+ before: type codes
751
+
752
+ class Employee:
753
+ def __init__(self, type_code):
754
+ self.type_code = type_code
755
+
756
+ def calculate_pay(self):
757
+ if self.type_code == "ENGINEER":
758
+ return self.salary * 1.0
759
+ elif self.type_code == "MANAGER":
760
+ return self.salary * 1.2
761
+ elif self.type_code == "SALES":
762
+ return self.salary * 0.9 + self.commission
763
+
764
+ def get_bonus(self):
765
+ if self.type_code == "ENGINEER":
766
+ return 5000
767
+ elif self.type_code == "MANAGER":
768
+ return 10000
769
+ elif self.type_code == "SALES":
770
+ return self.sales * 0.05
771
+
772
+
773
+ after: polymorphism
774
+
775
+ from abc import ABC, abstractmethod
776
+
777
+ class Employee(ABC):
778
+ def __init__(self, salary):
779
+ self.salary = salary
780
+
781
+ @abstractmethod
782
+ def calculate_pay(self):
783
+ pass
784
+
785
+ @abstractmethod
786
+ def get_bonus(self):
787
+ pass
788
+
789
+ class Engineer(Employee):
790
+ def calculate_pay(self):
791
+ return self.salary * 1.0
792
+
793
+ def get_bonus(self):
794
+ return 5000
795
+
796
+ class Manager(Employee):
797
+ def calculate_pay(self):
798
+ return self.salary * 1.2
799
+
800
+ def get_bonus(self):
801
+ return 10000
802
+
803
+ class Sales(Employee):
804
+ def __init__(self, salary, commission, sales):
805
+ super().__init__(salary)
806
+ self.commission = commission
807
+ self.sales = sales
808
+
809
+ def calculate_pay(self):
810
+ return self.salary * 0.9 + self.commission
811
+
812
+ def get_bonus(self):
813
+ return self.sales * 0.05
814
+
815
+
816
+ factory creation
817
+
818
+ class EmployeeFactory:
819
+ @staticmethod
820
+ def create(type_code, **kwargs):
821
+ if type_code == "ENGINEER":
822
+ return Engineer(kwargs.get("salary", 0))
823
+ elif type_code == "MANAGER":
824
+ return Manager(kwargs.get("salary", 0))
825
+ elif type_code == "SALES":
826
+ return Sales(
827
+ kwargs.get("salary", 0),
828
+ kwargs.get("commission", 0),
829
+ kwargs.get("sales", 0)
830
+ )
831
+
832
+
833
+ PHASE 9: DECOMPOSE CONDITIONAL
834
+
835
+ complex conditionals are hard to read.
836
+ break them into named methods.
837
+
838
+
839
+ when to decompose
840
+
841
+ signs you need to decompose:
842
+ - conditional logic is hard to understand
843
+ - comments are needed to explain the conditional
844
+ - same condition appears multiple times
845
+
846
+
847
+ decompose and/or
848
+
849
+ before:
850
+
851
+ def calculate_shipping_cost(self, order):
852
+ if (order.weight > 10 and order.destination.country != "US") or \
853
+ (order.weight > 20 and order.destination.country == "US"):
854
+ return order.weight * 2.0
855
+ else:
856
+ return order.weight * 1.0
857
+
858
+ after:
859
+
860
+ def calculate_shipping_cost(self, order):
861
+ if self._is_express_shipping(order):
862
+ return order.weight * 2.0
863
+ else:
864
+ return order.weight * 1.0
865
+
866
+ def _is_express_shipping(self, order):
867
+ return self._is_heavy_international(order) or \
868
+ self._is_very_heavy_domestic(order)
869
+
870
+ def _is_heavy_international(self, order):
871
+ return order.weight > 10 and order.destination.country != "US"
872
+
873
+ def _is_very_heavy_domestic(self, order):
874
+ return order.weight > 20 and order.destination.country == "US"
875
+
876
+
877
+ consolidate conditional fragments
878
+
879
+ before:
880
+
881
+ def send_email(self, user, message):
882
+ if user.email:
883
+ print(f"Sending to {user.email}")
884
+ print(f"Subject: {message.subject}")
885
+ print(f"Body: {message.body}")
886
+ self.sent_count += 1
887
+ else:
888
+ print("No email provided")
889
+
890
+ after:
891
+
892
+ def send_email(self, user, message):
893
+ if not user.email:
894
+ print("No email provided")
895
+ return
896
+
897
+ self._send_email_message(user.email, message)
898
+ self.sent_count += 1
899
+
900
+ def _send_email_message(self, email, message):
901
+ print(f"Sending to {email}")
902
+ print(f"Subject: {message.subject}")
903
+ print(f"Body: {message.body}")
904
+
905
+
906
+ replace nested conditional with guard clauses
907
+
908
+ before:
909
+
910
+ def get_pay_amount(self):
911
+ result = 0
912
+ if self.is_dead:
913
+ result = self.dead_amount()
914
+ else:
915
+ if self.is_separated:
916
+ result = self.separated_amount()
917
+ else:
918
+ if self.is_retired:
919
+ result = self.retired_amount()
920
+ else:
921
+ result = self.normal_amount()
922
+ return result
923
+
924
+ after:
925
+
926
+ def get_pay_amount(self):
927
+ if self.is_dead:
928
+ return self.dead_amount()
929
+ if self.is_separated:
930
+ return self.separated_amount()
931
+ if self.is_retired:
932
+ return self.retired_amount()
933
+ return self.normal_amount()
934
+
935
+
936
+ PHASE 10: INTRODUCE PARAMETER OBJECT
937
+
938
+ when parameters always travel together, group them.
939
+
940
+
941
+ when to group parameters
942
+
943
+ signs you need parameter object:
944
+ - same parameters appear together repeatedly
945
+ - parameters form a coherent concept
946
+ - number of parameters exceeds 3-4
947
+
948
+
949
+ before: grouped parameters
950
+
951
+ def draw_graph(self, data, x_min, x_max, y_min, y_max, color, width):
952
+ # drawing logic
953
+ pass
954
+
955
+ def analyze_data(self, data, x_min, x_max, y_min, y_max):
956
+ # analysis logic
957
+ pass
958
+
959
+ def export_data(self, data, x_min, x_max, y_min, y_max, format):
960
+ # export logic
961
+ pass
962
+
963
+
964
+ after: parameter object
965
+
966
+ class DataRange:
967
+ def __init__(self, x_min, x_max, y_min, y_max):
968
+ self.x_min = x_min
969
+ self.x_max = x_max
970
+ self.y_min = y_min
971
+ self.y_max = y_max
972
+
973
+ def contains(self, x, y):
974
+ return self.x_min <= x <= self.x_max and \
975
+ self.y_min <= y <= self.y_max
976
+
977
+ def draw_graph(self, data, range, style):
978
+ # drawing logic
979
+ pass
980
+
981
+ def analyze_data(self, data, range):
982
+ # analysis logic
983
+ pass
984
+
985
+ def export_data(self, data, range, format):
986
+ # export logic
987
+ pass
988
+
989
+
990
+ benefits:
991
+
992
+ - fewer parameters
993
+ - can add behavior to the object
994
+ - easier to add new related data
995
+ - clearer intent
996
+
997
+
998
+ PHASE 11: REMOVE DEAD CODE
999
+
1000
+ dead code has no tests and is not used.
1001
+ remove it without mercy.
1002
+
1003
+
1004
+ find dead code
1005
+
1006
+ check for unused imports:
1007
+ <terminal>python -m flake8 src/ --select=F401</terminal>
1008
+
1009
+ check for unused variables:
1010
+ <terminal>python -m pylint src/ --disable=all --enable=unused-variable</terminal>
1011
+
1012
+ check for unreachable code:
1013
+ <terminal>python -m vulture src/</terminal>
1014
+
1015
+
1016
+ grep for potential dead code
1017
+
1018
+ <terminal>grep -r "TODO.*remove" src/</terminal>
1019
+ <terminal>grep -r "FIXME.*deprecated" src/</terminal>
1020
+ <terminal>grep -r "def.*_old\|class.*_old" src/</terminal>
1021
+
1022
+
1023
+ check for unused functions
1024
+
1025
+ <terminal>grep -r "def " src/ | while read line; do
1026
+ func=$(echo $line | grep -o "def [a-z_]*" | cut -d' ' -f2)
1027
+ if [ -n "$func" ]; then
1028
+ count=$(grep -r "$func(" src/ tests/ | wc -l)
1029
+ if [ $count -le 1 ]; then
1030
+ echo "Possibly unused: $func"
1031
+ fi
1032
+ fi
1033
+ done</terminal>
1034
+
1035
+
1036
+ before removing dead code
1037
+
1038
+ [1] verify no tests reference it
1039
+ [2] search codebase for references
1040
+ [3] check git history for why it exists
1041
+
1042
+ <terminal>grep -r "function_name" src/ tests/</terminal>
1043
+
1044
+ <terminal>git log --all --source --full-history -S "function_name"</terminal>
1045
+
1046
+
1047
+ remove safely
1048
+
1049
+ <read><file>src/module.py</file></read>
1050
+
1051
+ <edit>
1052
+ <file>src/module.py</file>
1053
+ <find>
1054
+ # Old implementation, kept for reference
1055
+ def old_function():
1056
+ pass
1057
+ </find>
1058
+ <replace>
1059
+ </replace>
1060
+ </edit>
1061
+
1062
+ <terminal>python -m pytest tests/ -v</terminal>
1063
+
1064
+ tests should pass.
1065
+ dead code by definition has no tests.
1066
+
1067
+
1068
+ PHASE 12: SIMPLIFY CONDITIONAL LOGIC
1069
+
1070
+ complex conditionals indicate missing concepts.
1071
+
1072
+
1073
+ consolidate duplicate conditions
1074
+
1075
+ before:
1076
+
1077
+ if customer.is_vip:
1078
+ discount = 0.1
1079
+ if customer.tier == "VIP":
1080
+ discount = 0.1
1081
+
1082
+ after:
1083
+
1084
+ if customer.is_vip:
1085
+ discount = 0.1
1086
+
1087
+
1088
+ use De Morgan's laws
1089
+
1090
+ before:
1091
+
1092
+ if not (customer.is_vip or customer.is_new):
1093
+ apply_standard_pricing()
1094
+
1095
+ after:
1096
+
1097
+ if not customer.is_vip and not customer.is_new:
1098
+ apply_standard_pricing()
1099
+
1100
+
1101
+ before:
1102
+
1103
+ if not customer.is_vip and not customer.is_new:
1104
+ apply_standard_pricing()
1105
+
1106
+ after:
1107
+
1108
+ if not (customer.is_vip or customer.is_new):
1109
+ apply_standard_pricing()
1110
+
1111
+
1112
+ reverse conditionals for clarity
1113
+
1114
+ before:
1115
+
1116
+ def process_order(self, order):
1117
+ if order.is_valid:
1118
+ if order.has_items:
1119
+ if order.customer.can_pay:
1120
+ self._process(order)
1121
+ else:
1122
+ return "Payment failed"
1123
+ else:
1124
+ return "No items"
1125
+ else:
1126
+ return "Invalid order"
1127
+
1128
+ after:
1129
+
1130
+ def process_order(self, order):
1131
+ if not order.is_valid:
1132
+ return "Invalid order"
1133
+ if not order.has_items:
1134
+ return "No items"
1135
+ if not order.customer.can_pay:
1136
+ return "Payment failed"
1137
+
1138
+ self._process(order)
1139
+
1140
+
1141
+ PHASE 13: COMMON ANTI-PATTERNS AND FIXES
1142
+
1143
+
1144
+ anti-pattern: god method
1145
+
1146
+ signs:
1147
+ - method over 50 lines
1148
+ - multiple levels of nesting
1149
+ - does many different things
1150
+
1151
+ fix:
1152
+ - extract methods for each responsibility
1153
+ - decompose conditionals
1154
+ - use guard clauses
1155
+
1156
+ before:
1157
+
1158
+ def process(self, data):
1159
+ if data:
1160
+ for item in data:
1161
+ if item.type == "A":
1162
+ if item.value > 0:
1163
+ result = self._calculate_a(item.value)
1164
+ self._save(result)
1165
+ else:
1166
+ self._log("Invalid value")
1167
+ elif item.type == "B":
1168
+ if item.value > 0:
1169
+ result = self._calculate_b(item.value)
1170
+ self._save(result)
1171
+ else:
1172
+ self._log("Invalid value")
1173
+ return "Done"
1174
+
1175
+ after:
1176
+
1177
+ def process(self, data):
1178
+ if not data:
1179
+ return "Done"
1180
+
1181
+ for item in data:
1182
+ self._process_item(item)
1183
+ return "Done"
1184
+
1185
+ def _process_item(self, item):
1186
+ if item.value <= 0:
1187
+ self._log("Invalid value")
1188
+ return
1189
+
1190
+ if item.type == "A":
1191
+ self._process_type_a(item)
1192
+ elif item.type == "B":
1193
+ self._process_type_b(item)
1194
+
1195
+ def _process_type_a(self, item):
1196
+ result = self._calculate_a(item.value)
1197
+ self._save(result)
1198
+
1199
+ def _process_type_b(self, item):
1200
+ result = self._calculate_b(item.value)
1201
+ self._save(result)
1202
+
1203
+
1204
+ anti-pattern: feature envy
1205
+
1206
+ signs:
1207
+ - method uses more data from another class
1208
+ - method "belongs" in another class
1209
+
1210
+ fix: move the method
1211
+
1212
+ before:
1213
+
1214
+ class Order:
1215
+ def __init__(self, customer):
1216
+ self.customer = customer
1217
+
1218
+ def get_discounted_total(self):
1219
+ if self.customer.tier == "VIP":
1220
+ return self.total * 0.9
1221
+ elif self.customer.tier == "LOYAL":
1222
+ return self.total * 0.95
1223
+ return self.total
1224
+
1225
+ after:
1226
+
1227
+ class Order:
1228
+ def __init__(self, customer):
1229
+ self.customer = customer
1230
+
1231
+ def get_discounted_total(self):
1232
+ return self.customer.get_discounted_price(self.total)
1233
+
1234
+
1235
+ anti-pattern: primitive obsession
1236
+
1237
+ signs:
1238
+ - using primitives instead of small objects
1239
+ - related primitives travel together
1240
+
1241
+ fix: extract class
1242
+
1243
+ before:
1244
+
1245
+ def calculate_shipping(self, street, city, state, zip_code):
1246
+ # shipping logic using all these parameters
1247
+ pass
1248
+
1249
+ def validate_address(self, street, city, state, zip_code):
1250
+ # validation logic
1251
+ pass
1252
+
1253
+ after:
1254
+
1255
+ class Address:
1256
+ def __init__(self, street, city, state, zip_code):
1257
+ self.street = street
1258
+ self.city = city
1259
+ self.state = state
1260
+ self.zip_code = zip_code
1261
+
1262
+ def validate(self):
1263
+ # validation logic
1264
+ pass
1265
+
1266
+ def get_shipping_cost(self):
1267
+ # shipping logic
1268
+ pass
1269
+
1270
+
1271
+ PHASE 14: IDE REFACTORING TOOLS
1272
+
1273
+
1274
+ VS Code refactoring
1275
+
1276
+ install python extension:
1277
+ <terminal>code --install-extension ms-python.python</terminal>
1278
+
1279
+ refactoring shortcuts:
1280
+ - F2: rename symbol
1281
+ - Ctrl+Shift+R (Mac: Cmd+Shift+R): refactor preview
1282
+ - Ctrl+. (Mac: Cmd+.): quick fix
1283
+ - F12: go to definition
1284
+
1285
+
1286
+ PyCharm refactoring
1287
+
1288
+ refactoring menu:
1289
+ - Shift+F6: rename
1290
+ - Ctrl+Alt+M: extract method
1291
+ - Ctrl+Alt+V: extract variable
1292
+ - Ctrl+Alt+P: extract parameter
1293
+ - F6: move
1294
+ - Ctrl+Alt+N: inline
1295
+
1296
+
1297
+ command line tools
1298
+
1299
+ rope (python refactoring library):
1300
+ <terminal>pip install rope</terminal>
1301
+
1302
+ <terminal>rope refactor.py --extract-method extract_user_validation</terminal>
1303
+
1304
+ rope can:
1305
+ - extract method
1306
+ - rename
1307
+ - move
1308
+ - inline
1309
+ - extract variable
1310
+
1311
+
1312
+ bowler (code refactoring tool):
1313
+ <terminal>pip install bowler</terminal>
1314
+
1315
+ # create a refactoring script
1316
+ <terminal>cat > refactor_fixme.py << 'EOF'
1317
+ import bowler
1318
+
1319
+ def rename_old_function(command):
1320
+ (
1321
+ command
1322
+ .capture("old_func = 'old_function_name'")
1323
+ .rename("new_function_name")
1324
+ )
1325
+
1326
+ bowler.Query(".py").modify(rename_old_function).execute()
1327
+ EOF
1328
+
1329
+ <terminal>python refactor_fixme.py --diff</terminal>
1330
+
1331
+
1332
+ safe refactoring with git
1333
+
1334
+ create refactoring branch:
1335
+ <terminal>git checkout -b refactor/extract-validation</terminal>
1336
+
1337
+ after each small refactor:
1338
+ <terminal>git add -A && git commit -m "refactor: extract user validation"</terminal>
1339
+
1340
+ view progress:
1341
+ <terminal>git log --oneline refactor/extract-validation</terminal>
1342
+
1343
+ when done:
1344
+ <terminal>git checkout main</terminal>
1345
+ <terminal>git merge refactor/extract-validation --squash</terminal>
1346
+ <terminal>git commit -m "refactor: extract user validation to separate methods"</terminal>
1347
+
1348
+
1349
+ PHASE 15: REFACTORING RULES (STRICT MODE)
1350
+
1351
+
1352
+ while this skill is active, these rules are MANDATORY:
1353
+
1354
+ [1] NEVER refactor without tests
1355
+ tests are your safety net
1356
+ no tests, no refactoring, no exceptions
1357
+
1358
+ [2] run tests after EVERY change
1359
+ every single change, no matter how small
1360
+ <terminal>python -m pytest tests/ -v</terminal>
1361
+
1362
+ [3] commit when tests pass
1363
+ small commits are your friend
1364
+ make rollback trivial
1365
+ <terminal>git commit -m "refactor: description"</terminal>
1366
+
1367
+ [4] revert if tests fail
1368
+ dont try to fix broken tests
1369
+ revert immediately
1370
+ try a smaller change
1371
+
1372
+ [5] one refactoring at a time
1373
+ dont batch multiple changes
1374
+ test between each
1375
+ commit often
1376
+
1377
+ [6] never change behavior
1378
+ structure only
1379
+ if behavior changes, its not refactoring
1380
+ tests prove behavior unchanged
1381
+
1382
+ [7] keep changes small
1383
+ if you hesitate, make it smaller
1384
+ you can always make another change
1385
+
1386
+ [8] refactor only when green
1387
+ fix tests first
1388
+ never refactor broken tests
1389
+
1390
+
1391
+ PHASE 16: REFACTORING SESSION CHECKLIST
1392
+
1393
+
1394
+ before starting:
1395
+
1396
+ [ ] tests exist and pass
1397
+ [ ] working directory clean or on branch
1398
+ [ ] baseline coverage recorded
1399
+ [ ] refactoring goal identified
1400
+
1401
+ during refactoring:
1402
+
1403
+ [ ] tests pass before each change
1404
+ [ ] change is small and focused
1405
+ [ ] tests pass after each change
1406
+ [ ] change is committed
1407
+ [ ] progress toward goal
1408
+
1409
+ after completing:
1410
+
1411
+ [ ] all tests pass
1412
+ [ ] coverage unchanged or improved
1413
+ [ ] code is clearer
1414
+ [ ] no behavior changes
1415
+ [ ] commit message is clear
1416
+
1417
+
1418
+ FINAL REMINDERS
1419
+
1420
+
1421
+ refactoring is discipline
1422
+
1423
+ it requires patience.
1424
+ it requires tests.
1425
+ it requires small steps.
1426
+
1427
+ the discipline pays off in:
1428
+ - code you can understand
1429
+ - features you can add quickly
1430
+ - bugs you can fix safely
1431
+
1432
+
1433
+ the golden rule
1434
+
1435
+ tests must pass before and after.
1436
+ if tests fail, you broke it.
1437
+ revert and try again.
1438
+
1439
+
1440
+ when in doubt
1441
+
1442
+ make the change smaller.
1443
+ you can always make another small change.
1444
+ small changes are safe changes.
1445
+
1446
+
1447
+ the goal
1448
+
1449
+ code that is easy to understand.
1450
+ code that is easy to change.
1451
+ code that does what it says.
1452
+ no more, no less.
1453
+
1454
+ now go clean up some code.