@@ -1053,6 +1053,21 @@ enum Commands {
10531053 /// Why-this-approach, Verification, Affected). Reuses event log +
10541054 /// artifacts; introduces no new tables.
10551055 ExportPr { task_id : String } ,
1056+ /// Print a task's honesty score (0–100) and completeness gaps —
1057+ /// deterministic, zero-LLM (like `mex check`). `--json` for machines.
1058+ Check {
1059+ task_id : String ,
1060+ #[ arg( long) ]
1061+ json : bool ,
1062+ } ,
1063+ /// Print a targeted gap-fill prompt for a task (deterministic, zero-LLM,
1064+ /// like `mex sync --dry-run`); the in-session agent runs it to close gaps.
1065+ Gaps {
1066+ task_id : String ,
1067+ /// Emit the full fix prompt (default: just list gaps + score).
1068+ #[ arg( long) ]
1069+ fill : bool ,
1070+ } ,
10561071 /// Export task knowledge as Claude-memory frontmatter files (feeds native dream).
10571072 ExportMemory {
10581073 /// Export a single task by id.
@@ -3351,6 +3366,12 @@ runs in the background and won't block you; it only fills gaps and never closes
33513366 Commands :: ExportPr { task_id } => {
33523367 run_export_pr ( & task_id) ?;
33533368 }
3369+ Commands :: Check { task_id, json } => {
3370+ run_check ( & task_id, json) ?;
3371+ }
3372+ Commands :: Gaps { task_id, fill } => {
3373+ run_gaps ( & task_id, fill) ?;
3374+ }
33543375 Commands :: ExportMemory {
33553376 task,
33563377 all_closed,
@@ -3766,6 +3787,109 @@ fn run_export_pr(task_id: &str) -> Result<()> {
37663787 Ok ( ( ) )
37673788}
37683789
3790+ /// Severity label for a gap weight (10/3/1 → error/warn/info).
3791+ fn severity_for ( weight : u32 ) -> & ' static str {
3792+ match weight {
3793+ 10 => "error" ,
3794+ 3 => "warn" ,
3795+ _ => "info" ,
3796+ }
3797+ }
3798+
3799+ /// Open the project DB, ingest new events, and build the full completeness
3800+ /// report — structural gaps plus artifact honesty drift — for `task_id`.
3801+ /// Exits(1) when the task is unknown.
3802+ fn assess_task (
3803+ task_id : & str ,
3804+ ) -> Result < (
3805+ rusqlite:: Connection ,
3806+ tj_core:: completeness:: CompletenessReport ,
3807+ ) > {
3808+ let cwd = std:: env:: current_dir ( ) ?;
3809+ let project_hash = tj_core:: project_hash:: from_path ( & cwd) ?;
3810+ let events_path = tj_core:: paths:: events_dir ( ) ?. join ( format ! ( "{project_hash}.jsonl" ) ) ;
3811+ let state_path = tj_core:: paths:: state_dir ( ) ?. join ( format ! ( "{project_hash}.sqlite" ) ) ;
3812+ let conn = tj_core:: db:: open ( & state_path) ?;
3813+ if events_path. exists ( ) {
3814+ tj_core:: db:: ingest_new_events ( & conn, & events_path, & project_hash) ?;
3815+ }
3816+
3817+ // Typed not-found exit, mirroring run_export_pr.
3818+ if let Err ( rusqlite:: Error :: QueryReturnedNoRows ) = conn. query_row (
3819+ "SELECT 1 FROM tasks WHERE task_id = ?1" ,
3820+ rusqlite:: params![ task_id] ,
3821+ |r| r. get :: < _ , i64 > ( 0 ) ,
3822+ ) {
3823+ eprintln ! ( "Error: task not found: {task_id}" ) ;
3824+ std:: process:: exit ( 1 ) ;
3825+ }
3826+
3827+ let mut report =
3828+ tj_core:: completeness:: assess ( & conn, task_id, tj_core:: completeness:: pending_count ( ) ) ?;
3829+ let arts = tj_core:: db:: task_artifacts ( & conn, task_id) ?;
3830+ report
3831+ . gaps
3832+ . extend ( tj_core:: completeness:: artifact_gaps_for_cwd ( & arts) ) ;
3833+ Ok ( ( conn, report) )
3834+ }
3835+
3836+ /// `task-journal check <id> [--json]` — print honesty score + gaps.
3837+ fn run_check ( task_id : & str , json : bool ) -> Result < ( ) > {
3838+ let ( _conn, report) = assess_task ( task_id) ?;
3839+ if json {
3840+ let gaps: Vec < _ > = report
3841+ . gaps
3842+ . iter ( )
3843+ . map ( |g| {
3844+ serde_json:: json!( {
3845+ "kind" : format!( "{:?}" , g. kind) ,
3846+ "severity" : severity_for( g. kind. weight( ) ) ,
3847+ "detail" : g. detail,
3848+ } )
3849+ } )
3850+ . collect ( ) ;
3851+ let out = serde_json:: json!( {
3852+ "task_id" : task_id,
3853+ "score" : report. score( ) ,
3854+ "gaps" : gaps,
3855+ } ) ;
3856+ println ! ( "{}" , serde_json:: to_string_pretty( & out) ?) ;
3857+ } else {
3858+ println ! (
3859+ "honesty score: {}/100 ({} gap(s))" ,
3860+ report. score( ) ,
3861+ report. gaps. len( )
3862+ ) ;
3863+ for g in & report. gaps {
3864+ println ! ( "- [{}] {}" , severity_for( g. kind. weight( ) ) , g. detail) ;
3865+ }
3866+ }
3867+ Ok ( ( ) )
3868+ }
3869+
3870+ /// `task-journal gaps <id> [--fill]` — list gaps, or with `--fill` print the
3871+ /// deterministic gap-fill prompt (embedding the current pack) for the agent.
3872+ fn run_gaps ( task_id : & str , fill : bool ) -> Result < ( ) > {
3873+ let ( conn, report) = assess_task ( task_id) ?;
3874+ if !fill {
3875+ println ! (
3876+ "honesty score: {}/100 ({} gap(s))" ,
3877+ report. score( ) ,
3878+ report. gaps. len( )
3879+ ) ;
3880+ for g in & report. gaps {
3881+ println ! ( "- [{}] {}" , severity_for( g. kind. weight( ) ) , g. detail) ;
3882+ }
3883+ return Ok ( ( ) ) ;
3884+ }
3885+ let pack = tj_core:: pack:: assemble ( & conn, task_id, tj_core:: pack:: PackMode :: Full ) ?;
3886+ match tj_core:: completeness:: build_gap_fill_prompt ( task_id, & report, & pack. text ) {
3887+ Some ( prompt) => print ! ( "{prompt}" ) ,
3888+ None => println ! ( "honesty score: 100/100 — no gaps to fill" ) ,
3889+ }
3890+ Ok ( ( ) )
3891+ }
3892+
37693893fn run_export_memory ( task : Option < & str > , _all_closed : bool , dry_run : bool ) -> Result < ( ) > {
37703894 const MAX_ITEMS : usize = 10 ;
37713895
0 commit comments