@@ -463,6 +463,21 @@ enum KnowledgeCommands {
463463 #[ arg( long) ]
464464 output : Option < std:: path:: PathBuf > ,
465465 } ,
466+ /// Refresh a durable knowledge handoff snapshot across knowledge, session, and continuity surfaces
467+ Handoff {
468+ /// Emit machine-readable JSON
469+ #[ arg( long, default_value_t = false ) ]
470+ json : bool ,
471+ /// Emit compact JSON instead of pretty-printed JSON
472+ #[ arg( long, default_value_t = false ) ]
473+ compact : bool ,
474+ /// Write the handoff report to a file path instead of only printing it
475+ #[ arg( long) ]
476+ output : Option < std:: path:: PathBuf > ,
477+ /// Write the handoff report to ~/.sonechka/exports/knowledge-handoff/latest.json
478+ #[ arg( long, default_value_t = false ) ]
479+ export_latest : bool ,
480+ } ,
466481 /// Refresh the latest machine-readable knowledge-flow snapshot only
467482 Refresh {
468483 /// Emit compact JSON instead of pretty-printed JSON for the refreshed snapshot
@@ -2025,6 +2040,27 @@ async fn main() -> anyhow::Result<()> {
20252040 tool:: render_knowledge_flow_refresh( output. as_deref( ) , compact) ?
20262041 ) ;
20272042 }
2043+ KnowledgeCommands :: Handoff {
2044+ json,
2045+ compact,
2046+ output,
2047+ export_latest,
2048+ } => {
2049+ let report = build_knowledge_handoff_report ( ) ?;
2050+ if let Some ( path) =
2051+ resolve_knowledge_handoff_output_path ( output. as_deref ( ) , export_latest)
2052+ {
2053+ println ! ( "{}" , write_json_to_path( & path, & report, compact) ?) ;
2054+ if json {
2055+ return Ok ( ( ) ) ;
2056+ }
2057+ }
2058+ if json {
2059+ print_json ( & report, compact) ?;
2060+ } else {
2061+ println ! ( "{}" , render_knowledge_handoff_report_text( & report) ) ;
2062+ }
2063+ }
20282064 } ,
20292065 Some ( Commands :: Health { action } ) => match action {
20302066 HealthCommands :: Status {
@@ -3701,6 +3737,27 @@ struct OperatorReleaseReport {
37013737 operator_refresh : OperatorRefreshReport ,
37023738}
37033739
3740+ #[ derive( Debug , Serialize ) ]
3741+ struct KnowledgeHandoffContinuityReport {
3742+ trust_level : Option < String > ,
3743+ source : Option < String > ,
3744+ summary : Option < String > ,
3745+ }
3746+
3747+ #[ derive( Debug , Serialize ) ]
3748+ struct KnowledgeHandoffReport {
3749+ schema_version : String ,
3750+ generated_at_unix_ms : u128 ,
3751+ status : String ,
3752+ handoff_ready : bool ,
3753+ blocking_surfaces : Vec < String > ,
3754+ recommended_command : String ,
3755+ knowledge_flow : serde_json:: Value ,
3756+ session_continuity : serde_json:: Value ,
3757+ health_continuity : serde_json:: Value ,
3758+ continuity_handoff : KnowledgeHandoffContinuityReport ,
3759+ }
3760+
37043761fn operator_artifact_refs ( ) -> Vec < OperatorArtifactRef > {
37053762 [
37063763 (
@@ -3869,6 +3926,71 @@ fn build_operator_release_report(
38693926 } )
38703927}
38713928
3929+ fn build_knowledge_handoff_report ( ) -> anyhow:: Result < KnowledgeHandoffReport > {
3930+ let knowledge_flow: serde_json:: Value =
3931+ serde_json:: from_str ( & tool:: render_knowledge_flow_status_json ( true ) ?) ?;
3932+ let session_continuity: serde_json:: Value =
3933+ serde_json:: from_str ( & tool:: render_session_status_json ( None , true ) ?) ?;
3934+ let health_continuity: serde_json:: Value =
3935+ serde_json:: from_str ( & tool:: render_health_continuity_json ( true ) ?) ?;
3936+
3937+ let trust_level = health_continuity
3938+ . get ( "handoff" )
3939+ . and_then ( |value| value. get ( "continuity" ) )
3940+ . and_then ( |value| value. get ( "trust_level" ) )
3941+ . and_then ( |value| value. as_str ( ) )
3942+ . map ( ToOwned :: to_owned) ;
3943+ let source = health_continuity
3944+ . get ( "handoff" )
3945+ . and_then ( |value| value. get ( "continuity" ) )
3946+ . and_then ( |value| value. get ( "source" ) )
3947+ . and_then ( |value| value. as_str ( ) )
3948+ . map ( ToOwned :: to_owned) ;
3949+ let summary = health_continuity
3950+ . get ( "handoff" )
3951+ . and_then ( |value| value. get ( "continuity" ) )
3952+ . and_then ( |value| value. get ( "summary" ) )
3953+ . and_then ( |value| value. as_str ( ) )
3954+ . map ( ToOwned :: to_owned) ;
3955+
3956+ let mut blocking_surfaces = Vec :: new ( ) ;
3957+ let trusted = trust_level. as_deref ( ) == Some ( "trusted" ) ;
3958+ if !trusted {
3959+ blocking_surfaces. push ( "continuity" . to_string ( ) ) ;
3960+ }
3961+ let handoff_ready = blocking_surfaces. is_empty ( ) ;
3962+ let recommended_command = if trusted {
3963+ knowledge_flow
3964+ . get ( "recommended_command" )
3965+ . and_then ( |value| value. as_str ( ) )
3966+ . unwrap_or ( "sonechka knowledge status" )
3967+ . to_string ( )
3968+ } else {
3969+ "sonechka health continuity --json --compact" . to_string ( )
3970+ } ;
3971+
3972+ Ok ( KnowledgeHandoffReport {
3973+ schema_version : "knowledge-handoff-report.v1" . to_string ( ) ,
3974+ generated_at_unix_ms : report_generated_at_unix_ms ( ) ,
3975+ status : if handoff_ready {
3976+ "ready" . to_string ( )
3977+ } else {
3978+ "action-needed" . to_string ( )
3979+ } ,
3980+ handoff_ready,
3981+ blocking_surfaces,
3982+ recommended_command,
3983+ continuity_handoff : KnowledgeHandoffContinuityReport {
3984+ trust_level,
3985+ source,
3986+ summary,
3987+ } ,
3988+ knowledge_flow,
3989+ session_continuity,
3990+ health_continuity,
3991+ } )
3992+ }
3993+
38723994fn render_operator_status_report_text ( report : & OperatorStatusReport ) -> String {
38733995 let advisor_profile = report
38743996 . provider_routing
@@ -4027,6 +4149,68 @@ fn render_operator_release_report_text(report: &OperatorReleaseReport) -> String
40274149 )
40284150}
40294151
4152+ fn render_knowledge_handoff_report_text ( report : & KnowledgeHandoffReport ) -> String {
4153+ let trust_level = report
4154+ . continuity_handoff
4155+ . trust_level
4156+ . as_deref ( )
4157+ . unwrap_or ( "unknown" ) ;
4158+ let source = report
4159+ . continuity_handoff
4160+ . source
4161+ . as_deref ( )
4162+ . unwrap_or ( "unknown" ) ;
4163+ let summary = report
4164+ . continuity_handoff
4165+ . summary
4166+ . as_deref ( )
4167+ . unwrap_or ( "No continuity summary available." ) ;
4168+ let mut lines = vec ! [
4169+ "Knowledge handoff" . to_string( ) ,
4170+ format!( "- status: {}" , report. status) ,
4171+ format!(
4172+ "- handoff ready: {}" ,
4173+ if report. handoff_ready { "yes" } else { "no" }
4174+ ) ,
4175+ format!(
4176+ "- blocking surfaces: {}" ,
4177+ if report. blocking_surfaces. is_empty( ) {
4178+ "none" . to_string( )
4179+ } else {
4180+ report. blocking_surfaces. join( ", " )
4181+ }
4182+ ) ,
4183+ format!( "- continuity trust: {}" , trust_level) ,
4184+ format!( "- continuity source: {}" , source) ,
4185+ format!( "- recommended command: {}" , report. recommended_command) ,
4186+ String :: new( ) ,
4187+ "Continuity handoff" . to_string( ) ,
4188+ format!( "- {}" , summary) ,
4189+ ] ;
4190+ if let Some ( session_id) = report
4191+ . session_continuity
4192+ . get ( "session_id" )
4193+ . and_then ( |value| value. as_str ( ) )
4194+ {
4195+ lines. push ( String :: new ( ) ) ;
4196+ lines. push ( "Latest session" . to_string ( ) ) ;
4197+ lines. push ( format ! ( "- session id: {}" , session_id) ) ;
4198+ }
4199+ if let Some ( command) = report
4200+ . knowledge_flow
4201+ . get ( "recommended_command" )
4202+ . and_then ( |value| value. as_str ( ) )
4203+ {
4204+ lines. push ( String :: new ( ) ) ;
4205+ lines. push ( "Knowledge flow" . to_string ( ) ) ;
4206+ lines. push ( format ! ( "- recommended command: {}" , command) ) ;
4207+ }
4208+ lines. join (
4209+ "
4210+ " ,
4211+ )
4212+ }
4213+
40304214fn report_generated_at_unix_ms ( ) -> u128 {
40314215 std:: time:: SystemTime :: now ( )
40324216 . duration_since ( std:: time:: UNIX_EPOCH )
@@ -4346,6 +4530,13 @@ fn operator_report_latest_path(report_family: &str) -> std::path::PathBuf {
43464530 . join ( "latest.json" )
43474531}
43484532
4533+ fn knowledge_handoff_latest_path ( ) -> std:: path:: PathBuf {
4534+ config:: config_dir ( )
4535+ . join ( "exports" )
4536+ . join ( "knowledge-handoff" )
4537+ . join ( "latest.json" )
4538+ }
4539+
43494540fn resolve_operator_report_output_path (
43504541 output : Option < & std:: path:: Path > ,
43514542 export_latest : bool ,
@@ -4356,6 +4547,15 @@ fn resolve_operator_report_output_path(
43564547 . or_else ( || export_latest. then ( || operator_report_latest_path ( report_family) ) )
43574548}
43584549
4550+ fn resolve_knowledge_handoff_output_path (
4551+ output : Option < & std:: path:: Path > ,
4552+ export_latest : bool ,
4553+ ) -> Option < std:: path:: PathBuf > {
4554+ output
4555+ . map ( std:: path:: Path :: to_path_buf)
4556+ . or_else ( || export_latest. then ( knowledge_handoff_latest_path) )
4557+ }
4558+
43594559fn print_json < T : Serialize > ( value : & T , compact : bool ) -> anyhow:: Result < ( ) > {
43604560 let body = if compact {
43614561 serde_json:: to_string ( value) ?
0 commit comments