@@ -45,6 +45,10 @@ Examples
4545 alias : "p" ,
4646 default : "expert node.js developer, creative, code optimizer, interaction expert" ,
4747 } ,
48+ neg : {
49+ type : "string" ,
50+ alias : "n" ,
51+ } ,
4852 temperature : {
4953 type : "number" ,
5054 alias : "t" ,
@@ -73,51 +77,67 @@ const configuration = new Configuration({
7377export const openai = new OpenAIApi ( configuration ) ;
7478
7579const instructions = `
76- The code should ALWAYS be IMPROVED or EXTENDED or REFACTORED or FIXED
77- be creative and add new features
78- The GOAL must be completed
79-
80- GOAL: ${ flags . goal }
80+ GOAL: ${ flags . goal } ${
81+ flags . neg
82+ ? `, ${ flags . neg
83+ . split ( "," )
84+ . map ( neg => `no ${ neg . trim ( ) } ` )
85+ . join ( ", " ) } `
86+ : ""
87+ }
8188
89+ RULES:
90+ The code should ALWAYS be EXTENDED or REFACTORED or FIXED
91+ The GOAL must be completed
8292increment the generation constant ONCE per generation
83- Keep track of changes, EXTEND the CHANGELOG
93+ EXTEND the CHANGELOG
8494NEVER use external apis with secret or key
85- ONLY use es module syntax (with imports)
95+ EXCLUSIVELY use esm ( imports)
8696NEVER explain anything
87- ALWAYS output ONLY JavaScript
97+ NEVER output markdown
98+ EXCLUSIVELY output JavaScript
99+ EVERYTHING happens in one file
100+ VERY IMPORTANT: the entire answer has to be valid JavaScript
88101` ;
89102
90103const history = [ ] ;
91104
92105export const generations = flags . generations ;
93- const maxSpawns = 3 ;
94- let spawns = maxSpawns ;
95106let run = 0 ;
96107
108+ /**
109+ *
110+ * @param {number } generation
111+ * @returns {Promise<void> }
112+ */
97113export async function evolve ( generation ) {
98114 if ( flags . help ) {
99115 return ;
100116 }
101- if ( spawns <= 0 ) {
102- spinner . fail ( "Maximum retries reached" ) ;
103- return ;
104- }
117+
105118 const nextGeneration = generation + 1 ;
119+ spinner . start ( `Evolution ${ generation } -> ${ nextGeneration } ` ) ;
106120
107121 try {
108122 const filename = buildFilename ( generation ) ;
109123 const code = await fs . readFile ( filename , "utf-8" ) ;
110- spinner . start ( `Generation ${ generation } | ${ spawns } spawns left` ) ;
124+
125+ // Reduce history length
126+ history . shift ( ) ;
127+ history . shift ( ) ;
111128
112129 if ( flags . clean ) {
113130 // Remove all older generations
114- const files = ( await globby ( [ "generation-*.js" , "!generation-000.js" ] ) ) . filter (
115- file => file > buildFilename ( generation )
116- ) ;
131+ const files = (
132+ await globby ( [ "generation-*.js" , "generation-*.md" , "! generation-000.js" ] )
133+ ) . filter ( file => file > buildFilename ( generation ) ) ;
117134 await Promise . all ( files . map ( async file => await fs . unlink ( file ) ) ) ;
118135 }
119136
120137 if ( run === 0 ) {
138+ const promptFilename = buildPromptFilename ( generation ) ;
139+ await fs . writeFile ( promptFilename , buildPrompt ( flags . goal , flags . neg ) ) ;
140+
121141 history . push (
122142 {
123143 role : "user" ,
@@ -129,12 +149,14 @@ export async function evolve(generation) {
129149 }
130150 ) ;
131151 }
152+
132153 run ++ ;
154+
133155 history . push ( {
134156 role : "user" ,
135157 content : "continue the code" ,
136158 } ) ;
137- spinner . start ( `Evolution ${ generation } -> ${ generation + 1 } ` ) ;
159+
138160 const completion = await openai . createChatCompletion ( {
139161 // model: "gpt-4",
140162 model : flags . model ,
@@ -148,47 +170,121 @@ export async function evolve(generation) {
148170 max_tokens : 2048 ,
149171 temperature : flags . temperature ,
150172 } ) ;
151- spinner . stop ( ) ;
173+
152174 const { content } = completion . data . choices [ 0 ] . message ;
153- const cleanContent = content
175+
176+ // Clean GPT output (might return code block)
177+ const cleanContent = minify ( content )
154178 . replace ( "```javascript" , "" )
155179 . replace ( "```js" , "" )
156- . replace ( "```" , "" ) ;
180+ . replace ( "```" , "" )
181+ . trim ( ) ;
182+
183+ // Code should start with a comment (changelog). If it doesn't it is often not JavaScrip but
184+ // a human language response
157185 if ( cleanContent . startsWith ( "/*" ) ) {
158186 history . push ( {
159187 role : "assistant" ,
160188 content : cleanContent ,
161189 } ) ;
190+
162191 const nextFilename = buildFilename ( nextGeneration ) ;
163192 await fs . writeFile ( nextFilename , prettify ( cleanContent ) ) ;
164- spinner . succeed ( `Evolution ${ generation } -> ${ generation + 1 } ` ) ;
193+
194+ spinner . succeed ( `Evolution ${ generation } -> ${ nextGeneration } ` ) ;
195+
165196 await import ( `./${ nextFilename } ` ) ;
166197 } else {
167- throw new Error ( "NOT_JAVASCRIPT" ) ;
198+ spinner . fail ( `Evolution ${ generation } -> ${ nextGeneration } ` ) ;
199+ await handleError ( new Error ( "NOT_JAVASCRIPT" ) , generation ) ;
168200 }
169201 } catch ( error ) {
170- spawns -- ;
171- spinner . fail ( `Evolution ${ generation } -> ${ generation + 1 } ` ) ;
202+ spinner . fail ( `Evolution ${ generation } -> ${ nextGeneration } ` ) ;
172203 await handleError ( error , generation ) ;
173204 }
174205}
175206
207+ /**
208+ * Pads the given number or string with zeros to a length of 3 characters.
209+ *
210+ * @param {number } n - The input number or string to be padded.
211+ * @returns {string } - The padded string.
212+ */
176213export function pad ( n ) {
177214 return n . toString ( ) . padStart ( 3 , "0" ) ;
178215}
179216
217+ /**
218+ * Builds a filename string for the given generation number.
219+ *
220+ * @param {number } currentGeneration - The input generation number.
221+ * @returns {string } - The generated filename string.
222+ */
180223export function buildFilename ( currentGeneration ) {
181224 return path . join ( "." , `generation-${ pad ( currentGeneration ) } .js` ) ;
182225}
183226
227+ /**
228+ * Builds a prompt filename string for the given generation number.
229+ *
230+ * @param {number } currentGeneration - The input generation number.
231+ * @returns {string } - The generated prompt filename string.
232+ */
233+ export function buildPromptFilename ( currentGeneration ) {
234+ return path . join ( "." , `generation-${ pad ( currentGeneration ) } .md` ) ;
235+ }
236+
237+ /**
238+ * Builds a formatted string combining the given prompt and optional negativePrompt.
239+ *
240+ * @param {string } prompt - The main prompt to be included in the output.
241+ * @param {string } [negativePrompt] - The optional negative prompt to be included in the output.
242+ * @returns {string } - The formatted string combining the prompts.
243+ */
244+ export function buildPrompt ( prompt , negativePrompt = "" ) {
245+ return `# Configuration
246+
247+ ## Prompt
248+
249+ \`\`\`shell
250+ ${ prompt }
251+ \`\`\`
252+
253+ ## Negative Prompt
254+
255+ \`\`\`shell
256+ ${ negativePrompt }
257+ \`\`\`
258+ ` ;
259+ }
260+
261+ /**
262+ * Minifies the given code string by removing leading whitespace.
263+ *
264+ * @param {string } code - The input code to be minified.
265+ * @returns {string } - The minified code.
266+ */
184267export function minify ( code ) {
185- return code . replace ( / ^ \s + / gim , "" ) ;
268+ return code . replace ( / ^ \s + / g , "" ) ;
186269}
187270
271+ /**
272+ * Prettifies the given code string using Prettier.
273+ *
274+ * @param {string } code - The input code to be prettified.
275+ * @returns {string } - The prettified code.
276+ */
188277export function prettify ( code ) {
189278 return prettier . format ( code , { semi : false , parser : "babel" } ) ;
190279}
191280
281+ /**
282+ * Handles errors that occur during the code generation process.
283+ *
284+ * @param {Error } error - The error object containing information about the error.
285+ * @param {number } generation - The current generation number.
286+ * @returns {Promise<void> } - A promise that resolves when the error is handled.
287+ */
192288export async function handleError ( error , generation ) {
193289 const message = (
194290 error . response ?. data ?. error . message ??
0 commit comments