forked from VictorTaelin/AI-scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkindcoder.mjs
More file actions
executable file
·455 lines (367 loc) · 11.2 KB
/
kindcoder.mjs
File metadata and controls
executable file
·455 lines (367 loc) · 11.2 KB
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
#!/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);
const kind2_guide = await fs.readFile(new URL('./KIND2_GUIDE_AI.md', import.meta.url), 'utf-8');
// System prompt for the AI model, defining its role and behavior
const system_KindCoder = `
You are KindCoder, a Kind2-Lang coding assistant.
# USER INPUT
You will receive:
1. A target <FILE/> in the Kind2 language. That's the code you must update.
2. The user's change <REQUEST/>. You must perform that change on the target file.
3. Some additional context (files, dirs) that could be helpful.
# KINDCODER OUTPUT
You, KindCoder, must answer with a single <RESULT/> tag, which must include the
user's file, except *modified* to fulfill the user's request, and nothing else.
# 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. Preserve the same indentation and style of the target FILE.
4. Consult Kind2's guide to emit syntactically correct code.
5. Be precise and careful in your modifications.
${kind2_guide}
# KINDCODER EXAMPLE
Below is a complete example of how KindCoder should interact with the user.
## User:
<FILE path="/Users/v/vic/dev/kind2/book/Nat/_.kind2">
...
</FILE>
<FILE path="/Users/v/vic/dev/kind2/book/Nat/succ.kind2">
...
</FILE>
<FILE path="/Users/v/vic/dev/kind2/book/Nat/zero.kind2">
...
</FILE>
<FILE path="/Users/v/vic/dev/kind2/book/Nat/is_even.kind2" target>
use Nat/{succ,zero}
is_even
- n: Nat
: Nat
?a
</FILE>
<CHECKER>
GOAL ?a : Nat
- n: Nat
</CHECKER>
<REQUEST>
case-split on n
</REQUEST>
## KindCoder:
<RESULT>
use Nat/{succ,zero}
is_even
- n: Nat
: Nat
match n {
zero: ?zero_case
succ: ?succ_case
}
</RESULT>
# EXPLANATION
## Input:
The user provided a target file (Nat/is_even) to be modified, and a request:
"case-split on n". The user also provided some additional files and dirs for
context (including Nat, Nat/succ, Nat/zero). The target file had an incomplete
top-level definition, 'is_even', with a hole, '?a', as its body.
## Output:
As a response, you, KindCoder, performed a case-split on 'n', by using Kind's
'match' syntax-sugar. You did NOT perform any extra work, nor change anything
beyond what the user explicitly asked for. Instead, you just placed NEW holes
('?n_is_zero'/'?n_is_succ') on the respective cases. You included the updated
file inside a RESULT tag, completing the task successfully. Good job!
# TASK
The user will now give you a Kind2 file, and a change request. Read it carefully
and update it as demanded. Consult the guides above as necessary. Pay attention
to syntax details, like mandatory parenthesis, to emit valid code. Do it now:
`.trim();
const system_DepsPredictor = `
# ABOUT KIND2
Kind2 is a minimal purely functional programming language, where every file
defines exactly ONE function, type or constant. For example:
'''
// Nat/add.kind2: defines Nat addition
use Nat/{succ,zero}
add
- a: Nat
- b: Nat
: Nat
match a {
succ: (succ (add a.pred b))
zero: b
}
'''
The file above implements the global 'Nat/add' definition.
# INPUT
You will be given the NAME of a Kind2 file, its source code (which may be
empty), and a list of ALL Kind2 definitions available in the stdlib.
# OUTPUT
You must answer with a list of definitions that are, or that you predict WILL BE
used, directly or not, inside that Kind2 file. Answer in a <DEPENDENCIES/> tag.
Optionally, you can also include a SHORT, 1-paragraph <JUSTIFICATION/>.
# EXAMPLE INPUT
<NAME>Nat/equal</NAME>
<SOURCE>
</SOURCE>
<DEFINITIONS>
- List/
- cons
- nil
- match
- map
- fold
- filter
- equal
- zip
- length
- Nat/
- match
- fold
- succ
- zero
- compare
- add
- sub
- mul
- div
- mod
- pow
- lte
- gte
- Bool/
- match
- fold
- true
- false
- not
- and
- or
- xor
- nand
</DEFINITION>
# EXAMPLE OUTPUT
<JUSTIFICATION>
Nat/equal is likely to be a pairwise comparison between Nats. As such, it must
include Nat (obviously), as well as its constructor and match. It returns a
Bool, so, it must also include its constructors and match. For completion, I've
also added bool AND and OR, since these are often used in comparison. Finally,
Nat/compare and List/equal might be similar algorithms, so, I included them.
</JUSTIFICATION>
<DEPENDENCIES>
Nat
Nat/succ
Nat/zero
Nat/match
Bool
Bool/true
Bool/false
Bool/match
Bool/and
Bool/or
Nat/compare
List/equal
</DEPENDENCIES>
# HINTS
- Attempt to include ALL files that might be relevant, directly or not.
- Always include files that might be similar algorithms to the current one.
Example: 'Map/set' MUST include 'Mat/get'
- If the file is the constructor of an ADT, then, INCLUDE its type.
Example: 'List/cons' MUST include 'List'
- When in doubt, prefer to include MORE, rather than LESS, potencial dependencies.
- Try to include AT LEAST 4 dependencies, and AT MOST (only if needed) 16.
- Sometimes the user will give hints in the file. Follow them.
`.trim();
// Function to predict dependencies
async function predictDependencies(name, fileContent) {
// Function to get all Kind2 files recursively
async function getAllKind2Files(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = await Promise.all(entries.map(async (entry) => {
const res = path.resolve(dir, entry.name);
if (entry.isDirectory()) {
const subFiles = await getAllKind2Files(res);
return subFiles.length > 0 ? { name: entry.name, children: subFiles } : null;
} else if (entry.name.endsWith('.kind2')) {
return { name: entry.name.replace('.kind2', '') };
}
return null;
}));
return files.filter(file => file !== null).map(file => ({...file, name: file.name.replace(/\/_$/, '')}));
}
// Function to build a tree structure from files
function buildTree(files, prefix = '') {
let result = '';
for (const file of files) {
if (file.children) {
result += `${prefix}- ${file.name}/\n`;
result += buildTree(file.children, `${prefix} `);
} else {
result += `${prefix}- ${file.name}\n`;
}
}
return result;
}
const allFiles = await getAllKind2Files("./");
const defsTree = buildTree(allFiles);
const aiInput = [
`<NAME>${name}</NAME>`,
'<SOURCE>',
fileContent.trim(),
'</SOURCE>',
'<DEFINITIONS>',
defsTree.trim(),
'</DEFINITIONS>'
].join('\n').trim();
const aiOutput = await chat("s")(aiInput, { system: system_DepsPredictor, model: "s" });
console.log("");
const dependenciesMatch = aiOutput.match(/<DEPENDENCIES>([\s\S]*)<\/DEPENDENCIES>/);
if (!dependenciesMatch) {
console.error("Error: AI output does not contain a valid DEPENDENCIES tag.");
return [];
}
return dependenciesMatch[1].trim().split('\n').map(dep => dep.trim());
}
// Function to perform type checking based on file extension
async function typeCheck(file) {
let ext = path.extname(file);
let cmd = `kind2 check ${file}`;
try {
var result = await execAsync(cmd);
return result.stderr.trim() || result.stdout.trim();
} catch (error) {
return error.stderr.trim();
}
}
// 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: kindcoder <file> <request> [<model>]");
process.exit(1);
}
let file = process.argv[2];
let request = process.argv[3];
let model = process.argv[4] || "s";
// Initialize the chat function with the specified model
let ask = chat(model);
// Get directory and file information
let dir = path.dirname(file);
let fileContent;
try {
fileContent = await fs.readFile(file, 'utf-8');
} catch (e) {
fileContent = "";
}
let dirContent = await fs.readdir(dir);
// If the request is empty, replace it by a default request.
if (request.trim() === '') {
request = [
"Update this file.",
"- If it is empty, implement an initial template.",
"- If it has holes, fill them, up to \"one layer\".",
"- If it has no holes, fully complete it, as much as possible."
].join('\n');
}
// If the file is empty, ask the AI to fill with an initial template
if (fileContent.trim() === '') {
fileContent = [
"This file is empty. Please replace it with a Kind2 definition. Example:",
"",
"```kind2",
"/// Does foo.",
"///",
"/// # Input",
"///",
"/// * `x0` - Description",
"/// * `x1` - Description",
"/// ...",
"///",
"/// # Output",
"///",
"/// The result of doing foo",
"",
"use Lib/A/{a,b}",
"use Lib/B/{c,d}",
"...",
"",
"foo <A> <B> ...",
"- x0: X0",
"- x1: X1",
"...",
"",
"body",
"```",
].join('\n');
}
// Extract the definition name from the file path
let defName = file.split('/book/')[1].replace('.kind2', '');
// Collect direct and indirect dependencies
let deps;
try {
let { stdout } = await execAsync(`kind2 deps ${defName}`);
deps = stdout.trim().split('\n');
} catch (e) {
deps = [];
}
// Predict additional dependencies
const predictedDeps = await predictDependencies(defName, fileContent);
//console.log(JSON.stringify(predictedDeps,null,2));
//process.exit();
deps = [...new Set([...deps, ...predictedDeps])];
deps = deps.filter(dep => dep !== defName);
// Read dependent files
let depFiles = await Promise.all(deps.map(async (dep) => {
let depPath, content;
let path0 = path.join(dir, '..', `${dep}.kind2`);
let path1 = path.join(dir, '..', `${dep}/_.kind2`);
try {
content = await fs.readFile(path0, 'utf-8');
depPath = path0;
} catch (error) {
try {
content = await fs.readFile(path1, 'utf-8');
depPath = path1;
} catch (error) {
return "";
}
}
return `<FILE path="${depPath}">\n${content}\n</FILE>`;
}));
// Perform initial type checking
let initialCheck = (await typeCheck(defName)).replace(/\x1b\[[0-9;]*m/g, '');
// Prepare AI input
let aiInput = [
...depFiles,
`<FILE path="${file}" target>`,
fileContent,
'</FILE>',
'<CHECKER>',
initialCheck,
'</CHECKER>',
'<REQUEST>',
request,
'</REQUEST>'
].join('\n').trim();
// Write a .prompt file with the system + aiInput strings
await fs.writeFile('.kindcoder', system_KindCoder + '\n\n' + aiInput, 'utf-8');
// Call the AI model
let aiOutput = await ask(aiInput, { system: system_KindCoder, model });
console.log("");
// Extract the result from AI output
let resultMatch = aiOutput.match(/<RESULT>([\s\S]*)<\/RESULT>/);
if (!resultMatch) {
console.error("Error: AI output does not contain a valid RESULT tag.");
process.exit(1);
}
let result = resultMatch[1].trim();
// Write the result back to the file
await fs.writeFile(file, result, 'utf-8');
console.log("File updated successfully.");
}
// Run the main function and handle any errors
main().catch(console.error);