-
Notifications
You must be signed in to change notification settings - Fork 40
/
refactor.mjs
executable file
·268 lines (206 loc) · 6.33 KB
/
refactor.mjs
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/usr/bin/env node
import fs from 'fs/promises';
import os from 'os';
import path from 'path';
import process from "process";
import { chat, MODELS, tokenCount } from './Chat.mjs';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
// System prompt for the AI model, defining its role and behavior
const system = `
You are a file refactoring tool.
- INPUT: You will receive a FILE and a change REQUEST.
- OUTPUT: You must answer with the changed file inside <RESULT></RESULT> tags.
# GUIDE FOR REFACTORING
1. Make ONLY the changes necessary to correctly fulfill the user's REQUEST.
2. Do NOT fix, remove, complete, or alter any parts unrelated to the REQUEST.
3. Do not include any additional comments, explanations, or text outside of the RESULT tags.
4. NEVER assume information you don't have. ALWAYS request files to make sure.
5. Preserve the same indentation and style of the current file.
6. Be precise and careful in your modifications.
# GUIDE FOR NAVIGATING
In some cases, you WILL need additional context to fulfill a request. When that is the case, do NOT attempt to refactor the file immediatelly. Instead, ask for additional files inside <SHOW></SHOW> tags, as follows:
<SHOW>
["./README.md", "./src/some_file.js", "./src/some_dir"]
</SHOW>
You can ask for information as many times as you want.
# GUIDE FOR AGDA STYLE
Every Agda file MUST:
1. Start with a "module" declaration.
2. Must have ONLY ONE top-level definition.
3. Export ALL its in-scope values>
Example file:
\`\`\`
module Term where
{-# NO_TERMINATION_CHECK #-}
data Term : Set where
var : Term
abs : (Term → Term) → Term
app : Term → Term → Term
open Term public using (Term; var; abs; app)
\`\`\`
# EXAMPLE
Below is a complete example of how you should interact with the user.
## User:
<DIR path="/Users/v/dev/nat-lib" current>
Bool.agda
Nat.agda
is_zero.agda
</DIR>
<FILE path="./is_zero.agda" current>
module is_zero where
open import Nat
open import Bool
is_zero : Nat -> Bool
is_zero n = ?
</FILE>
<REQUEST>
case-split on n
</REQUEST>
## You:
<REQUEST>
["./Nat.agda"]
</REQUEST>
## User:
<FILE path="./Nat.agda">
module Nat where
data Nat : Set where
zero : Nat
succ : Nat → Nat
open Nat public using (Nat; zero; succ)
</FILE>
## You:
<RESULT>
module is_zero where
open import Nat
open import Bool
is_zero : Nat -> Bool
is_zero zero = ?
is_zero (succ n) = ?
</RESULT>
## User:
<REQUEST>
complete it
</REQUEST>
## You:
<REQUEST>
["/.Bool.agda"]
</REQUEST>
## User:
<FILE path="./Bool.agda">
module Bool where
data Bool : Set where
true : Bool
false : Bool
open Bool public using (Bool; true; false)
</FILE>
## You:
<RESULT>
module is_zero where
open import Nat
open import Bool
is_zero : Nat -> Bool
is_zero zero = true
is_zero (succ n) = false
</RESULT>
`;
// Main function to handle the refactoring process
async function main() {
// Check for correct usage and parse command-line arguments
if (process.argv.length < 3) {
console.log("Usage: refactor <file> <request> [<model>] [--check]");
process.exit(1);
}
const file = process.argv[2];
const request = process.argv[3];
const model = process.argv[4] || "s";
const check = process.argv.includes("--check");
// Initialize the chat function with the specified model
const ask = chat(model);
// Get directory and file information
const dir = path.dirname(file);
const fileContent = await fs.readFile(file, 'utf-8');
const dirContent = await fs.readdir(dir);
// Prepare initial input for the AI
let aiInput = `<DIR path="${dir}" current>\n${dirContent.join('\n')}\n</DIR>\n\n<FILE path="${file}" current>\n${fileContent}\n</FILE>\n\n<REQUEST>\n${request}\n</REQUEST>`;
// If --check flag is present, perform initial type check
if (check) {
const initialCheck = await typeCheck(file);
aiInput += `\n\n<CHECK>\n${initialCheck || 'No errors.'}\n</CHECK>`;
}
// Main interaction loop with the AI
while (true) {
console.log("");
const aiOutput = await ask(aiInput, { system, model });
// Handle AI's request for additional information
if (aiOutput.includes("<SHOW>")) {
const showMatch = aiOutput.match(/<SHOW>([\s\S]*?)<\/SHOW>/);
if (showMatch) {
const filesToShow = JSON.parse(showMatch[1]);
let showContent = "";
for (const fileToShow of filesToShow) {
const fullPath = path.resolve(dir, fileToShow);
if (await fs.stat(fullPath).then(stat => stat.isDirectory())) {
const dirContent = await fs.readdir(fullPath);
showContent += `<DIR path="${fullPath}">\n${dirContent.join('\n')}\n</DIR>\n`;
} else {
const content = await fs.readFile(fullPath, 'utf-8');
showContent += `<FILE path="${fullPath}">\n${content}\n</FILE>\n`;
}
}
aiInput = showContent;
}
}
// Handle AI's refactoring result
else if (aiOutput.includes("<RESULT>")) {
const resultMatch = aiOutput.match(/<RESULT>([\s\S]*?)<\/RESULT>/);
if (resultMatch) {
const newContent = resultMatch[1];
await fs.writeFile(file, newContent.trim(), 'utf-8');
console.log("\nFile refactored successfully.");
// If --check flag is present, perform type check on the refactored file
if (check) {
const checkResult = await typeCheck(file);
if (checkResult) {
aiInput = `<FILE path="${file}" current>\n${newContent.trim()}\n</FILE>\n\n<REQUEST>\nFix this file.\n</REQUEST>\n\n<CHECK>\n${checkResult}\n</CHECK>`;
continue;
}
}
break;
}
}
}
}
// Function to perform type checking based on file extension
async function typeCheck(file) {
const ext = path.extname(file);
let cmd;
switch (ext) {
case '.agda':
cmd = `agda-check ${file}`;
break;
case '.kind2':
cmd = `kind2 check ${file}`;
break;
case '.c':
cmd = `gcc -fsyntax-only ${file}`;
break;
case '.ts':
cmd = `tsc --noEmit ${file}`;
break;
case '.hs':
cmd = `ghc -fno-code ${file}`;
break;
default:
return null;
}
try {
var result = await execAsync(cmd);
return result.stderr || result.stdout;
} catch (error) {
return error.stderr;
}
}
// Run the main function and handle any errors
main().catch(console.error);