@@ -466,14 +466,30 @@ async def _multi_pass_synthesis(
466466 # === Pass 3: Gap Detection ===
467467 logger .debug ("Pass 3: Detecting gaps" )
468468
469+ # Rule-based gap detection (reliable, consistent)
470+ rule_gaps = self ._detect_gaps (jira_ctx , meeting_ctx , docs_ctx )
471+
472+ # LLM-based gap detection (additional insights)
469473 gap_prompt = GAP_DETECTION_PROMPT .format (context = synthesized )
470474 gap_response = await provider .generate (gap_prompt , max_tokens = 500 )
471- gaps = self ._parse_gaps (gap_response )
475+ llm_gaps = self ._parse_gaps (gap_response )
476+
477+ # Merge gaps (rule-based first, then unique LLM gaps)
478+ all_gaps = list (rule_gaps )
479+ existing_lower = {g .lower () for g in all_gaps }
480+ for gap in llm_gaps :
481+ if gap .lower () not in existing_lower :
482+ all_gaps .append (gap )
483+ existing_lower .add (gap .lower ())
472484
473485 # === Calculate Quality Score ===
474486 quality_score = self ._calculate_quality_score (jira_ctx , meeting_ctx , docs_ctx )
475487
476- return synthesized , quality_score , gaps
488+ # === Append Gaps to Synthesized Context ===
489+ if all_gaps :
490+ synthesized = self ._append_gaps_to_context (synthesized , all_gaps , quality_score )
491+
492+ return synthesized , quality_score , all_gaps
477493
478494 def _format_jira_for_extraction (self , ctx : JiraContext ) -> str :
479495 """Format Jira context for extraction prompt."""
@@ -579,41 +595,180 @@ def _calculate_quality_score(
579595 ) -> float :
580596 """Calculate context quality score based on completeness.
581597
582- Score components ( 0-1 each , averaged):
583- - Has description: 0.2
584- - Has acceptance criteria: 0.2
585- - Has meeting context: 0.2
586- - Has architecture docs: 0.2
587- - Has coding standards: 0.2
598+ Scoring rubric (each dimension 0-1, averaged):
599+ 1. Has clear requirements/acceptance criteria
600+ 2. Has implementation decisions (meeting context)
601+ 3. Has architecture context (relevant docs)
602+ 4. Has coding standards (standards docs)
603+ 5. Has related work context (linked issues)
588604
589605 Returns:
590606 Quality score between 0 and 1.
591607 """
592- score = 0.0
608+ dimensions : list [ float ] = []
593609
594- # Has description (0.2)
595- if jira_ctx .ticket .description :
596- score += 0.2
597-
598- # Has acceptance criteria (0.2)
599- if jira_ctx .ticket .acceptance_criteria :
600- score += 0.2
610+ # 1. Has clear requirements or acceptance criteria
611+ has_requirements = bool (
612+ jira_ctx .ticket .acceptance_criteria
613+ or (jira_ctx .ticket .description and len (jira_ctx .ticket .description ) > 100 )
614+ )
615+ dimensions .append (1.0 if has_requirements else 0.0 )
601616
602- # Has meeting context (0.2 )
603- if meeting_ctx .meetings :
604- score += 0.2
617+ # 2. Has implementation decisions (meeting context )
618+ has_meetings = bool ( meeting_ctx .meetings )
619+ dimensions . append ( 1.0 if has_meetings else 0.0 )
605620
606- # Has architecture docs (0.2 )
621+ # 3. Has architecture context (relevant docs matched )
607622 has_arch = any (s .doc_type == "architecture" for s in docs_ctx .sections )
608- if has_arch :
609- score += 0.2
623+ dimensions .append (1.0 if has_arch else 0.0 )
624+
625+ # 4. Has coding standards (standards docs included)
626+ has_standards = any (s .doc_type == "standards" for s in docs_ctx .sections )
627+ dimensions .append (1.0 if has_standards else 0.0 )
628+
629+ # 5. Has related work context (linked issues exist)
630+ has_related = bool (jira_ctx .linked_issues )
631+ dimensions .append (1.0 if has_related else 0.0 )
632+
633+ # Return average of all dimensions
634+ return sum (dimensions ) / len (dimensions ) if dimensions else 0.0
635+
636+ def _detect_gaps (
637+ self ,
638+ jira_ctx : JiraContext ,
639+ meeting_ctx : MeetingContext ,
640+ docs_ctx : DocsContext ,
641+ ) -> list [str ]:
642+ """Detect specific gaps in context and return actionable messages.
643+
644+ Checks for:
645+ - Missing acceptance criteria
646+ - Missing meeting discussions
647+ - Missing architecture documentation
648+ - Missing coding standards
649+ - Missing linked/related tickets
650+
651+ Returns:
652+ List of actionable gap messages.
653+ """
654+ gaps : list [str ] = []
655+
656+ # Check for acceptance criteria
657+ if not jira_ctx .ticket .acceptance_criteria :
658+ gaps .append (
659+ "No acceptance criteria found in Jira ticket — "
660+ "consider adding clear done criteria before implementation"
661+ )
610662
611- # Has coding standards (0.2)
663+ # Check for meeting discussions
664+ if not meeting_ctx .meetings :
665+ gaps .append (
666+ "No meeting discussions found for this task — "
667+ "consider a planning discussion with the team"
668+ )
669+
670+ # Check for architecture documentation
671+ has_arch = any (s .doc_type == "architecture" for s in docs_ctx .sections )
672+ if not has_arch :
673+ # Try to identify the service area from components or labels
674+ service_area = None
675+ if jira_ctx .ticket .components :
676+ service_area = jira_ctx .ticket .components [0 ]
677+ elif jira_ctx .ticket .labels :
678+ service_area = jira_ctx .ticket .labels [0 ]
679+
680+ if service_area :
681+ gaps .append (
682+ f"No architecture documentation found for service area '{ service_area } ' — "
683+ "consider documenting the architectural approach"
684+ )
685+ else :
686+ gaps .append (
687+ "No architecture documentation found — "
688+ "consider documenting the architectural approach for this feature"
689+ )
690+
691+ # Check for coding standards
612692 has_standards = any (s .doc_type == "standards" for s in docs_ctx .sections )
613- if has_standards :
614- score += 0.2
693+ if not has_standards :
694+ gaps .append (
695+ "No coding standards documentation found — "
696+ "check if CLAUDE.md or standards/ docs exist in the repository"
697+ )
698+
699+ # Check for linked/related tickets
700+ if not jira_ctx .linked_issues :
701+ gaps .append (
702+ "No linked or related tickets — "
703+ "this task may lack broader context; consider linking related work"
704+ )
705+
706+ # Additional checks from comments and description
707+ ticket = jira_ctx .ticket
708+ description = (ticket .description or "" ).lower ()
709+
710+ # Check for unclear dependencies
711+ no_ac = not ticket .acceptance_criteria
712+ no_depends_mention = "depends" not in description and "blocked" not in description
713+ if no_ac and no_depends_mention and not jira_ctx .linked_issues :
714+ gaps .append (
715+ "No dependencies identified — "
716+ "verify this task has no blocking dependencies before starting"
717+ )
718+
719+ return gaps
720+
721+ def _append_gaps_to_context (
722+ self ,
723+ synthesized : str ,
724+ gaps : list [str ],
725+ quality_score : float ,
726+ ) -> str :
727+ """Append identified gaps to the synthesized context.
728+
729+ Adds a section at the end of the context to inform the AI agent
730+ about missing information that may affect implementation.
731+
732+ Args:
733+ synthesized: The synthesized context markdown.
734+ gaps: List of identified gaps.
735+ quality_score: The calculated quality score.
736+
737+ Returns:
738+ Synthesized context with gaps section appended.
739+ """
740+ if not gaps :
741+ return synthesized
742+
743+ # Format quality indicator
744+ if quality_score >= 0.8 :
745+ quality_label = "Good"
746+ elif quality_score >= 0.6 :
747+ quality_label = "Moderate"
748+ elif quality_score >= 0.4 :
749+ quality_label = "Limited"
750+ else :
751+ quality_label = "Incomplete"
752+
753+ gaps_section = f"""
754+
755+ ---
756+
757+ ## ⚠️ Context Quality: { quality_label } ({ quality_score :.0%} )
758+
759+ The following gaps were identified in the available context.
760+ Consider addressing these before or during implementation:
761+
762+ """
763+ for gap in gaps :
764+ gaps_section += f"- { gap } \n "
765+
766+ gaps_section += """
767+ *These gaps were automatically detected during context preprocessing.*
768+ *Some may not be relevant to your specific task.*
769+ """
615770
616- return score
771+ return synthesized + gaps_section
617772
618773 async def close (self ) -> None :
619774 """Close resources."""
0 commit comments