Skip to content

Commit 5735934

Browse files
committed
KindCoder dependency predictor
1 parent caa0e21 commit 5735934

File tree

2 files changed

+213
-92
lines changed

2 files changed

+213
-92
lines changed

KIND2_GUIDE_AI.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,9 @@ use Nat/{succ,zero}
100100
This locally expands 'succ' and 'zero' to 'Nat/succ' and 'Nat/zero'. It is
101101
specially useful to avoid typing full constructor names on 'match' cases.
102102

103-
Note this is different from the `use x = value` syntax, which is used to create
104-
a local definition that, unlike `let`, is expanded statically (inlined).
105-
106-
Also note that, for better organization, Kind2 will also look for a definition
107-
in two places: `Def/name.kind2` and `Def/name/_.kind2`. This allows us to, for
108-
example, place `Nat` on `book/Nat/_.kind2` instead of `book/Nat.kind2`.
103+
NOTE: when a definition is not found in `Foo/Bar.kind2`, Kind2 will try to
104+
look for it on `Foo/Bar/_.kind2`. The `_` is just a placeholder and is NOT
105+
part of the definition's name.
109106

110107
### Pattern-Matching
111108

@@ -150,8 +147,9 @@ switch x = expr {
150147

151148
### Other Sugars
152149

153-
- Lists: `[a, b, c]`
154-
- Equality: `{a = b}`
150+
- Lists: `[a, b, c]` (desugars to cons/nil)
151+
- Strings: `"foo"` (desugars to lists of u48 codepoints)
152+
- Equality: `{a = b}` (desugars to `(Equal _ a b)`)
155153
- Function: `A -> B` (desugars to `∀(x_: A) B`)
156154
- Implicit: `(foo/ x y)` (desugars to `(foo _ _ ... x y)`, auto-filling erased args)
157155
- Comments: `// comment here`

kindcoder.mjs

Lines changed: 207 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const execAsync = promisify(exec);
1212
const kind2_guide = await fs.readFile(new URL('./KIND2_GUIDE_AI.md', import.meta.url), 'utf-8');
1313

1414
// System prompt for the AI model, defining its role and behavior
15-
const system = `
15+
const system_KindCoder = `
1616
You are KindCoder, a Kind2-Lang coding assistant.
1717
1818
# USER INPUT
@@ -114,7 +114,189 @@ file inside a RESULT tag, completing the task successfully. Good job!
114114
The user will now give you a Kind2 file, and a change request. Read it carefully
115115
and update it as demanded. Consult the guides above as necessary. Pay attention
116116
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(/<DEPENDENCIES>([\s\S]*)<\/DEPENDENCIES>/);
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+
}
118300

119301
// Main function to handle the refactoring process
120302
async function main() {
@@ -136,70 +318,20 @@ async function main() {
136318
let fileContent = await fs.readFile(file, 'utf-8');
137319
let dirContent = await fs.readdir(dir);
138320

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-
149321
// If the request is empty, replace it by a default request.
150322
if (request.trim() === '') {
151323
request = [
152324
"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\".",
155327
"- If it has no holes, fully complete it, as much as possible."
156328
].join('\n');
157329
}
158330

159331
// If the file is empty, ask the AI to fill with an initial template
160332
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-
194333
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:",
203335
"",
204336
"```kind2",
205337
"/// Does foo.",
@@ -223,25 +355,29 @@ async function main() {
223355
"- x1: X1",
224356
"...",
225357
"",
226-
"?body",
358+
"body",
227359
"```",
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,
234360
].join('\n');
235361
}
236362

237363
// Extract the definition name from the file path
238364
let defName = file.split('/book/')[1].replace('.kind2', '');
239365

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);
245381

246382
// Read dependent files
247383
let depFiles = await Promise.all(deps.map(async (dep) => {
@@ -263,7 +399,7 @@ async function main() {
263399
}));
264400

265401
// Perform initial type checking
266-
let initialCheck = await typeCheck(defName);
402+
let initialCheck = (await typeCheck(defName)).replace(/\x1b\[[0-9;]*m/g, '');
267403

268404
// Prepare AI input
269405
let aiInput = [
@@ -279,13 +415,11 @@ async function main() {
279415
'</REQUEST>'
280416
].join('\n').trim();
281417

282-
// TODO: write a .prompt file with the system + aiInput strings
283-
284418
// 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');
286420

287421
// Call the AI model
288-
let aiOutput = await ask(aiInput, { system, model });
422+
let aiOutput = await ask(aiInput, { system: system_KindCoder, model });
289423

290424
// Extract the result from AI output
291425
let resultMatch = aiOutput.match(/<RESULT>([\s\S]*)<\/RESULT>/);
@@ -302,17 +436,6 @@ async function main() {
302436
console.log("File updated successfully.");
303437
}
304438

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-
317439
// Run the main function and handle any errors
318440
main().catch(console.error);
441+

0 commit comments

Comments
 (0)