11const fs = require ( 'fs' ) ;
22const { execSync } = require ( 'child_process' ) ;
3+ const path = require ( 'path' ) ;
4+
5+ // Configuration de débogage
6+ const DEBUG = true ;
7+ const SINGLE_EXERCISE = process . env . SINGLE_EXERCISE || null ; // ex: "intro1"
8+ const SAVE_RESPONSES = true ;
9+
10+ function log ( message ) {
11+ if ( DEBUG ) {
12+ console . log ( `[DEBUG] ${ message } ` ) ;
13+ }
14+ }
315
416function parseInfoToml ( infoPath ) {
17+ log ( `Parsing info.toml from: ${ infoPath } ` ) ;
18+
19+ if ( ! fs . existsSync ( infoPath ) ) {
20+ throw new Error ( `info.toml not found at: ${ infoPath } ` ) ;
21+ }
22+
523 const content = fs . readFileSync ( infoPath , 'utf8' ) ;
24+ log ( `File content length: ${ content . length } characters` ) ;
25+
626 const exercises = [ ] ;
727 const lines = content . split ( '\n' ) ;
828 let currentExercise = null ;
929 let collectingHint = false ;
1030 let hintLines = [ ] ;
1131
12- for ( const line of lines ) {
32+ for ( let i = 0 ; i < lines . length ; i ++ ) {
33+ const line = lines [ i ] ;
1334 const cleanLine = line . trim ( ) ;
1435
1536 if ( cleanLine . startsWith ( '[[exercises]]' ) ) {
@@ -18,6 +39,7 @@ function parseInfoToml(infoPath) {
1839 currentExercise . hint = hintLines . join ( '\n' ) . replace ( / ^ " " " / , '' ) . replace ( / " " " $ / , '' ) ;
1940 }
2041 exercises . push ( currentExercise ) ;
42+ log ( `Added exercise: ${ currentExercise . name } ` ) ;
2143 }
2244 currentExercise = { } ;
2345 collectingHint = false ;
@@ -33,11 +55,20 @@ function parseInfoToml(infoPath) {
3355 hintLines . push ( cleanLine ) ;
3456 }
3557 } else if ( cleanLine . startsWith ( 'name = ' ) ) {
36- currentExercise . name = cleanLine . match ( / n a m e = " ( .+ ) " / ) [ 1 ] ;
58+ const match = cleanLine . match ( / n a m e = " ( .+ ) " / ) ;
59+ if ( match ) {
60+ currentExercise . name = match [ 1 ] ;
61+ }
3762 } else if ( cleanLine . startsWith ( 'path = ' ) ) {
38- currentExercise . path = cleanLine . match ( / p a t h = " ( .+ ) " / ) [ 1 ] ;
63+ const match = cleanLine . match ( / p a t h = " ( .+ ) " / ) ;
64+ if ( match ) {
65+ currentExercise . path = match [ 1 ] ;
66+ }
3967 } else if ( cleanLine . startsWith ( 'mode = ' ) ) {
40- currentExercise . mode = cleanLine . match ( / m o d e = " ( .+ ) " / ) [ 1 ] ;
68+ const match = cleanLine . match ( / m o d e = " ( .+ ) " / ) ;
69+ if ( match ) {
70+ currentExercise . mode = match [ 1 ] ;
71+ }
4172 }
4273 }
4374
@@ -47,31 +78,41 @@ function parseInfoToml(infoPath) {
4778 currentExercise . hint = hintLines . join ( '\n' ) . replace ( / " " " $ / , '' ) ;
4879 }
4980 exercises . push ( currentExercise ) ;
81+ log ( `Added final exercise: ${ currentExercise . name } ` ) ;
5082 }
5183
84+ log ( `Total exercises parsed: ${ exercises . length } ` ) ;
5285 return exercises ;
5386}
5487
55- function getExerciseObjective ( exerciseName ) {
56- const objectives = {
57- 'intro1' : 'Introduction to Cairo syntax and basic program structure' ,
58- 'intro2' : 'Understanding Cairo compilation and execution' ,
59- 'variables1' : 'Learn to declare variables with the `let` keyword' ,
60- 'variables2' : 'Understand type annotations and basic felt252 type' ,
61- 'variables3' : 'Learn to initialize variables with values' ,
62- 'variables4' : 'Understand mutability with the `mut` keyword' ,
63- 'variables5' : 'Learn about variable shadowing' ,
64- 'variables6' : 'Understand constants with the `const` keyword' ,
65- // ... continuer pour tous les exercices
66- } ;
67- return objectives [ exerciseName ] || 'Practice Cairo programming concepts' ;
88+ async function testServerConnection ( ) {
89+ log ( 'Testing server connection...' ) ;
90+
91+ try {
92+ const response = await fetch ( 'http://localhost:3001/' , {
93+ method : 'GET' ,
94+ timeout : 5000
95+ } ) ;
96+
97+ if ( response . ok ) {
98+ log ( '✅ Server connection successful' ) ;
99+ return true ;
100+ } else {
101+ log ( `❌ Server responded with status: ${ response . status } ` ) ;
102+ return false ;
103+ }
104+ } catch ( error ) {
105+ log ( `❌ Server connection failed: ${ error . message } ` ) ;
106+ return false ;
107+ }
68108}
69109
70110async function callCairoCoderAPI ( exerciseContent , exercise ) {
111+ log ( `Calling API for exercise: ${ exercise . name } ` ) ;
112+
71113 const prompt = `You are solving a Cairo programming exercise.
72114
73115Exercise: ${ exercise . name }
74- Objective: ${ getExerciseObjective ( exercise . name ) }
75116${ exercise . hint ? `Hint: ${ exercise . hint } ` : '' }
76117
77118Instructions:
@@ -81,47 +122,214 @@ Instructions:
811224. Ensure the solution demonstrates the intended concept
82123
83124Code to fix:
84- ${ exerciseContent } `;
85-
86- const response = await fetch ( 'http://localhost:3001/v1/chat/completions' , {
87- method : 'POST' ,
88- headers : { 'Content-Type' : 'application/json' } ,
89- body : JSON . stringify ( {
90- model : 'cairo-coder' ,
91- messages : [ { role : 'user' , content : prompt } ]
92- } )
93- } ) ;
94-
95- return response . json ( ) ;
125+ ${ exerciseContent }
126+
127+ Please provide only the corrected code, without any additional explanation or markdown formatting.` ;
128+
129+ const requestBody = {
130+ model : 'cairo-coder' ,
131+ messages : [ { role : 'user' , content : prompt } ] ,
132+ stream : false
133+ } ;
134+
135+ log ( `Request body size: ${ JSON . stringify ( requestBody ) . length } characters` ) ;
136+
137+ try {
138+ const response = await fetch ( 'http://localhost:3001/v1/chat/completions' , {
139+ method : 'POST' ,
140+ headers : {
141+ 'Content-Type' : 'application/json' ,
142+ 'User-Agent' : 'Starklings-Evaluator/1.0'
143+ } ,
144+ body : JSON . stringify ( requestBody ) ,
145+ timeout : 60000 // 60 secondes
146+ } ) ;
147+
148+ if ( ! response . ok ) {
149+ const errorText = await response . text ( ) ;
150+ log ( `API Error - Status: ${ response . status } , Response: ${ errorText } ` ) ;
151+ throw new Error ( `HTTP error! status: ${ response . status } - ${ errorText } ` ) ;
152+ }
153+
154+ const data = await response . json ( ) ;
155+ log ( `API Response received, data structure: ${ Object . keys ( data ) . join ( ', ' ) } ` ) ;
156+
157+ // Sauvegarder la réponse complète si demandé
158+ if ( SAVE_RESPONSES ) {
159+ const responseFile = path . join ( __dirname , '..' , '..' , 'debug' , `${ exercise . name } _response.json` ) ;
160+ fs . mkdirSync ( path . dirname ( responseFile ) , { recursive : true } ) ;
161+ fs . writeFileSync ( responseFile , JSON . stringify ( data , null , 2 ) ) ;
162+ log ( `Response saved to: ${ responseFile } ` ) ;
163+ }
164+
165+ // Extraire le contenu de la réponse
166+ if ( data . choices && data . choices [ 0 ] && data . choices [ 0 ] . message ) {
167+ const content = data . choices [ 0 ] . message . content ;
168+ log ( `Generated code length: ${ content . length } characters` ) ;
169+ return content ;
170+ } else {
171+ log ( `Invalid response format: ${ JSON . stringify ( data ) } ` ) ;
172+ throw new Error ( 'Invalid response format from API' ) ;
173+ }
174+ } catch ( error ) {
175+ log ( `API call failed: ${ error . message } ` ) ;
176+ throw error ;
177+ }
178+ }
179+
180+ async function testExercise ( exercise , starklingsPath ) {
181+ log ( `\n=== Testing exercise: ${ exercise . name } ===` ) ;
182+
183+ const exercisePath = path . join ( starklingsPath , exercise . path ) ;
184+ log ( `Exercise path: ${ exercisePath } ` ) ;
185+
186+ if ( ! fs . existsSync ( exercisePath ) ) {
187+ log ( `❌ Exercise file not found: ${ exercisePath } ` ) ;
188+ return false ;
189+ }
190+
191+ // Lire le contenu original
192+ const originalContent = fs . readFileSync ( exercisePath , 'utf8' ) ;
193+ log ( `Original file size: ${ originalContent . length } characters` ) ;
194+
195+ // Sauvegarder l'original
196+ const backupPath = exercisePath + '.backup' ;
197+ fs . writeFileSync ( backupPath , originalContent ) ;
198+ log ( `Backup saved to: ${ backupPath } ` ) ;
199+
200+ try {
201+ // Appeler l'API
202+ const correctedCode = await callCairoCoderAPI ( originalContent , exercise ) ;
203+
204+ // Sauvegarder la solution
205+ fs . writeFileSync ( exercisePath , correctedCode ) ;
206+ log ( `Updated exercise file with generated code` ) ;
207+
208+ // Sauvegarder la solution générée pour debug
209+ if ( SAVE_RESPONSES ) {
210+ const solutionFile = path . join ( __dirname , '..' , '..' , 'debug' , `${ exercise . name } _solution.cairo` ) ;
211+ fs . mkdirSync ( path . dirname ( solutionFile ) , { recursive : true } ) ;
212+ fs . writeFileSync ( solutionFile , correctedCode ) ;
213+ log ( `Solution saved to: ${ solutionFile } ` ) ;
214+ }
215+
216+ // Tester la solution
217+ try {
218+ log ( `Running starklings for ${ exercise . name } ...` ) ;
219+ const result = execSync ( `cargo run --bin starklings run ${ exercise . name } ` , {
220+ cwd : starklingsPath ,
221+ stdio : 'pipe' ,
222+ timeout : 30000 ,
223+ encoding : 'utf8'
224+ } ) ;
225+
226+ log ( `✅ ${ exercise . name } - Success` ) ;
227+ log ( `Starklings output: ${ result . substring ( 0 , 200 ) } ...` ) ;
228+ return true ;
229+ } catch ( error ) {
230+ log ( `❌ ${ exercise . name } - Execution failed` ) ;
231+ log ( `Error code: ${ error . status } ` ) ;
232+ log ( `stdout: ${ error . stdout ? error . stdout . substring ( 0 , 500 ) : 'none' } ` ) ;
233+ log ( `stderr: ${ error . stderr ? error . stderr . substring ( 0 , 500 ) : 'none' } ` ) ;
234+
235+ // Sauvegarder l'erreur pour debug
236+ if ( SAVE_RESPONSES ) {
237+ const errorFile = path . join ( __dirname , '..' , '..' , 'debug' , `${ exercise . name } _error.txt` ) ;
238+ fs . writeFileSync ( errorFile , `Exit code: ${ error . status } \n\nSTDOUT:\n${ error . stdout } \n\nSTDERR:\n${ error . stderr } ` ) ;
239+ log ( `Error details saved to: ${ errorFile } ` ) ;
240+ }
241+
242+ return false ;
243+ }
244+ } catch ( error ) {
245+ log ( `❌ ${ exercise . name } - API call failed: ${ error . message } ` ) ;
246+ return false ;
247+ } finally {
248+ // Restaurer l'original
249+ fs . writeFileSync ( exercisePath , originalContent ) ;
250+ fs . unlinkSync ( backupPath ) ;
251+ log ( `Restored original file and cleaned up backup` ) ;
252+ }
96253}
97254
98255async function main ( ) {
99- const exercises = parseInfoToml ( './starklings/info.toml' ) ;
256+ log ( '=== Starting Starklings Debug Session ===' ) ;
257+
258+ const starklingsPath = path . join ( process . cwd ( ) , 'starklings' ) ;
259+ const infoPath = path . join ( starklingsPath , 'info.toml' ) ;
260+
261+ // Vérifications initiales
262+ log ( `Working directory: ${ process . cwd ( ) } ` ) ;
263+ log ( `Starklings path: ${ starklingsPath } ` ) ;
264+ log ( `Info.toml path: ${ infoPath } ` ) ;
265+
266+ if ( ! fs . existsSync ( starklingsPath ) ) {
267+ console . error ( '❌ Starklings directory not found' ) ;
268+ process . exit ( 1 ) ;
269+ }
270+
271+ if ( ! fs . existsSync ( infoPath ) ) {
272+ console . error ( '❌ info.toml not found in starklings directory' ) ;
273+ process . exit ( 1 ) ;
274+ }
275+
276+ // Tester la connexion au serveur
277+ const serverOk = await testServerConnection ( ) ;
278+ if ( ! serverOk ) {
279+ console . error ( '❌ Server is not accessible' ) ;
280+ process . exit ( 1 ) ;
281+ }
282+
283+ // Parser les exercices
284+ const exercises = parseInfoToml ( infoPath ) ;
285+
286+ if ( exercises . length === 0 ) {
287+ console . error ( '❌ No exercises found' ) ;
288+ process . exit ( 1 ) ;
289+ }
290+
291+ // Filtrer à un seul exercice si demandé
292+ let exercisesToTest = exercises ;
293+ if ( SINGLE_EXERCISE ) {
294+ exercisesToTest = exercises . filter ( ex => ex . name === SINGLE_EXERCISE ) ;
295+ if ( exercisesToTest . length === 0 ) {
296+ console . error ( `❌ Exercise '${ SINGLE_EXERCISE } ' not found` ) ;
297+ console . log ( 'Available exercises:' , exercises . map ( ex => ex . name ) . join ( ', ' ) ) ;
298+ process . exit ( 1 ) ;
299+ }
300+ log ( `Testing single exercise: ${ SINGLE_EXERCISE } ` ) ;
301+ }
302+
303+ // Créer le dossier de debug
304+ const debugDir = path . join ( __dirname , '..' , '..' , 'debug' ) ;
305+ fs . mkdirSync ( debugDir , { recursive : true } ) ;
306+
307+ // Tester les exercices
100308 let passed = 0 ;
101- let total = exercises . length ;
309+ let total = exercisesToTest . length ;
310+
311+ console . log ( `\n🧪 Starting evaluation of ${ total } exercises...` ) ;
102312
103- for ( const exercise of exercises ) {
104- console . log ( `Testing ${ exercise . name } ...` ) ;
105- const exerciseContent = fs . readFileSync ( `./starklings/${ exercise . path } ` , 'utf8' ) ;
106- const response = await callCairoCoderAPI ( exerciseContent , exercise ) ;
313+ for ( const exercise of exercisesToTest ) {
314+ const success = await testExercise ( exercise , starklingsPath ) ;
315+ if ( success ) {
316+ passed ++ ;
317+ }
107318
108- fs . writeFileSync ( `./starklings/${ exercise . path } ` , response ) ;
109-
110- try {
111- execSync ( `cd starklings && cargo run -r --bin starklings run ${ exercise . name } ` , { stdio : 'pipe' } ) ;
112- console . log ( `✅ ${ exercise . name } ` ) ;
113- passed ++ ;
114- } catch ( error ) {
115- console . log ( `❌ ${ exercise . name } ` ) ;
319+ // Pause entre les exercices pour éviter la surcharge
320+ if ( exercisesToTest . length > 1 ) {
321+ await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
322+ }
116323 }
117- }
118-
119- console . log ( `\nResults: ${ passed } /${ total } exercises passed (${ ( passed / total * 100 ) . toFixed ( 1 ) } %)` ) ;
120-
121- // Fail CI si moins de 80% de réussite
122- if ( passed / total < 0.8 ) {
123- process . exit ( 1 ) ;
124- }
324+
325+ console . log ( `\n=== Final Results ===` ) ;
326+ console . log ( `${ passed } /${ total } exercises passed (${ ( passed / total * 100 ) . toFixed ( 1 ) } %)` ) ;
327+
328+ log ( `Debug files saved in: ${ debugDir } ` ) ;
329+ log ( '=== Debug Session Complete ===' ) ;
125330}
126331
127- main ( ) . catch ( console . error ) ;
332+ main ( ) . catch ( error => {
333+ console . error ( '❌ Fatal error:' , error ) ;
334+ process . exit ( 1 ) ;
335+ } ) ;
0 commit comments