code-towel 1.0.0__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.
@@ -0,0 +1,722 @@
1
+ Metadata-Version: 2.4
2
+ Name: code-towel
3
+ Version: 1.0.0
4
+ Summary: A Python tool that DRYs your code - finds and refactors repeated code using unification-based analysis
5
+ Author: Eric Allen
6
+ License: Apache License
7
+ Version 2.0, January 2004
8
+ http://www.apache.org/licenses/
9
+
10
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
11
+
12
+ 1. Definitions.
13
+
14
+ "License" shall mean the terms and conditions for use, reproduction,
15
+ and distribution as defined by Sections 1 through 9 of this document.
16
+
17
+ "Licensor" shall mean the copyright owner or entity authorized by
18
+ the copyright owner that is granting the License.
19
+
20
+ "Legal Entity" shall mean the union of the acting entity and all
21
+ other entities that control, are controlled by, or are under common
22
+ control with that entity. For the purposes of this definition,
23
+ "control" means (i) the power, direct or indirect, to cause the
24
+ direction or management of such entity, whether by contract or
25
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
26
+ outstanding shares, or (iii) beneficial ownership of such entity.
27
+
28
+ "You" (or "Your") shall mean an individual or Legal Entity
29
+ exercising permissions granted by this License.
30
+
31
+ "Source" form shall mean the preferred form for making modifications,
32
+ including but not limited to software source code, documentation
33
+ source, and configuration files.
34
+
35
+ "Object" form shall mean any form resulting from mechanical
36
+ transformation or translation of a Source form, including but
37
+ not limited to compiled object code, generated documentation,
38
+ and conversions to other media types.
39
+
40
+ "Work" shall mean the work of authorship, whether in Source or
41
+ Object form, made available under the License, as indicated by a
42
+ copyright notice that is included in or attached to the work
43
+ (an example is provided in the Appendix below).
44
+
45
+ "Derivative Works" shall mean any work, whether in Source or Object
46
+ form, that is based on (or derived from) the Work and for which the
47
+ editorial revisions, annotations, elaborations, or other modifications
48
+ represent, as a whole, an original work of authorship. For the purposes
49
+ of this License, Derivative Works shall not include works that remain
50
+ separable from, or merely link (or bind by name) to the interfaces of,
51
+ the Work and Derivative Works thereof.
52
+
53
+ "Contribution" shall mean any work of authorship, including
54
+ the original version of the Work and any modifications or additions
55
+ to that Work or Derivative Works thereof, that is intentionally
56
+ submitted to Licensor for inclusion in the Work by the copyright owner
57
+ or by an individual or Legal Entity authorized to submit on behalf of
58
+ the copyright owner. For the purposes of this definition, "submitted"
59
+ means any form of electronic, verbal, or written communication sent
60
+ to the Licensor or its representatives, including but not limited to
61
+ communication on electronic mailing lists, source code control systems,
62
+ and issue tracking systems that are managed by, or on behalf of, the
63
+ Licensor for the purpose of discussing and improving the Work, but
64
+ excluding communication that is conspicuously marked or otherwise
65
+ designated in writing by the copyright owner as "Not a Contribution."
66
+
67
+ "Contributor" shall mean Licensor and any individual or Legal Entity
68
+ on behalf of whom a Contribution has been received by Licensor and
69
+ subsequently incorporated within the Work.
70
+
71
+ 2. Grant of Copyright License. Subject to the terms and conditions of
72
+ this License, each Contributor hereby grants to You a perpetual,
73
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
74
+ copyright license to reproduce, prepare Derivative Works of,
75
+ publicly display, publicly perform, sublicense, and distribute the
76
+ Work and such Derivative Works in Source or Object form.
77
+
78
+ 3. Grant of Patent License. Subject to the terms and conditions of
79
+ this License, each Contributor hereby grants to You a perpetual,
80
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
81
+ (except as stated in this section) patent license to make, have made,
82
+ use, offer to sell, sell, import, and otherwise transfer the Work,
83
+ where such license applies only to those patent claims licensable
84
+ by such Contributor that are necessarily infringed by their
85
+ Contribution(s) alone or by combination of their Contribution(s)
86
+ with the Work to which such Contribution(s) was submitted. If You
87
+ institute patent litigation against any entity (including a
88
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
89
+ or a Contribution incorporated within the Work constitutes direct
90
+ or contributory patent infringement, then any patent licenses
91
+ granted to You under this License for that Work shall terminate
92
+ as of the date such litigation is filed.
93
+
94
+ 4. Redistribution. You may reproduce and distribute copies of the
95
+ Work or Derivative Works thereof in any medium, with or without
96
+ modifications, and in Source or Object form, provided that You
97
+ meet the following conditions:
98
+
99
+ (a) You must give any other recipients of the Work or
100
+ Derivative Works a copy of this License; and
101
+
102
+ (b) You must cause any modified files to carry prominent notices
103
+ stating that You changed the files; and
104
+
105
+ (c) You must retain, in the Source form of any Derivative Works
106
+ that You distribute, all copyright, patent, trademark, and
107
+ attribution notices from the Source form of the Work,
108
+ excluding those notices that do not pertain to any part of
109
+ the Derivative Works; and
110
+
111
+ (d) If the Work includes a "NOTICE" text file as part of its
112
+ distribution, then any Derivative Works that You distribute must
113
+ include a readable copy of the attribution notices contained
114
+ within such NOTICE file, excluding those notices that do not
115
+ pertain to any part of the Derivative Works, in at least one
116
+ of the following places: within a NOTICE text file distributed
117
+ as part of the Derivative Works; within the Source form or
118
+ documentation, if provided along with the Derivative Works; or,
119
+ within a display generated by the Derivative Works, if and
120
+ wherever such third-party notices normally appear. The contents
121
+ of the NOTICE file are for informational purposes only and
122
+ do not modify the License. You may add Your own attribution
123
+ notices within Derivative Works that You distribute, alongside
124
+ or as an addendum to the NOTICE text from the Work, provided
125
+ that such additional attribution notices cannot be construed
126
+ as modifying the License.
127
+
128
+ You may add Your own copyright statement to Your modifications and
129
+ may provide additional or different license terms and conditions
130
+ for use, reproduction, or distribution of Your modifications, or
131
+ for any such Derivative Works as a whole, provided Your use,
132
+ reproduction, and distribution of the Work otherwise complies with
133
+ the conditions stated in this License.
134
+
135
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
136
+ any Contribution intentionally submitted for inclusion in the Work
137
+ by You to the Licensor shall be under the terms and conditions of
138
+ this License, without any additional terms or conditions.
139
+ Notwithstanding the above, nothing herein shall supersede or modify
140
+ the terms of any separate license agreement you may have executed
141
+ with Licensor regarding such Contributions.
142
+
143
+ 6. Trademarks. This License does not grant permission to use the trade
144
+ names, trademarks, service marks, or product names of the Licensor,
145
+ except as required for reasonable and customary use in describing the
146
+ origin of the Work and reproducing the content of the NOTICE file.
147
+
148
+ 7. Disclaimer of Warranty. Unless required by applicable law or
149
+ agreed to in writing, Licensor provides the Work (and each
150
+ Contributor provides its Contributions) on an "AS IS" BASIS,
151
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
152
+ implied, including, without limitation, any warranties or conditions
153
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
154
+ PARTICULAR PURPOSE. You are solely responsible for determining the
155
+ appropriateness of using or redistributing the Work and assume any
156
+ risks associated with Your exercise of permissions under this License.
157
+
158
+ 8. Limitation of Liability. In no event and under no legal theory,
159
+ whether in tort (including negligence), contract, or otherwise,
160
+ unless required by applicable law (such as deliberate and grossly
161
+ negligent acts) or agreed to in writing, shall any Contributor be
162
+ liable to You for damages, including any direct, indirect, special,
163
+ incidental, or consequential damages of any character arising as a
164
+ result of this License or out of the use or inability to use the
165
+ Work (including but not limited to damages for loss of goodwill,
166
+ work stoppage, computer failure or malfunction, or any and all
167
+ other commercial damages or losses), even if such Contributor
168
+ has been advised of the possibility of such damages.
169
+
170
+ 9. Accepting Warranty or Additional Liability. While redistributing
171
+ the Work or Derivative Works thereof, You may choose to offer,
172
+ and charge a fee for, acceptance of support, warranty, indemnity,
173
+ or other liability obligations and/or rights consistent with this
174
+ License. However, in accepting such obligations, You may act only
175
+ on Your own behalf and on Your sole responsibility, not on behalf
176
+ of any other Contributor, and only if You agree to indemnify,
177
+ defend, and hold each Contributor harmless for any liability
178
+ incurred by, or claims asserted against, such Contributor by reason
179
+ of your accepting any such warranty or additional liability.
180
+
181
+ END OF TERMS AND CONDITIONS
182
+
183
+ APPENDIX: How to apply the Apache License to your work.
184
+
185
+ To apply the Apache License to your work, attach the following
186
+ boilerplate notice, with the fields enclosed by brackets "[]"
187
+ replaced with your own identifying information. (Don't include
188
+ the brackets!) The text should be enclosed in the appropriate
189
+ comment syntax for the file format. We also recommend that a
190
+ file or class name and description of purpose be included on the
191
+ same "printed page" as the copyright notice for easier
192
+ identification within third-party archives.
193
+
194
+ Copyright [yyyy] [name of copyright owner]
195
+
196
+ Licensed under the Apache License, Version 2.0 (the "License");
197
+ you may not use this file except in compliance with the License.
198
+ You may obtain a copy of the License at
199
+
200
+ http://www.apache.org/licenses/LICENSE-2.0
201
+
202
+ Unless required by applicable law or agreed to in writing, software
203
+ distributed under the License is distributed on an "AS IS" BASIS,
204
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
205
+ See the License for the specific language governing permissions and
206
+ limitations under the License.
207
+
208
+ Classifier: Development Status :: 5 - Production/Stable
209
+ Classifier: Intended Audience :: Developers
210
+ Classifier: License :: OSI Approved :: Apache Software License
211
+ Classifier: Programming Language :: Python :: 3
212
+ Classifier: Programming Language :: Python :: 3.9
213
+ Classifier: Programming Language :: Python :: 3.10
214
+ Classifier: Programming Language :: Python :: 3.11
215
+ Classifier: Programming Language :: Python :: 3.12
216
+ Classifier: Programming Language :: Python :: 3.13
217
+ Classifier: Topic :: Software Development :: Quality Assurance
218
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
219
+ Requires-Python: >=3.9
220
+ Description-Content-Type: text/markdown
221
+ License-File: LICENSE
222
+ Provides-Extra: dev
223
+ Requires-Dist: coverage>=7.0.0; extra == "dev"
224
+ Requires-Dist: black>=23.0.0; extra == "dev"
225
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
226
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
227
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
228
+ Requires-Dist: pydocstyle>=6.0.0; extra == "dev"
229
+ Requires-Dist: tqdm>=4.66.0; extra == "dev"
230
+ Dynamic: license-file
231
+
232
+ # Towel
233
+
234
+ [![CI](https://github.com/ericeallen/towel/actions/workflows/ci.yml/badge.svg)](https://github.com/ericeallen/towel/actions/workflows/ci.yml)
235
+ [![codecov](https://codecov.io/gh/ericeallen/towel/branch/main/graph/badge.svg)](https://codecov.io/gh/ericeallen/towel)
236
+ [![PyPI](https://img.shields.io/pypi/v/code-towel.svg)](https://pypi.org/project/code-towel/)
237
+
238
+ **A Python tool that DRYs your code.**
239
+
240
+ Towel automatically detects and refactors violations of the DRY (Don't Repeat Yourself) principle in Python codebases using unification algorithms from automated theorem proving.
241
+
242
+ ## Testing Status
243
+
244
+ - **Unit Tests**: 125/125 passing (100%)
245
+ - **Observational Equivalence**: 175/175 refactoring proposals pass automated behavioral equivalence testing
246
+ - **Test Coverage**: 18 example files with comprehensive edge cases
247
+
248
+ All refactorings are verified to behave identically to the original code through automated observational equivalence testing.
249
+
250
+ ## Quick Start
251
+
252
+ The tool uses only Python stdlib with zero external dependencies.
253
+
254
+ ### Using Just (Recommended)
255
+
256
+ The easiest way to use the tool is with [just](https://github.com/casey/just):
257
+
258
+ ```bash
259
+ # Install just: brew install just (macOS) or cargo install just
260
+
261
+ # Preview refactoring opportunities (read-only)
262
+ just preview test_examples/
263
+
264
+ # Apply refactorings to a new directory
265
+ just dry test_examples/ cleaned_examples/
266
+
267
+ # Apply refactorings in-place (overwrites original)
268
+ just dry my_code/ my_code/
269
+
270
+ # Run tests
271
+ just test
272
+
273
+ # Check test coverage
274
+ just coverage-unification
275
+
276
+ # See all commands
277
+ just --list
278
+ ```
279
+
280
+ ### CLI Commands
281
+
282
+ After installation, Towel provides three main commands:
283
+
284
+ ```bash
285
+ # Preview duplicates (read-only)
286
+ code-towel preview <file_or_directory>
287
+
288
+ # Apply refactorings
289
+ code-towel dry <input> <output>
290
+
291
+ # Rename extracted functions with LLM assistance
292
+ code-towel rename-helpers <directory>
293
+ ```
294
+
295
+ ### Direct Script Usage
296
+
297
+ You can also run the scripts directly without installation:
298
+
299
+ ```bash
300
+ # Preview duplicates (read-only)
301
+ python3 scripts/preview <file_or_directory>
302
+
303
+ # Refactor code (writes to output location)
304
+ python3 scripts/dry <input> <output>
305
+ ```
306
+
307
+ ## Features
308
+
309
+ - **Zero External Dependencies**: Uses only Python standard library
310
+ - **Unification-Based Analysis**: Advanced algorithm from automated theorem proving
311
+ - **Hygienic Code Generation**: Generates functions with hygienically renamed parameters to avoid name conflicts. The generated parameter names like `__param_0` are intentionally generic and can be renamed to more meaningful names using an LLM coding assistant for better readability.
312
+ - **Referential Transparency**: Preserves referential transparency and maintains program semantics through careful scope analysis
313
+ - **Safe Refactoring**:
314
+ - Alpha-renaming for loop variables (treats `i` and `j` as equivalent)
315
+ - Return value propagation (detects returns anywhere in block)
316
+ - Orphan variable detection (prevents unsafe extractions)
317
+ - Builtin filtering (never parameterizes `len`, `print`, etc.)
318
+ - F-string handling (correct AST manipulation)
319
+ - **Smart Parameterization**:
320
+ - Constant parameterization (different numbers/strings become parameters)
321
+ - Structural comparison (only extracts truly similar code)
322
+ - Max parameter limits (prevents over-parameterization)
323
+ - **Class-Aware Method Extraction**:
324
+ - Promotes duplicate methods into the nearest shared base class when possible
325
+ - Preserves decorators (`@classmethod`, `@staticmethod`) and implicit binders (`self`/`cls`)
326
+ - Rewrites call sites across files to dispatch through the new helper correctly
327
+ - Falls back gracefully when no safe shared ancestor exists
328
+ - **Cross-File Support**: Automatically handles duplicates spanning multiple files
329
+ - **Comprehensive Testing**:
330
+ - 175/175 refactoring proposals pass observational equivalence testing
331
+ - 125 unit tests passing (100%)
332
+ - Automatic observational equivalence testing verifies refactored code behaves identically to original
333
+ - Tests 175 refactoring proposals across 18 example files automatically
334
+ - Intelligent test input generation based on AST analysis
335
+ - Recursive testing of returned functions (closures)
336
+ - See `tests/OBSERVATIONAL_EQUIVALENCE.md` for details
337
+
338
+ ## Installation
339
+
340
+ ### For Users
341
+
342
+ ```bash
343
+ # Install from PyPI (zero external dependencies)
344
+ pip install code-towel
345
+
346
+ # Or install from source
347
+ git clone https://github.com/ericeallen/towel.git
348
+ cd towel
349
+ pip install -e .
350
+ ```
351
+
352
+ ### For Developers
353
+
354
+ ```bash
355
+ # Create virtual environment
356
+ python3 -m venv venv
357
+ source venv/bin/activate
358
+
359
+ # Install with dev dependencies
360
+ pip install -e ".[dev]"
361
+
362
+ # Alternatively, install the specific tools you need
363
+ pip install black flake8 mypy coverage
364
+ ```
365
+
366
+ ## Usage
367
+
368
+ ### Preview Mode (Read-Only)
369
+
370
+ Preview refactoring opportunities without modifying files:
371
+
372
+ ```bash
373
+ # Preview a single file
374
+ python3 preview.py my_code.py
375
+
376
+ # Preview a directory
377
+ python3 preview.py src/
378
+ ```
379
+
380
+ ### Refactoring Mode
381
+
382
+ Apply refactorings to code:
383
+
384
+ ```bash
385
+ # Refactor to a new location (safe - doesn't overwrite)
386
+ python3 dry.py src/ src_refactored/
387
+
388
+ # Refactor a single file
389
+ python3 dry.py my_code.py my_code_clean.py
390
+
391
+ # Refactor in-place (overwrites original)
392
+ python3 dry.py src/ src/
393
+ python3 dry.py src/ src/ --progress detail --max-iterations 0 # verbose unlimited
394
+ python3 dry.py src/ src/ --progress none --max-iterations 100 # quiet capped
395
+ ```
396
+
397
+ ### Examples
398
+
399
+ Preview duplicates in test examples:
400
+
401
+ ```bash
402
+ just preview test_examples/
403
+ ```
404
+
405
+ Refactor a project to a new location:
406
+
407
+ ```bash
408
+ just dry my_project/ my_project_refactored/
409
+ ```
410
+
411
+ Test the tool with coverage:
412
+
413
+ ```bash
414
+ just coverage-unification
415
+ ```
416
+
417
+ ### Renaming Extracted Helpers with LLM Assistance
418
+
419
+ After running the DRY tool, extracted functions are named `__extracted_func_*` with generic parameter names like `__param_0`. Towel includes an interactive tool that uses LLM assistance to rename these into meaningful, human-readable names:
420
+
421
+ ```bash
422
+ # Interactive mode: Generate LLM prompt and apply suggestions
423
+ code-towel rename-helpers src/
424
+
425
+ # List all extracted helpers
426
+ code-towel rename-helpers src/ --list
427
+
428
+ # Apply renamings from a JSON file
429
+ code-towel rename-helpers src/ --rename-file renames.json
430
+
431
+ # Dry run (preview only)
432
+ code-towel rename-helpers src/ --dry-run
433
+
434
+ # Limit to specific files or functions
435
+ code-towel rename-helpers src/ --file mymodule.py
436
+ code-towel rename-helpers src/ --function __extracted_func_7
437
+ ```
438
+
439
+ #### How It Works
440
+
441
+ The `rename-helpers` command works in two modes:
442
+
443
+ **1. Interactive Mode (default):**
444
+ - Analyzes all `__extracted_func_*` functions in your code
445
+ - Generates a prompt showing each function's code
446
+ - You paste this prompt into Claude Code, ChatGPT, or any LLM
447
+ - The LLM suggests meaningful names based on what each function does
448
+ - You paste the LLM's JSON response back
449
+ - Towel automatically renames all references throughout your codebase
450
+
451
+ **2. File Mode (`--rename-file`):**
452
+ - Provide a JSON file mapping old names to new names
453
+ - Towel applies these renamings across your entire codebase
454
+
455
+ Example workflow:
456
+ ```bash
457
+ # Run the refactoring tool
458
+ code-towel dry src/ src_cleaned/
459
+
460
+ # Use LLM to rename extracted functions
461
+ code-towel rename-helpers src_cleaned/
462
+ # (Follow the interactive prompts to get LLM suggestions)
463
+ ```
464
+
465
+ The tool uses smart regex-based renaming to update all function definitions and calls throughout your project.
466
+
467
+ ## How It Works
468
+
469
+ The tool uses **unification** from automated theorem proving to detect and parameterize duplicates:
470
+
471
+ 1. **Parsing**: Parses Python files into ASTs using the `ast` module
472
+ 2. **Block Extraction**: Extracts all contiguous code blocks from functions
473
+ 3. **Unification**: Uses a nominal unification algorithm to find blocks that can be unified:
474
+ - Matches AST structure recursively
475
+ - Allows alpha-renaming of loop variables (`i` ≈ `j`)
476
+ - Parameterizes differing constants and expressions
477
+ - Respects Python builtin names and scoping rules
478
+ 4. **Orphan Detection**: Validates that extraction won't create undefined variable references
479
+ 5. **Function Extraction**: Generates hygienically-renamed extracted functions
480
+ 6. **Replacement Generation**: Creates function calls with correct parameter order
481
+ 7. **Cross-File Support**: Handles duplicates across multiple files with import generation
482
+
483
+ ## Safety Guarantees
484
+
485
+ The tool ensures safe refactorings by:
486
+
487
+ - **Orphan Variable Detection**: Never extracts code that binds variables used later
488
+ - **Return Value Propagation**: Detects return statements anywhere in block
489
+ - **Alpha-Renaming**: Treats loop variables `i`, `j`, `k` as equivalent binding constructs
490
+ - **Builtin Filtering**: Never parameterizes Python builtins (`len`, `print`, `range`, etc.)
491
+ - **F-String Handling**: Correct AST manipulation for f-strings (never parameterizes literal parts)
492
+ - **Comprehension Scoping**: Respects that comprehension variables are local to the comprehension
493
+ - **Structural Similarity**: Only unifies blocks with >60% structural similarity
494
+
495
+ ## Advanced Features
496
+
497
+ **Class-Aware Helper Promotion** – Duplicate instance, class, or static methods are automatically lifted into their nearest shared base class, even across different files:
498
+
499
+ - Builds an inheritance table while scanning the project
500
+ - Chooses the most specific shared ancestor for the extracted helper
501
+ - Preserves method semantics (decorators, implicit parameters, and call dispatch)
502
+ - Emits the helper in the ancestor class and rewrites original methods to dispatch through it, inserting imports only when needed
503
+
504
+ **Orphan Variable Detection** – Prevents unsafe extractions that would create undefined variables:
505
+ ```python
506
+ # Rejects this unsafe extraction:
507
+ def compute():
508
+ x = 10
509
+ y = 20
510
+ total = x + y
511
+ return total # Would leave 'total' undefined if lines 1-3 extracted alone
512
+ ```
513
+
514
+ **Return Value Propagation** – Detects returns anywhere in code blocks:
515
+ ```python
516
+ # Correctly generates: return extracted_func()
517
+ if x > 100:
518
+ return y * 2 # Nested return automatically detected
519
+ ```
520
+
521
+ **Alpha-Renaming for Loop Variables** - Treats `i`, `j`, `k` as equivalent binding variables:
522
+ ```python
523
+ for i in range(10): # Unifies with
524
+ for j in range(10): # this block
525
+ ```
526
+
527
+ ### Progress & Iteration Feedback
528
+
529
+ The directory fixed-point refactoring loop supports progress modes via `--progress`:
530
+
531
+ | Mode | Description |
532
+ |---------|-------------|
533
+ | `tqdm` | Rich progress bar (applied count + queued proposals). |
534
+ | `auto` | Attempts `tqdm`; falls back to single-line textual bar. |
535
+ | `none` | Suppresses progress output (quiet / CI). |
536
+ | `detail`| Verbose: lists discovered proposals (first 25) and localized follow-ups. |
537
+
538
+ `refactor_directory_to_fixed_point` returns `(results_dict, termination_reason)` where `termination_reason` is `fixed_point` (no proposals remain) or `iteration_cap` (stopped due to `--max-iterations N`). Use `--max-iterations 0` (default) for unlimited iterations until a fixed point.
539
+
540
+ Example (detail mode):
541
+
542
+ ```text
543
+ [towel] Analyzing 26 file(s)...
544
+ [towel] Discovered 128 proposal(s)
545
+ 1. Extract common code from state_machine_pattern_v1 and state_machine_pattern_v2
546
+ 2. Extract common code from deeply_nested_computation_v1 and deeply_nested_computation_v2
547
+ ...
548
+ ```
549
+
550
+ Localized follow-ups: after each applied proposal the engine re-analyzes only changed files and prepends new opportunities to the queue for faster chained extraction.
551
+
552
+ ## Testing
553
+
554
+ ### Observational Equivalence Testing
555
+
556
+ Towel includes **automatic observational equivalence testing** that verifies refactored code behaves identically to the original:
557
+
558
+ ```bash
559
+ # Run observational equivalence tests
560
+ just test-observational
561
+
562
+ # Run comprehensive automatic tests on all 18 example files
563
+ python -m unittest tests.test_observational_equivalence.TestAutomaticObservationalEquivalence -v
564
+ ```
565
+
566
+ **Features:**
567
+ - **175/175 proposals pass (100%)** across **18 example files**
568
+ - No manual test configuration needed - extracts function names from proposals
569
+ - Intelligent test input generation using AST analysis:
570
+ - Detects tuple unpacking: `for a, b in pairs:` → generates `[('a', 1), ('b', 2)]`
571
+ - Detects dictionary access: `data['key']` → generates `{'key': 'value'}`
572
+ - Uses type annotations and parameter name heuristics
573
+ - Recursive testing: When functions return functions (closures), tests the returned functions for behavioral equivalence
574
+ - **100% success rate** - all refactorings preserve program behavior
575
+
576
+ See `tests/OBSERVATIONAL_EQUIVALENCE.md` for complete documentation.
577
+
578
+ ### Unit Tests
579
+
580
+ Run the comprehensive unit test suite:
581
+
582
+ ```bash
583
+ # Run all 125 unit tests (100% passing)
584
+ just test
585
+
586
+ # Run with coverage
587
+ just coverage-unification
588
+
589
+ # Generate HTML coverage report
590
+ just coverage-html
591
+
592
+ # Test specific aspects
593
+ just test-bindings # Binding constructs (for loops, comprehensions)
594
+ just test-returns # Return value propagation
595
+ just test-fstrings # F-string handling
596
+ just test-orphans # Orphan variable detection
597
+ just test-engine # End-to-end refactoring
598
+
599
+ ### Fast Smoke Suite
600
+
601
+ During inner-loop development you can run a curated fast subset (proposal generation, stability, and observational equivalence on representative examples) instead of the full suite:
602
+
603
+ ```bash
604
+ just test-smoke
605
+ ```
606
+
607
+ Use this for quick validation (< a few minutes). Run the full suite (`just test` or `just test-all`) before pushing or releasing.
608
+ ```
609
+
610
+ ## Project Structure
611
+
612
+ ```
613
+ src/towel/ # Core package (towel for compatibility)
614
+ └── unification/ # Unification-based refactoring system
615
+ ├── refactor_engine.py # Main refactoring engine
616
+ ├── unifier.py # Nominal unification algorithm
617
+ ├── extractor.py # Hygienic function extraction
618
+ ├── scope_analyzer.py # Variable scope analysis
619
+ ├── orphan_detector.py # Orphan variable detection
620
+ └── builtins.py # Python builtin filtering
621
+
622
+ scripts/
623
+ ├── dry # Main refactoring tool
624
+ ├── preview # Read-only preview tool
625
+ └── verify-examples # Verification tool
626
+
627
+ tests/ # 125 comprehensive unit tests
628
+ test_examples/ # 18 example files with duplicates
629
+ docs/ # Documentation
630
+ ```
631
+
632
+ ## Examples
633
+
634
+ The `test_examples/` directory contains examples with DRY violations:
635
+
636
+ - `example1_simple.py`: Simple repeated validation logic
637
+ - `example4_complex.py`: Complex data processing loops
638
+ - `bindings_for_loops.py`: Loop variable edge cases
639
+ - `bindings_comprehensions.py`: List/dict/set comprehensions
640
+ - `return_values.py`: Return value propagation scenarios
641
+ - `fstrings_constants.py`: F-string and constant handling
642
+
643
+ ## Justfile Commands
644
+
645
+ Run `just --list` to see all commands, or `just help` for detailed help.
646
+
647
+ ### Common Commands
648
+
649
+ ```bash
650
+ # For users
651
+ just dry <input> <output> # Refactor code (writes to output)
652
+ just preview <target> # Preview duplicates (read-only)
653
+ just help # Show detailed help
654
+
655
+ # For developers
656
+ just test # Run all 97 unit tests
657
+ just coverage-unification # Check coverage (91%)
658
+ just coverage-html # Generate HTML coverage report
659
+ just check # Run all code quality checks (format, lint, typecheck)
660
+ just clean # Clean generated files
661
+ just reset-examples # Reset example files to original state
662
+
663
+ # Documentation
664
+ just docs # Show usage guide
665
+ just docs-cross-file # Show cross-file refactoring docs
666
+ just docs-directory # Show directory usage docs
667
+ ```
668
+
669
+ ### Examples
670
+
671
+ ```bash
672
+ # Analyze test examples
673
+ just analyze test_examples
674
+
675
+ # Preview what would change in src/
676
+ just preview src/
677
+
678
+ # Run all tests
679
+ just test-all
680
+
681
+ # Apply refactoring to example3
682
+ just refactor-example3
683
+ ```
684
+
685
+ ## Python API - Unification-Based Approach
686
+
687
+ The tool uses a unification-based approach for principled refactoring:
688
+
689
+ ```python
690
+ from towel.unification.refactor_engine import UnificationRefactorEngine
691
+
692
+ # Create engine
693
+ engine = UnificationRefactorEngine(
694
+ max_parameters=5, # Max parameters for extracted functions
695
+ min_lines=4 # Minimum lines for code blocks
696
+ )
697
+
698
+ # Analyze entire directory (finds cross-file duplicates)
699
+ proposals = engine.analyze_directory("src/")
700
+
701
+ # Apply refactorings
702
+ for proposal in proposals:
703
+ modified_files = engine.apply_refactoring_multi_file(proposal)
704
+ for file_path, content in modified_files.items():
705
+ with open(file_path, 'w') as f:
706
+ f.write(content)
707
+ ```
708
+
709
+ See [docs/USAGE_GUIDE.md](docs/USAGE_GUIDE.md) for complete API documentation.
710
+
711
+ ## Documentation
712
+
713
+ - **[docs/USAGE_GUIDE.md](docs/USAGE_GUIDE.md)** - Complete usage guide
714
+ - **[docs/CROSS_FILE_REFACTORING.md](docs/CROSS_FILE_REFACTORING.md)** - Cross-file refactoring details
715
+ - **[docs/README_DIRECTORY_USAGE.md](docs/README_DIRECTORY_USAGE.md)** - Directory analysis guide
716
+ - **[docs/UNIFICATION_IMPLEMENTATION.md](docs/UNIFICATION_IMPLEMENTATION.md)** - Implementation details
717
+ - **[docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)** - Common issues and solutions
718
+ - **[docs/JUSTFILE_REFERENCE.md](docs/JUSTFILE_REFERENCE.md)** - Complete justfile command reference
719
+
720
+ ## License
721
+
722
+ Apache 2.0