@@ -220,9 +220,16 @@ Return JSON only.",
220220 . and_then ( |content| parse_interview_payload_from_text ( & content) )
221221 . and_then ( |payload| sanitize_generated_interview_payload ( payload, & context) ) ;
222222
223- let plan_validation = plan_context
224- . as_ref ( )
225- . and_then ( |ctx| ctx. plan_validation . clone ( ) ) ;
223+ let response_plan_validation = response_text
224+ . and_then ( |text| extract_proposed_plan ( text) . plan_text )
225+ . as_deref ( )
226+ . map ( validate_plan_content) ;
227+ let plan_validation = select_best_plan_validation (
228+ plan_context
229+ . as_ref ( )
230+ . and_then ( |ctx| ctx. plan_validation . as_ref ( ) ) ,
231+ response_plan_validation. as_ref ( ) ,
232+ ) ;
226233 let tracker_summary = plan_context
227234 . as_ref ( )
228235 . and_then ( |ctx| ctx. tracker_summary . clone ( ) ) ;
@@ -320,10 +327,30 @@ fn collect_interview_research_context(
320327 }
321328 }
322329
330+ let extracted_plan = response_text
331+ . and_then ( |text| extract_proposed_plan ( text) . plan_text )
332+ . filter ( |text| !text. trim ( ) . is_empty ( ) ) ;
333+ let extracted_plan_excerpt = extracted_plan
334+ . as_deref ( )
335+ . map ( |content| truncate_for_context ( content, MAX_PLAN_DRAFT_CHARS ) ) ;
336+ let extracted_plan_validation = extracted_plan. as_deref ( ) . map ( validate_plan_content) ;
337+ let preferred_validation = select_best_plan_validation (
338+ plan_context. and_then ( |ctx| ctx. plan_validation . as_ref ( ) ) ,
339+ extracted_plan_validation. as_ref ( ) ,
340+ ) ;
341+ let prefer_extracted_plan = match (
342+ plan_context. and_then ( |ctx| ctx. plan_validation . as_ref ( ) ) ,
343+ extracted_plan_validation. as_ref ( ) ,
344+ ) {
345+ ( Some ( existing) , Some ( candidate) ) => is_validation_better ( candidate, existing) ,
346+ ( None , Some ( _) ) => true ,
347+ _ => false ,
348+ } ;
349+
323350 let mut open_decision_hints = response_text
324351 . map ( extract_open_decision_hints)
325352 . unwrap_or_default ( ) ;
326- if let Some ( plan_validation) = plan_context . and_then ( |ctx| ctx . plan_validation . as_ref ( ) ) {
353+ if let Some ( plan_validation) = preferred_validation . as_ref ( ) {
327354 for decision in plan_validation
328355 . open_decisions
329356 . iter ( )
@@ -333,21 +360,45 @@ fn collect_interview_research_context(
333360 }
334361 }
335362
363+ let extracted_plan_snapshot = extracted_plan_validation
364+ . as_ref ( )
365+ . map ( PlanValidationSnapshot :: from) ;
366+
336367 let ( plan_draft_excerpt, plan_draft_path, plan_validation, task_tracker_excerpt,
337368 task_tracker_path, task_tracker_summary) = if let Some ( plan_context) = plan_context {
369+ let plan_excerpt = if prefer_extracted_plan {
370+ extracted_plan_excerpt
371+ . clone ( )
372+ . or_else ( || plan_context. plan_excerpt . clone ( ) )
373+ } else {
374+ plan_context
375+ . plan_excerpt
376+ . clone ( )
377+ . or_else ( || extracted_plan_excerpt. clone ( ) )
378+ } ;
338379 (
339- plan_context . plan_excerpt . clone ( ) ,
380+ plan_excerpt,
340381 plan_context. plan_path . clone ( ) ,
341- plan_context
342- . plan_validation
382+ preferred_validation
343383 . as_ref ( )
344- . map ( PlanValidationSnapshot :: from) ,
384+ . map ( PlanValidationSnapshot :: from)
385+ . or ( extracted_plan_snapshot. clone ( ) ) ,
345386 plan_context. tracker_excerpt . clone ( ) ,
346387 plan_context. tracker_path . clone ( ) ,
347388 plan_context. tracker_summary . clone ( ) ,
348389 )
349390 } else {
350- ( None , None , None , None , None , None )
391+ (
392+ extracted_plan_excerpt,
393+ None ,
394+ preferred_validation
395+ . as_ref ( )
396+ . map ( PlanValidationSnapshot :: from)
397+ . or ( extracted_plan_snapshot) ,
398+ None ,
399+ None ,
400+ None ,
401+ )
351402 } ;
352403
353404 InterviewResearchContext {
@@ -1216,6 +1267,68 @@ fn extract_plan_validation(response_text: Option<&str>) -> Option<PlanValidation
12161267 Some ( validate_plan_content ( & plan_text) )
12171268}
12181269
1270+ fn select_best_plan_validation (
1271+ current : Option < & PlanValidationReport > ,
1272+ candidate : Option < & PlanValidationReport > ,
1273+ ) -> Option < PlanValidationReport > {
1274+ match ( current, candidate) {
1275+ ( None , None ) => None ,
1276+ ( Some ( current) , None ) => Some ( current. clone ( ) ) ,
1277+ ( None , Some ( candidate) ) => Some ( candidate. clone ( ) ) ,
1278+ ( Some ( current) , Some ( candidate) ) => {
1279+ if is_validation_better ( candidate, current) {
1280+ Some ( candidate. clone ( ) )
1281+ } else {
1282+ Some ( current. clone ( ) )
1283+ }
1284+ }
1285+ }
1286+ }
1287+
1288+ fn is_validation_better (
1289+ candidate : & PlanValidationReport ,
1290+ current : & PlanValidationReport ,
1291+ ) -> bool {
1292+ if candidate. is_ready ( ) && !current. is_ready ( ) {
1293+ return true ;
1294+ }
1295+ if current. is_ready ( ) && !candidate. is_ready ( ) {
1296+ return false ;
1297+ }
1298+
1299+ let candidate_missing = candidate. missing_sections . len ( ) ;
1300+ let current_missing = current. missing_sections . len ( ) ;
1301+ if candidate_missing != current_missing {
1302+ return candidate_missing < current_missing;
1303+ }
1304+
1305+ let candidate_placeholders = candidate. placeholder_tokens . len ( ) ;
1306+ let current_placeholders = current. placeholder_tokens . len ( ) ;
1307+ if candidate_placeholders != current_placeholders {
1308+ return candidate_placeholders < current_placeholders;
1309+ }
1310+
1311+ let candidate_open = candidate. open_decisions . len ( ) ;
1312+ let current_open = current. open_decisions . len ( ) ;
1313+ if candidate_open != current_open {
1314+ return candidate_open < current_open;
1315+ }
1316+
1317+ if candidate. summary_present != current. summary_present {
1318+ return candidate. summary_present ;
1319+ }
1320+
1321+ if candidate. implementation_step_count != current. implementation_step_count {
1322+ return candidate. implementation_step_count > current. implementation_step_count ;
1323+ }
1324+
1325+ if candidate. validation_item_count != current. validation_item_count {
1326+ return candidate. validation_item_count > current. validation_item_count ;
1327+ }
1328+
1329+ candidate. assumption_count > current. assumption_count
1330+ }
1331+
12191332fn build_fallback_question (
12201333 id : & str ,
12211334 header : & str ,
0 commit comments