@@ -770,40 +770,101 @@ export const HOOK_TEMPLATES: HookTemplate[] = [
770770 name : 'CCW Coordinator Tracker' ,
771771 description : 'Track /ccw and /ccw-coordinator execution progress, inject next-step hints when paused' ,
772772 category : 'automation' ,
773- trigger : 'PostToolUse ' ,
773+ trigger : 'Stop ' ,
774774 execute : ( data ) => {
775775 const sessionId = getStringInput ( data . session_id ) ;
776776 if ( ! sessionId ) return { exitCode : 0 } ;
777777
778778 const workspace = data . cwd || process . env . CLAUDE_PROJECT_DIR || process . cwd ( ) ;
779779
780- // Dynamic import to avoid circular dependencies
781780 try {
782- const { readLatestCcwSession, readCoordBridge, writeCoordBridge, buildNextStepHint } =
783- require ( '../hooks/ccw-coordinator-tracker.js' ) ;
784-
785- const existing = readCoordBridge ( sessionId ) ;
786- const bridgeData = readLatestCcwSession ( workspace , existing ) ;
787- if ( ! bridgeData ) return { exitCode : 0 } ;
781+ const { existsSync, readFileSync, writeFileSync, readdirSync, statSync } = require ( 'fs' ) as typeof import ( 'fs' ) ;
782+ const { join } = require ( 'path' ) as typeof import ( 'path' ) ;
783+ const { tmpdir } = require ( 'os' ) as typeof import ( 'os' ) ;
784+ const CCW_COORD_BRIDGE_PREFIX = 'ccw-coord-' ;
785+
786+ // --- Inline: read latest CCW session ---
787+ function readLatestSession ( dir : string , subPath : string ) : { bridge : CcwBridgeData ; mtime : number } | null {
788+ const base = join ( dir , subPath ) ;
789+ if ( ! existsSync ( base ) ) return null ;
790+ try {
791+ const sessions = readdirSync ( base )
792+ . map ( name => {
793+ const sp = join ( base , name , 'state.json' ) ;
794+ if ( ! existsSync ( sp ) ) {
795+ // ccw uses status.json, ccw-coordinator uses state.json
796+ return null ;
797+ }
798+ const mtime = statSync ( sp ) . mtimeMs ;
799+ const raw = JSON . parse ( readFileSync ( sp , 'utf8' ) ) ;
800+ const chain = raw . command_chain ?? [ ] ;
801+ const results = raw . execution_results ?? [ ] ;
802+ const ci = results . findIndex ( ( r : any ) => r . status === 'in-progress' ) ;
803+ const completed = results . filter ( ( r : any ) => r . status === 'completed' ) . length ;
804+ const current = ci >= 0 && chain [ ci ] ? chain [ ci ] . command : null ;
805+ const ni = ci >= 0 ? ci + 1 : chain . length ;
806+ const next = chain [ ni ] ? chain [ ni ] . command : null ;
807+ return {
808+ bridge : {
809+ session_id : sessionId , coordinator : subPath . includes ( 'ccw-coordinator' ) ? 'ccw-coordinator' : 'ccw' ,
810+ chain_name : raw . workflow ?? '' , intent : raw . analysis ?. goal ?? '' ,
811+ steps_total : chain . length , steps_completed : completed ,
812+ current_step : current != null ? { index : ci , command : current } : null ,
813+ next_step : next != null ? { index : ni , command : next } : null ,
814+ remaining_steps : chain . slice ( ni ) . map ( ( s : any ) => ( { command : s . command ?? '' } ) ) ,
815+ status : raw . status ?? 'unknown' , updated_at : Math . floor ( mtime ) ,
816+ } ,
817+ mtime,
818+ } ;
819+ } )
820+ . filter ( ( s ) : s is NonNullable < typeof s > => s !== null )
821+ . sort ( ( a , b ) => b . mtime - a . mtime ) ;
822+ if ( sessions . length === 0 ) return null ;
823+ return sessions [ 0 ] ;
824+ } catch { return null ; }
825+ }
788826
789- bridgeData . session_id = sessionId ;
790- writeCoordBridge ( sessionId , bridgeData ) ;
827+ interface CcwBridgeData {
828+ session_id : string ; coordinator : string ; chain_name : string ; intent : string ;
829+ steps_total : number ; steps_completed : number ;
830+ current_step : { index : number ; command : string } | null ;
831+ next_step : { index : number ; command : string } | null ;
832+ remaining_steps : Array < { command : string } > ; status : string ; updated_at : number ;
833+ }
791834
792- // Inject next-step hint for active sessions
793- const hint = buildNextStepHint ( bridgeData ) ;
794- if ( hint ) {
795- return {
796- exitCode : 0 ,
797- jsonOutput : {
798- hookSpecificOutput : {
799- hookEventName : 'PostToolUse' ,
800- additionalContext : hint ,
801- } ,
802- } ,
803- } ;
835+ const ccwResult = readLatestSession ( workspace , '.workflow/.ccw' ) ;
836+ const coordResult = readLatestSession ( workspace , '.workflow/.ccw-coordinator' ) ;
837+ const candidates = [ ccwResult , coordResult ] . filter ( ( s ) : s is NonNullable < typeof s > => s !== null ) ;
838+ if ( candidates . length === 0 ) return { exitCode : 0 } ;
839+
840+ const best = candidates . sort ( ( a , b ) => b . mtime - a . mtime ) [ 0 ] ;
841+
842+ // Write bridge file
843+ const bridgePath = join ( tmpdir ( ) , `${ CCW_COORD_BRIDGE_PREFIX } ${ sessionId } .json` ) ;
844+ writeFileSync ( bridgePath , JSON . stringify ( best . bridge ) ) ;
845+
846+ // Build next-step hint for active sessions
847+ if ( best . bridge . status === 'running' || best . bridge . status === 'waiting' ) {
848+ if ( best . bridge . next_step ) {
849+ const p = `[${ best . bridge . steps_completed } /${ best . bridge . steps_total } ]` ;
850+ const lines = [
851+ `## CCW Coordinator Active` ,
852+ `Chain: ${ best . bridge . chain_name } ${ p } | Status: ${ best . bridge . status } ` ,
853+ `Last: ${ best . bridge . current_step ?. command ?? '(unknown)' } ` ,
854+ `Next: ${ best . bridge . next_step . command } ` ,
855+ ] ;
856+ if ( best . bridge . remaining_steps . length > 1 ) {
857+ const r = best . bridge . remaining_steps . slice ( 1 , 4 ) . map ( s => s . command ) . join ( ' → ' ) ;
858+ lines . push ( `Then: ${ r } ${ best . bridge . remaining_steps . length > 4 ? ' …' : '' } ` ) ;
859+ }
860+ return {
861+ exitCode : 0 ,
862+ jsonOutput : { continue : true , message : lines . join ( '\n' ) } ,
863+ } ;
864+ }
804865 }
805866 } catch {
806- // Silent fail — tracker must not break tool execution
867+ // Silent fail — tracker must not break hook execution
807868 }
808869 return { exitCode : 0 } ;
809870 }
@@ -826,24 +887,45 @@ export const HOOK_TEMPLATES: HookTemplate[] = [
826887 if ( ! sessionId ) return { exitCode : 0 } ;
827888
828889 try {
829- const { readCoordBridge, buildNextStepHint } =
830- require ( '../hooks/ccw-coordinator-tracker.js' ) ;
831-
832- const bridgeData = readCoordBridge ( sessionId ) ;
833- if ( ! bridgeData ) return { exitCode : 0 } ;
890+ const { existsSync, readFileSync } = require ( 'fs' ) as typeof import ( 'fs' ) ;
891+ const { join } = require ( 'path' ) as typeof import ( 'path' ) ;
892+ const { tmpdir } = require ( 'os' ) as typeof import ( 'os' ) ;
893+
894+ // Read bridge file
895+ const bridgePath = join ( tmpdir ( ) , `ccw-coord-${ sessionId } .json` ) ;
896+ if ( ! existsSync ( bridgePath ) ) return { exitCode : 0 } ;
897+
898+ const bridge : {
899+ status : string ; steps_total : number ; steps_completed : number ;
900+ chain_name : string ; current_step : { command : string } | null ; next_step : { command : string } | null ;
901+ remaining_steps : Array < { command : string } > ;
902+ } = JSON . parse ( readFileSync ( bridgePath , 'utf8' ) ) ;
903+
904+ // Only inject for active sessions with a next step
905+ const isActive = bridge . status === 'running' || bridge . status === 'waiting' ;
906+ if ( ! isActive || ! bridge . next_step ) return { exitCode : 0 } ;
907+
908+ const p = `[${ bridge . steps_completed } /${ bridge . steps_total } ]` ;
909+ const lines = [
910+ `## CCW Coordinator Active` ,
911+ `Chain: ${ bridge . chain_name } ${ p } | Status: ${ bridge . status } ` ,
912+ `Last: ${ bridge . current_step ?. command ?? '(unknown)' } ` ,
913+ `Next: ${ bridge . next_step . command } ` ,
914+ ] ;
915+ if ( bridge . remaining_steps . length > 1 ) {
916+ const r = bridge . remaining_steps . slice ( 1 , 4 ) . map ( s => s . command ) . join ( ' → ' ) ;
917+ lines . push ( `Then: ${ r } ${ bridge . remaining_steps . length > 4 ? ' …' : '' } ` ) ;
918+ }
834919
835- const hint = buildNextStepHint ( bridgeData ) ;
836- if ( hint ) {
837- return {
838- exitCode : 0 ,
839- jsonOutput : {
840- hookSpecificOutput : {
841- hookEventName : 'UserPromptSubmit' ,
842- additionalContext : hint ,
843- } ,
920+ return {
921+ exitCode : 0 ,
922+ jsonOutput : {
923+ hookSpecificOutput : {
924+ hookEventName : 'UserPromptSubmit' ,
925+ additionalContext : lines . join ( '\n' ) ,
844926 } ,
845- } ;
846- }
927+ } ,
928+ } ;
847929 } catch {
848930 // Silent fail
849931 }
0 commit comments