-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathaider-agile.el
173 lines (166 loc) · 11 KB
/
aider-agile.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
;;; aider-agile.el --- Agile practices operations for Aider -*- lexical-binding: t; -*-
;; Author: Kang Tu <[email protected]>
;; SPDX-License-Identifier: Apache-2.0
;;; Commentary:
;; This file provides agile practice operations such as refactoring and TDD cycle
;; for the Aider package. Extracted from aider-code-change.el.
;;; Code:
(require 'aider-core)
(require 'aider-file)
;;;###autoload
(defun aider-refactor-book-method ()
"Apply famous refactoring techniques from Martin Fowler's book.
Uses current context (function, class, selected region) to generate appropriate prompts.
Works across different programming languages."
(interactive)
(let* ((region-active (region-active-p))
(region-text (when region-active
(buffer-substring-no-properties (region-beginning) (region-end))))
(current-function (which-function))
(file-name (when buffer-file-name (file-name-nondirectory buffer-file-name)))
(context-description (cond
(current-function (format "in function '%s'" current-function))
(file-name (format "in file '%s'" file-name))
(t "in current context")))
;; Provide different refactoring techniques based on whether a region is selected
(refactoring-techniques
(if region-active
;; Refactoring techniques for selected regions
'(("Extract Method" . "Extract the selected code into a new method named [METHOD_NAME]. Identify parameters and return values needed, and place the new method in an appropriate location.")
("Extract Variable" . "Replace this complex expression with a well-named variable [VARIABLE_NAME]. Choose a name that clearly explains the expression's purpose.")
("Extract Parameter" . "Extract this expression into a new parameter named [PARAMETER_NAME] for the containing function. Update all call sites to pass this value as an argument.")
("Extract Field" . "Extract this expression into a class field named [FIELD_NAME]. Initialize the field appropriately and replace the expression with a reference to the field.")
("Decompose Conditional" . "Break down this complex conditional into smaller, more readable pieces. Extract conditions and branches into well-named methods that express the high-level logic."))
;; Refactoring techniques for entire functions or files
'(("Rename Variable/Method" . "Rename [CURRENT_NAME] to [NEW_NAME]. Ensure all references are updated consistently following naming conventions appropriate for this codebase.")
("Inline Method" . "Replace calls to method [METHOD_NAME] with its body. Ensure the inlining doesn't change behavior or introduce bugs, and remove the original method if it's no longer needed.")
("Inline Variable" . "Replace all references to variable [VARIABLE_NAME] with its value. Ensure the inlining doesn't change behavior or introduce bugs.")
("Move Method" . "Move method [METHOD_NAME] to class [TARGET_CLASS]. Update all references to use the new location and consider creating a delegation if needed.")
("Replace Conditional with Polymorphism" . "Replace this conditional logic with polymorphic objects. Create appropriate class hierarchy and move conditional branches to overridden methods.")
("Introduce Parameter Object" . "Replace these related parameters with a single parameter object named [OBJECT_NAME]. Create an appropriate class for the parameter object."))))
(technique-names (mapcar #'car refactoring-techniques))
(prompt (if region-active
"Select refactoring technique for selected region: "
"Select refactoring technique: "))
(selected-technique (completing-read prompt technique-names nil t))
(technique-description (cdr (assoc selected-technique refactoring-techniques)))
;; Replace placeholders with user input for techniques that need parameters
(prompt-with-params
(cond
((string= selected-technique "Extract Method")
(let ((method-name (read-string "New method name: ")))
(replace-regexp-in-string "\\[METHOD_NAME\\]" method-name technique-description t)))
((string= selected-technique "Rename Variable/Method")
(let* ((current-name (or (thing-at-point 'symbol t)
(read-string "Current name: ")))
(new-name (read-string (format "Rename '%s' to: " current-name))))
(replace-regexp-in-string
"\\[NEW_NAME\\]" new-name
(replace-regexp-in-string
"\\[CURRENT_NAME\\]" current-name technique-description t)
t)))
((string= selected-technique "Inline Method")
(let ((method-name (or (thing-at-point 'symbol t)
(read-string "Method to inline: "))))
(replace-regexp-in-string "\\[METHOD_NAME\\]" method-name technique-description t)))
((string= selected-technique "Inline Variable")
(let ((variable-name (or (thing-at-point 'symbol t)
(read-string "Variable to inline: "))))
(replace-regexp-in-string "\\[VARIABLE_NAME\\]" variable-name technique-description t)))
((string= selected-technique "Move Method")
(let ((method-name (or current-function
(read-string "Method to move: ")))
(target-class (read-string "Target class: ")))
(replace-regexp-in-string
"\\[TARGET_CLASS\\]" target-class
(replace-regexp-in-string
"\\[METHOD_NAME\\]" method-name technique-description t)
t)))
((string= selected-technique "Extract Variable")
(let ((var-name (read-string "New variable name: ")))
(replace-regexp-in-string "\\[VARIABLE_NAME\\]" var-name technique-description t)))
((string= selected-technique "Extract Parameter")
(let ((param-name (read-string "New parameter name: ")))
(replace-regexp-in-string "\\[PARAMETER_NAME\\]" param-name technique-description t)))
((string= selected-technique "Introduce Parameter Object")
(let ((object-name (read-string "Parameter object name: ")))
(replace-regexp-in-string "\\[OBJECT_NAME\\]" object-name technique-description t)))
((string= selected-technique "Extract Field")
(let ((field-name (read-string "New field name: ")))
(replace-regexp-in-string "\\[FIELD_NAME\\]" field-name technique-description t)))
(t technique-description)))
(initial-final-instruction (format "%s %s. %s"
selected-technique
context-description
prompt-with-params))
(final-instruction (aider-read-string "Edit refactoring instruction: " initial-final-instruction))
(command (if region-active
(format "\"%s\n\nSelected code:\n%s\""
final-instruction
region-text)
(format "\"%s\"" final-instruction))))
(aider-current-file-command-and-switch "/architect " command)
(message "%s refactoring request sent to Aider. After code refactored, better to re-run unit-tests." selected-technique)))
;;;###autoload
(defun aider-tdd-cycle ()
"Guide through Test Driven Development cycle (Red-Green-Refactor).
Helps users follow Kent Beck's TDD methodology with AI assistance.
Works with both source code and test files that have been added to aider."
(interactive)
(let* ((current-file (buffer-file-name))
(is-test-file (and current-file
(string-match-p "\\(test\\|spec\\)"
(file-name-nondirectory current-file))))
(function-name (which-function))
(cycle-stage (completing-read
"Select TDD stage: "
'("1. Red (Write failing test)"
"2. Green (Make test pass)"
"3. Refactor (Improve code quality)")
nil t))
(stage-num (string-to-number (substring cycle-stage 0 1))))
(cond
;; Red stage - write failing test
((= stage-num 1)
(let* ((initial-input
(if function-name
(format "Write a failing test for function '%s': " function-name)
"Write a failing test for this feature: "))
(feature-desc (aider-read-string "Describe the feature to test: " initial-input))
(tdd-instructions
(format "%s Follow TDD principles - write only the test now, not the implementation. The test should fail when run because the functionality doesn't exist yet."
feature-desc)))
(aider-current-file-command-and-switch "/architect " tdd-instructions)))
;; Green stage - make test pass
((= stage-num 2)
(let* ((initial-input
(if function-name
(format "Implement function '%s' with minimal code to make tests pass: " function-name)
"Implement the minimal code needed to make the failing test pass: "))
(implementation-desc (aider-read-string "Implementation instruction: " initial-input))
(tdd-instructions
(format "%s Follow TDD principles - implement only the minimal code needed to make the test pass. Don't over-engineer or implement features not required by the test."
implementation-desc)))
(aider-current-file-command-and-switch "/architect " tdd-instructions)))
;; Refactor stage
((= stage-num 3)
(let* ((context-desc (if function-name
(format "in function '%s'" function-name)
"in this code"))
(initial-input (format "Refactor the code %s while ensuring all tests continue to pass: " context-desc))
(refactoring-suggestions
(list
(format "Improve naming of variables and functions %s" context-desc)
(format "Extract duplicated code into helper methods %s" context-desc)
(format "Simplify complex conditionals %s" context-desc)
(format "Improve code organization and structure %s" context-desc)))
(refactor-desc (aider-read-string
"Describe the refactoring needed: "
initial-input
refactoring-suggestions))
(tdd-instructions
(format "%s Follow TDD principles - improve code quality without changing behavior. Ensure all tests still pass after refactoring."
refactor-desc)))
(aider-current-file-command-and-switch "/architect " tdd-instructions))))))
(provide 'aider-agile)
;;; aider-agile.el ends here