@@ -70,20 +70,30 @@ export function createToolProgress(log: ToolProgressLog): (progress: StreamProgr
7070 }
7171 }
7272
73- return ( { type, chunk, section } ) => {
73+ return ( { type, chunk, text , section } ) => {
7474 if ( type === 'text' ) {
7575 const key = section ?? ''
7676 const now = Date . now ( )
7777 const last = lastTextEmit . get ( key ) ?? 0
7878 if ( now - last < TEXT_THROTTLE_MS )
7979 return
8080 lastTextEmit . set ( key , now )
81- emit ( `${ section ? `\x1B[90m[${ section } ]\x1B[0m ` : '' } Writing...` )
81+ const prefix = section ? `\x1B[90m[${ section } ]\x1B[0m ` : ''
82+ // Count bullet items in accumulated text for meaningful progress
83+ const items = text ? ( text . match ( / ^ - (?: B R E A K I N G | D E P R E C A T E D | N E W | C H A N G E D | R E M O V E D | U s e | D o | S e t | A d d | A v o i d | A l w a y s | N e v e r | P r e f e r | C h e c k | E n s u r e ) / gm) ?. length ?? 0 ) : 0
84+ emit ( items > 0 ? `${ prefix } Writing... \x1B[90m(${ items } items)\x1B[0m` : `${ prefix } Writing...` )
8285 return
8386 }
8487 if ( type !== 'reasoning' || ! chunk . startsWith ( '[' ) )
8588 return
8689
90+ // Handle status messages like [starting...], [retrying...], [cached]
91+ if ( / ^ \[ (?: s t a r t i n g | r e t r y i n g | c a c h e d ) / . test ( chunk ) ) {
92+ const prefix = section ? `\x1B[90m[${ section } ]\x1B[0m ` : ''
93+ emit ( `${ prefix } ${ chunk . slice ( 1 , - 1 ) } ` )
94+ return
95+ }
96+
8797 // Parse individual tool names and hints from "[Read: path]" or "[Read, Glob: path1, path2]"
8898 const match = chunk . match ( / ^ \[ ( [ ^ : [ \] ] + ) (?: : \s ( .+ ) ) ? \] $ / )
8999 if ( ! match )
@@ -322,6 +332,12 @@ async function optimizeSectionViaPiAi(opts: {
322332 const { section, prompt, outputFile, skillDir, model, onProgress, timeout, debug } = opts
323333 const skilldDir = join ( skillDir , '.skilld' )
324334 const outputPath = join ( skilldDir , outputFile )
335+ const logsDir = join ( skilldDir , 'logs' )
336+ const logName = section . toUpperCase ( ) . replace ( / - / g, '_' )
337+
338+ // Remove stale output so we don't read a leftover from a previous run
339+ if ( existsSync ( outputPath ) )
340+ unlinkSync ( outputPath )
325341
326342 // Write prompt for debugging (same as CLI path)
327343 writeFileSync ( join ( skilldDir , `PROMPT_${ section } .md` ) , prompt )
@@ -334,11 +350,10 @@ async function optimizeSectionViaPiAi(opts: {
334350
335351 const raw = result . text . trim ( )
336352
337- // Debug logging
353+ // Debug logging — match CLI path: actual prompt sent + raw output
338354 if ( debug ) {
339- const logsDir = join ( skilldDir , 'logs' )
340- const logName = section . toUpperCase ( ) . replace ( / - / g, '_' )
341355 mkdirSync ( logsDir , { recursive : true } )
356+ writeFileSync ( join ( skilldDir , `PROMPT_${ section } .md` ) , result . fullPrompt )
342357 if ( raw )
343358 writeFileSync ( join ( logsDir , `${ logName } .md` ) , raw )
344359 }
@@ -349,6 +364,7 @@ async function optimizeSectionViaPiAi(opts: {
349364
350365 const content = cleanSectionOutput ( raw )
351366
367+ // Always write cleaned content to output file (matches CLI path)
352368 if ( content )
353369 writeFileSync ( outputPath , content )
354370
@@ -366,7 +382,13 @@ async function optimizeSectionViaPiAi(opts: {
366382 }
367383 }
368384 catch ( err ) {
369- return { section, content : '' , wasOptimized : false , error : ( err as Error ) . message }
385+ // Write error to logs on failure (matches CLI stderr logging)
386+ const errMsg = ( err as Error ) . message
387+ if ( debug || errMsg ) {
388+ mkdirSync ( logsDir , { recursive : true } )
389+ writeFileSync ( join ( logsDir , `${ logName } .stderr.log` ) , errMsg )
390+ }
391+ return { section, content : '' , wasOptimized : false , error : errMsg }
370392 }
371393}
372394
@@ -822,15 +844,11 @@ export function cleanSectionOutput(content: string): string {
822844 }
823845 }
824846
825- // Strip raw code preamble before first section marker (defense against LLMs dumping source )
847+ // Strip preamble before first section marker (LLM reasoning, fake tool calls, code dumps )
826848 // Section markers: ## heading, BREAKING/DEPRECATED/NEW labels
827849 const firstMarker = cleaned . match ( / ^ ( # # \s | - (?: B R E A K I N G | D E P R E C A T E D | N E W ) : ) / m)
828850 if ( firstMarker ?. index && firstMarker . index > 0 ) {
829- const preamble = cleaned . slice ( 0 , firstMarker . index )
830- // Only strip if preamble looks like code (contains function/const/export/return patterns)
831- if ( / \b (?: f u n c t i o n | c o n s t | l e t | v a r | e x p o r t | r e t u r n | i m p o r t | a s y n c | c l a s s ) \b / . test ( preamble ) ) {
832- cleaned = cleaned . slice ( firstMarker . index ) . trim ( )
833- }
851+ cleaned = cleaned . slice ( firstMarker . index ) . trim ( )
834852 }
835853
836854 // Strip duplicate section headings (LLM echoing the format example before real content)
0 commit comments