@@ -12,7 +12,7 @@ const execAsync = promisify(exec);
12
12
const kind2_guide = await fs . readFile ( new URL ( './KIND2_GUIDE_AI.md' , import . meta. url ) , 'utf-8' ) ;
13
13
14
14
// System prompt for the AI model, defining its role and behavior
15
- const system = `
15
+ const system_KindCoder = `
16
16
You are KindCoder, a Kind2-Lang coding assistant.
17
17
18
18
# USER INPUT
@@ -114,7 +114,189 @@ file inside a RESULT tag, completing the task successfully. Good job!
114
114
The user will now give you a Kind2 file, and a change request. Read it carefully
115
115
and update it as demanded. Consult the guides above as necessary. Pay attention
116
116
to syntax details, like mandatory parenthesis, to emit valid code. Do it now:
117
- ` ;
117
+ ` . trim ( ) ;
118
+
119
+ const system_DepsPredictor = `
120
+ # ABOUT KIND2
121
+
122
+ Kind2 is a minimal purely functional programming language, where every file
123
+ defines exactly ONE function, type or constant. For example:
124
+
125
+ '''
126
+ // Nat/add.kind2: defines Nat addition
127
+
128
+ use Nat/{succ,zero}
129
+
130
+ add
131
+ - a: Nat
132
+ - b: Nat
133
+ : Nat
134
+
135
+ match a {
136
+ succ: (succ (add a.pred b))
137
+ zero: b
138
+ }
139
+ '''
140
+
141
+ The file above implements the global 'Nat/add' definition.
142
+
143
+ # INPUT
144
+
145
+ You will be given the NAME of a Kind2 file, its source code (which may be
146
+ empty), and a list of ALL Kind2 definitions available in the stdlib.
147
+
148
+ # OUTPUT
149
+
150
+ You must answer with a list of definitions that are, or that you predict WILL BE
151
+ used, directly or not, inside that Kind2 file. Answer in a <DEPENDENCIES/> tag.
152
+
153
+ Optionally, you can also include a SHORT, 1-paragraph <JUSTIFICATION/>.
154
+
155
+ # EXAMPLE INPUT
156
+
157
+ <NAME>Nat/equal</NAME>
158
+
159
+ <SOURCE>
160
+ </SOURCE>
161
+
162
+ <DEFINITIONS>
163
+ - List/
164
+ - cons
165
+ - nil
166
+ - match
167
+ - map
168
+ - fold
169
+ - filter
170
+ - zip
171
+ - length
172
+ - Nat/
173
+ - match
174
+ - fold
175
+ - succ
176
+ - zero
177
+ - add
178
+ - sub
179
+ - mul
180
+ - div
181
+ - mod
182
+ - pow
183
+ - lte
184
+ - gte
185
+ - Bool/
186
+ - match
187
+ - fold
188
+ - true
189
+ - false
190
+ - not
191
+ - and
192
+ - or
193
+ - xor
194
+ - nand
195
+ </DEFINITION>
196
+
197
+ # EXAMPLE OUTPUT
198
+
199
+ <JUSTIFICATION>
200
+ Nat/equal is likely to be a pairwise comparison between Nats. As such, it must
201
+ include Nat (obviously), as well as its constructor and match. It returns a
202
+ Bool, so, it must also include its constructors and match. For completion, I've
203
+ also added boolean AND and OR, since these are commonly used in comparison.
204
+ </JUSTIFICATION>
205
+ <DEPENDENCIES>
206
+ Nat
207
+ Nat/succ
208
+ Nat/zero
209
+ Nat/match
210
+ Bool
211
+ Bool/true
212
+ Bool/false
213
+ Bool/match
214
+ Bool/and
215
+ Bool/or
216
+ </DEPENDENCIES>
217
+
218
+ # HINTS
219
+
220
+ - Attempt to include ALL files that might be relevant, directly or not.
221
+
222
+ - If the file is the constructor of an ADT, then, INCLUDE its type.
223
+ Example: 'List/cons' MUST include 'List'
224
+
225
+ - When in doubt, prefer to include MORE, rather than LESS, potencial dependencies.
226
+
227
+ - Try to include AT LEAST 8 dependencies, and AT MOST 32.
228
+
229
+ - Sometimes the user will give hints in the file. Follow them.
230
+ ` . trim ( ) ;
231
+
232
+ // Function to predict dependencies
233
+ async function predictDependencies ( name , fileContent ) {
234
+ // Function to get all Kind2 files recursively
235
+ async function getAllKind2Files ( dir ) {
236
+ const entries = await fs . readdir ( dir , { withFileTypes : true } ) ;
237
+ const files = await Promise . all ( entries . map ( async ( entry ) => {
238
+ const res = path . resolve ( dir , entry . name ) ;
239
+ if ( entry . isDirectory ( ) ) {
240
+ const subFiles = await getAllKind2Files ( res ) ;
241
+ return subFiles . length > 0 ? { name : entry . name , children : subFiles } : null ;
242
+ } else if ( entry . name . endsWith ( '.kind2' ) ) {
243
+ return { name : entry . name . replace ( '.kind2' , '' ) } ;
244
+ }
245
+ return null ;
246
+ } ) ) ;
247
+ return files . filter ( file => file !== null ) ;
248
+ }
249
+
250
+ // Function to build a tree structure from files
251
+ function buildTree ( files , prefix = '' ) {
252
+ let result = '' ;
253
+ for ( const file of files ) {
254
+ if ( file . children ) {
255
+ result += `${ prefix } - ${ file . name } /\n` ;
256
+ result += buildTree ( file . children , `${ prefix } ` ) ;
257
+ } else {
258
+ result += `${ prefix } - ${ file . name } \n` ;
259
+ }
260
+ }
261
+ return result ;
262
+ }
263
+
264
+ const allFiles = await getAllKind2Files ( path . join ( path . dirname ( name ) , '..' , '..' , 'book' ) ) ;
265
+ const defsTree = buildTree ( allFiles ) ;
266
+
267
+ const aiInput = [
268
+ `<NAME>${ name } </NAME>` ,
269
+ '<SOURCE>' ,
270
+ fileContent . trim ( ) ,
271
+ '</SOURCE>' ,
272
+ '<DEFINITIONS>' ,
273
+ defsTree . trim ( ) ,
274
+ '</DEFINITIONS>'
275
+ ] . join ( '\n' ) . trim ( ) ;
276
+
277
+ const aiOutput = await chat ( "h" ) ( aiInput , { system : system_DepsPredictor , model : "h" } ) ;
278
+ console . log ( "" ) ;
279
+
280
+ const dependenciesMatch = aiOutput . match ( / < D E P E N D E N C I E S > ( [ \s \S ] * ) < \/ D E P E N D E N C I E S > / ) ;
281
+ if ( ! dependenciesMatch ) {
282
+ console . error ( "Error: AI output does not contain a valid DEPENDENCIES tag." ) ;
283
+ return [ ] ;
284
+ }
285
+
286
+ return dependenciesMatch [ 1 ] . trim ( ) . split ( '\n' ) . map ( dep => dep . trim ( ) ) ;
287
+ }
288
+
289
+ // Function to perform type checking based on file extension
290
+ async function typeCheck ( file ) {
291
+ let ext = path . extname ( file ) ;
292
+ let cmd = `kind2 check ${ file } ` ;
293
+ try {
294
+ var result = await execAsync ( cmd ) ;
295
+ return result . stderr . trim ( ) || result . stdout . trim ( ) ;
296
+ } catch ( error ) {
297
+ return error . stderr . trim ( ) ;
298
+ }
299
+ }
118
300
119
301
// Main function to handle the refactoring process
120
302
async function main ( ) {
@@ -136,70 +318,20 @@ async function main() {
136
318
let fileContent = await fs . readFile ( file , 'utf-8' ) ;
137
319
let dirContent = await fs . readdir ( dir ) ;
138
320
139
- // New functionality: Handle lines starting with '//@'
140
- let extraFiles = [ ] ;
141
- fileContent = fileContent . split ( '\n' ) . filter ( line => {
142
- if ( line . startsWith ( '//@' ) ) {
143
- extraFiles . push ( line . slice ( 3 ) ) ;
144
- return false ;
145
- }
146
- return true ;
147
- } ) . join ( '\n' ) ;
148
-
149
321
// If the request is empty, replace it by a default request.
150
322
if ( request . trim ( ) === '' ) {
151
323
request = [
152
324
"Update this file." ,
153
- "- If it is empty, implement the *Initial Template* ." ,
154
- "- If it has holes, fill them, up to \"one layer\". Don't fully complete it. " ,
325
+ "- If it is empty, implement an initial template ." ,
326
+ "- If it has holes, fill them, up to \"one layer\"." ,
155
327
"- If it has no holes, fully complete it, as much as possible."
156
328
] . join ( '\n' ) ;
157
329
}
158
330
159
331
// If the file is empty, ask the AI to fill with an initial template
160
332
if ( fileContent . trim ( ) === '' ) {
161
-
162
- const getAllKind2Files = async ( dir ) => {
163
- const entries = await fs . readdir ( dir , { withFileTypes : true } ) ;
164
- const files = await Promise . all ( entries . map ( async ( entry ) => {
165
- const res = path . resolve ( dir , entry . name ) ;
166
- if ( entry . isDirectory ( ) ) {
167
- const subFiles = await getAllKind2Files ( res ) ;
168
- return { name : entry . name , children : subFiles } ;
169
- } else if ( entry . name . endsWith ( '.kind2' ) ) {
170
- return { name : entry . name } ;
171
- }
172
- return null ;
173
- } ) ) ;
174
- return files . filter ( Boolean ) ;
175
- } ;
176
-
177
- const buildTree = ( files , prefix = '' ) => {
178
- let result = '' ;
179
- for ( const file of files ) {
180
- if ( file . children ) {
181
- result += `${ prefix } - ${ file . name } /\n` ;
182
- result += buildTree ( file . children , `${ prefix } ` ) ;
183
-
184
- } else {
185
- result += `${ prefix } - ${ file . name } \n` ;
186
- }
187
- }
188
- return result ;
189
- } ;
190
-
191
- const allFiles = await getAllKind2Files ( path . join ( dir , '..' , '..' , 'book' ) ) ;
192
- const relativeFilesString = buildTree ( allFiles ) ;
193
-
194
333
fileContent = [
195
- "This is a new, empty file." ,
196
- "" ,
197
- "Please replace this file with a Kind2 definition, including:" ,
198
- "- 'use' imports (read the list above to decide which are relevant)" ,
199
- "- the function name and its arguments (based on the file name)" ,
200
- "- a hole in the place of its body (just `?body`)" ,
201
- "" ,
202
- "Example Initial Template:" ,
334
+ "This file is empty. Please replace it with a Kind2 definition. Example:" ,
203
335
"" ,
204
336
"```kind2" ,
205
337
"/// Does foo." ,
@@ -223,25 +355,29 @@ async function main() {
223
355
"- x1: X1" ,
224
356
"..." ,
225
357
"" ,
226
- "? body" ,
358
+ "body" ,
227
359
"```" ,
228
- "" ,
229
- "Do not complete the file yet. Just write this *Initial Template*." ,
230
- "Exception: if this should be a 'data' declaration, fully complete it." ,
231
- "" ,
232
- "[HINT] Below is a list of ALL files in the book:" ,
233
- relativeFilesString ,
234
360
] . join ( '\n' ) ;
235
361
}
236
362
237
363
// Extract the definition name from the file path
238
364
let defName = file . split ( '/book/' ) [ 1 ] . replace ( '.kind2' , '' ) ;
239
365
240
- // Get dependencies
241
- let depsCmd = `kind2 deps ${ defName } ` ;
242
- let { stdout : depsOutput } = await execAsync ( depsCmd ) ;
243
- let deps = depsOutput . trim ( ) . split ( '\n' ) ;
244
- deps = [ ...new Set ( [ ...deps , ...extraFiles ] ) ] ;
366
+ // Collect direct and indirect dependencies
367
+ let deps ;
368
+ try {
369
+ let { stdout } = await execAsync ( `kind2 deps ${ defName } ` ) ;
370
+ deps = stdout . trim ( ) . split ( '\n' ) ;
371
+ } catch ( e ) {
372
+ deps = [ ] ;
373
+ }
374
+
375
+ // Predict additional dependencies
376
+ const predictedDeps = await predictDependencies ( defName , fileContent ) ;
377
+ //console.log(JSON.stringify(predictedDeps,null,2));
378
+ //process.exit();
379
+ deps = [ ...new Set ( [ ...deps , ...predictedDeps ] ) ] ;
380
+ deps = deps . filter ( dep => dep !== defName ) ;
245
381
246
382
// Read dependent files
247
383
let depFiles = await Promise . all ( deps . map ( async ( dep ) => {
@@ -263,7 +399,7 @@ async function main() {
263
399
} ) ) ;
264
400
265
401
// Perform initial type checking
266
- let initialCheck = await typeCheck ( defName ) ;
402
+ let initialCheck = ( await typeCheck ( defName ) ) . replace ( / \x1b \[ [ 0 - 9 ; ] * m / g , '' ) ;
267
403
268
404
// Prepare AI input
269
405
let aiInput = [
@@ -279,13 +415,11 @@ async function main() {
279
415
'</REQUEST>'
280
416
] . join ( '\n' ) . trim ( ) ;
281
417
282
- // TODO: write a .prompt file with the system + aiInput strings
283
-
284
418
// Write a .prompt file with the system + aiInput strings
285
- await fs . writeFile ( '.kindcoder' , system + '\n\n' + aiInput , 'utf-8' ) ;
419
+ await fs . writeFile ( '.kindcoder' , system_KindCoder + '\n\n' + aiInput , 'utf-8' ) ;
286
420
287
421
// Call the AI model
288
- let aiOutput = await ask ( aiInput , { system, model } ) ;
422
+ let aiOutput = await ask ( aiInput , { system : system_KindCoder , model } ) ;
289
423
290
424
// Extract the result from AI output
291
425
let resultMatch = aiOutput . match ( / < R E S U L T > ( [ \s \S ] * ) < \/ R E S U L T > / ) ;
@@ -302,17 +436,6 @@ async function main() {
302
436
console . log ( "File updated successfully." ) ;
303
437
}
304
438
305
- // Function to perform type checking based on file extension
306
- async function typeCheck ( file ) {
307
- let ext = path . extname ( file ) ;
308
- let cmd = `kind2 check ${ file } ` ;
309
- try {
310
- var result = await execAsync ( cmd ) ;
311
- return result . stderr . trim ( ) || result . stdout . trim ( ) ;
312
- } catch ( error ) {
313
- return error . stderr . trim ( ) ;
314
- }
315
- }
316
-
317
439
// Run the main function and handle any errors
318
440
main ( ) . catch ( console . error ) ;
441
+
0 commit comments