@pmaddire/gcie 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -422,7 +422,16 @@ def _mentioned_file_paths(file_text: dict[str, str], query: str) -> list[tuple[f
422
422
  matches.sort(key=lambda item: (-item[0], item[1]))
423
423
  return matches
424
424
 
425
-
425
+ def _target_matched(target: str, files: list[str]) -> bool:
426
+ target_norm = target.replace("\\", "/").lower()
427
+ target_name = Path(target_norm).name
428
+ return any(
429
+ file_path.lower() == target_norm
430
+ or Path(file_path.lower()).name == target_name
431
+ for file_path in files
432
+ )
433
+
434
+
426
435
  def _layer_bucket(path: str | None) -> str:
427
436
  if not path:
428
437
  return "unknown"
@@ -2032,6 +2041,129 @@ def _context_fallback_reason(
2032
2041
  return None
2033
2042
 
2034
2043
 
2044
+
2045
+ def _context_retrieval_diagnostics(
2046
+ payload,
2047
+ query: str,
2048
+ intent: str | None,
2049
+ file_text: dict[str, str],
2050
+ attached: dict[str, dict[str, object]],
2051
+ *,
2052
+ budget: int | None = None,
2053
+ ) -> dict[str, object]:
2054
+ """Summarize retrieval coverage and confidence for fallback gating."""
2055
+ selected_paths = _selected_file_paths(payload.snippets)
2056
+ code_paths = [path for path in selected_paths if _classify_path(path) == "code"]
2057
+ families = {_candidate_family(path) for path in code_paths}
2058
+ selected_scores = sorted((float(snippet.score) for snippet in payload.snippets), reverse=True)
2059
+ score_top = selected_scores[0] if selected_scores else 0.0
2060
+ score_second = selected_scores[1] if len(selected_scores) > 1 else 0.0
2061
+ score_gap = max(0.0, score_top - score_second)
2062
+ score_ratio = score_top / max(score_second, 0.15) if selected_scores else 0.0
2063
+
2064
+ explicit_targets = [path for _, path in _mentioned_file_paths(file_text, query)]
2065
+ explicit_hits = sum(1 for target in explicit_targets if _target_matched(target, selected_paths))
2066
+ explicit_missing = max(0, len(explicit_targets) - explicit_hits)
2067
+ referenced_missing = _referenced_companion_paths(code_paths, file_text, set(selected_paths))
2068
+
2069
+ strong_files = 0
2070
+ for snippet in payload.snippets:
2071
+ meta = attached.get(snippet.node_id, {})
2072
+ channels = meta.get("channels", []) if isinstance(meta, dict) else []
2073
+ if len(channels) >= 2 and (_node_file_path(snippet.node_id) or "") in code_paths:
2074
+ strong_files += 1
2075
+
2076
+ selected_count = len(selected_paths)
2077
+ code_count = len(code_paths)
2078
+ coverage_ratio = (code_count / selected_count) if selected_count else 0.0
2079
+ explicit_coverage_ratio = (explicit_hits / len(explicit_targets)) if explicit_targets else 1.0
2080
+ family_diversity = len(families)
2081
+
2082
+ confidence = 0.0
2083
+ if selected_count:
2084
+ confidence += 0.18
2085
+ confidence += min(0.22, coverage_ratio * 0.22)
2086
+ confidence += min(0.18, explicit_coverage_ratio * 0.18)
2087
+ confidence += min(0.12, 0.04 * min(code_count, 3))
2088
+ confidence += min(0.08, 0.04 * min(strong_files, 2))
2089
+ confidence += min(0.08, 0.04 * min(family_diversity, 2))
2090
+ confidence += min(0.10, score_top * 0.10)
2091
+ confidence += min(0.06, score_gap * 0.35)
2092
+
2093
+ reason = _context_fallback_reason(payload, query, intent, file_text, attached)
2094
+ if reason == "support_family_missing":
2095
+ confidence -= 0.28
2096
+ elif reason == "insufficient_context_coverage":
2097
+ confidence -= 0.22
2098
+ elif reason == "low_context_confidence":
2099
+ confidence -= 0.16
2100
+ if explicit_missing:
2101
+ confidence -= min(0.18, 0.09 * explicit_missing)
2102
+ if referenced_missing:
2103
+ confidence -= min(0.18, 0.08 * len(referenced_missing))
2104
+ if _is_system_query(query) and (code_count < 2 or family_diversity < 2):
2105
+ confidence -= 0.12
2106
+ if _support_promotion_enabled(query, intent) and strong_files < 2:
2107
+ confidence -= 0.10
2108
+ if budget is not None and int(payload.total_tokens_estimate) > budget:
2109
+ confidence -= 0.05
2110
+
2111
+ confidence = max(0.0, min(1.0, confidence))
2112
+ if confidence >= 0.75:
2113
+ confidence_band = "high"
2114
+ elif confidence >= 0.55:
2115
+ confidence_band = "guarded"
2116
+ else:
2117
+ confidence_band = "low"
2118
+
2119
+ return {
2120
+ "confidence": round(confidence, 3),
2121
+ "confidence_band": confidence_band,
2122
+ "selected_count": selected_count,
2123
+ "code_count": code_count,
2124
+ "family_count": family_diversity,
2125
+ "strong_file_count": strong_files,
2126
+ "coverage_ratio": round(coverage_ratio, 3),
2127
+ "explicit_target_count": len(explicit_targets),
2128
+ "explicit_target_hits": explicit_hits,
2129
+ "explicit_target_misses": explicit_missing,
2130
+ "explicit_coverage_ratio": round(explicit_coverage_ratio, 3),
2131
+ "referenced_missing_count": len(referenced_missing),
2132
+ "score_top": round(score_top, 3),
2133
+ "score_second": round(score_second, 3),
2134
+ "score_gap": round(score_gap, 3),
2135
+ "score_ratio": round(score_ratio, 3),
2136
+ "fallback_reason": reason,
2137
+ }
2138
+
2139
+
2140
+ def _confidence_gate_threshold(strict_accuracy: bool) -> float:
2141
+ return 0.72 if strict_accuracy else 0.58
2142
+
2143
+
2144
+ def _confidence_gate_triggers(diagnostics: dict[str, object], *, strict_accuracy: bool) -> bool:
2145
+ confidence = float(diagnostics.get("confidence", 0.0) or 0.0)
2146
+ if diagnostics.get("fallback_reason"):
2147
+ return True
2148
+ if confidence < 0.35:
2149
+ return True
2150
+ return confidence < _confidence_gate_threshold(strict_accuracy)
2151
+
2152
+
2153
+ def _accuracy_fallback_stage(
2154
+ diagnostics: dict[str, object],
2155
+ *,
2156
+ search_used: bool,
2157
+ strict_accuracy: bool,
2158
+ ) -> str:
2159
+ if not search_used:
2160
+ return "none"
2161
+ if strict_accuracy and float(diagnostics.get("confidence", 0.0) or 0.0) < 0.72:
2162
+ return "search_fallback_strict"
2163
+ if diagnostics.get("fallback_reason"):
2164
+ return "search_fallback"
2165
+ return "confidence_gate"
2166
+
2035
2167
  def _normal_search_fallback_snippets(
2036
2168
  file_text: dict[str, str],
2037
2169
  query: str,
@@ -2085,13 +2217,22 @@ def _apply_normal_search_fallback(
2085
2217
  intent: str | None,
2086
2218
  payload,
2087
2219
  attached: dict[str, dict[str, object]],
2220
+ *,
2221
+ limit: int = 4,
2088
2222
  ) -> tuple[list[RankedSnippet], dict[str, dict[str, object]], set[str], str | None, bool]:
2089
2223
  reason = _context_fallback_reason(payload, query, intent, file_text, attached)
2090
2224
  if not reason:
2091
2225
  return ranked, attached, set(), None, False
2092
2226
 
2093
2227
  existing_ids = {item.node_id for item in ranked}
2094
- fallback_candidates = _normal_search_fallback_snippets(file_text, query, intent, payload.snippets, existing_ids)
2228
+ fallback_candidates = _normal_search_fallback_snippets(
2229
+ file_text,
2230
+ query,
2231
+ intent,
2232
+ payload.snippets,
2233
+ existing_ids,
2234
+ limit=limit,
2235
+ )
2095
2236
  if not fallback_candidates:
2096
2237
  return ranked, attached, set(), reason, True
2097
2238
 
@@ -2540,7 +2681,7 @@ def run_context_basic(path: str, query: str, budget: int | None, intent: str | N
2540
2681
  }
2541
2682
 
2542
2683
 
2543
- def run_context(path: str, query: str, budget: int | None, intent: str | None, top_k: int = 40) -> dict:
2684
+ def run_context(path: str, query: str, budget: int | None, intent: str | None, top_k: int = 40, *, strict_accuracy: bool = False) -> dict:
2544
2685
  target = Path(path)
2545
2686
 
2546
2687
  if target.is_dir():
@@ -2699,148 +2840,208 @@ def run_context(path: str, query: str, budget: int | None, intent: str | None, t
2699
2840
  mandatory_node_ids=mandatory_node_ids,
2700
2841
  )
2701
2842
 
2702
- payload = build_context(
2703
- query,
2704
- packed_ranked,
2705
- token_budget=budget,
2706
- intent=intent,
2707
- mandatory_node_ids=mandatory_node_ids,
2708
- )
2709
-
2710
- ranked, attached, fallback_priority_ids, fallback_reason, fallback_search_used = _apply_normal_search_fallback(
2711
- ranked,
2712
- file_text,
2713
- query,
2714
- intent,
2715
- payload,
2716
- attached,
2717
- )
2718
- if fallback_priority_ids:
2719
- combined_priority_ids = support_priority_ids | fallback_priority_ids | linked_priority_ids | chain_priority_ids
2720
- ranked, support_priority_ids = _collapse_support_query_snippets(ranked, query, intent, file_text)
2721
- combined_priority_ids |= support_priority_ids
2722
- ranked = _promote_priority_first(
2723
- ranked,
2724
- explicit_priority_ids,
2725
- linked_priority_ids,
2726
- chain_priority_ids,
2727
- explicit_target_paths,
2728
- )
2729
- mandatory_node_ids = _mandatory_node_ids(
2730
- ranked,
2731
- query,
2732
- intent,
2733
- support_priority_ids=combined_priority_ids,
2734
- explicit_priority_ids=explicit_priority_ids | linked_priority_ids | chain_priority_ids,
2735
- )
2736
- mandatory_node_ids = _trim_mandatory_node_ids_for_dense_queries(
2737
- mandatory_node_ids,
2738
- ranked,
2739
- query,
2740
- intent,
2741
- explicit_priority_ids=explicit_priority_ids | linked_priority_ids | chain_priority_ids,
2742
- )
2743
- ranked = _mmr_diversify_ranked(
2744
- ranked,
2745
- attached,
2746
- query_shape=query_shape,
2747
- mandatory_node_ids=mandatory_node_ids,
2748
- explicit_priority_ids=explicit_priority_ids,
2749
- linked_priority_ids=linked_priority_ids,
2750
- chain_priority_ids=chain_priority_ids,
2751
- )
2752
- ranked = _prune_non_musthave_candidates(
2753
- ranked,
2754
- attached,
2755
- query=query,
2756
- intent=intent,
2757
- query_shape=query_shape,
2758
- mandatory_node_ids=mandatory_node_ids,
2759
- explicit_priority_ids=explicit_priority_ids,
2760
- linked_priority_ids=linked_priority_ids,
2761
- chain_priority_ids=chain_priority_ids,
2762
- )
2763
- ranked = _enforce_family_file_cap(
2764
- ranked,
2765
- query=query,
2766
- intent=intent,
2767
- query_shape=query_shape,
2768
- mandatory_node_ids=mandatory_node_ids,
2769
- explicit_priority_ids=explicit_priority_ids,
2770
- linked_priority_ids=linked_priority_ids,
2771
- chain_priority_ids=chain_priority_ids,
2772
- )
2773
- ranked = _order_for_packing(
2774
- ranked,
2775
- mandatory_node_ids=mandatory_node_ids,
2776
- explicit_priority_ids=explicit_priority_ids,
2777
- linked_priority_ids=linked_priority_ids,
2778
- chain_priority_ids=chain_priority_ids,
2779
- )
2780
- pivot_node_ids, skeleton_node_ids, compact_node_ids = _packaging_sets(
2781
- ranked,
2782
- attached,
2783
- explicit_priority_ids=explicit_priority_ids,
2784
- linked_priority_ids=linked_priority_ids,
2785
- chain_priority_ids=chain_priority_ids,
2786
- mandatory_node_ids=mandatory_node_ids,
2787
- )
2788
- packed_ranked = _apply_packaging(
2789
- ranked,
2790
- pivot_node_ids,
2791
- skeleton_node_ids,
2792
- compact_node_ids,
2793
- query=query,
2794
- intent=intent,
2795
- query_shape=query_shape,
2796
- mandatory_node_ids=mandatory_node_ids,
2797
- )
2798
- payload = build_context(
2799
- query,
2800
- packed_ranked,
2801
- token_budget=budget,
2802
- intent=intent,
2803
- mandatory_node_ids=mandatory_node_ids,
2804
- )
2805
- snippets_out: list[dict[str, object]] = []
2806
- for snippet in payload.snippets:
2807
- base_meta = attached.get(
2808
- snippet.node_id,
2809
- {
2810
- "channels": [],
2811
- "family": _candidate_family(_node_file_path(snippet.node_id)),
2812
- "file_role": _file_role(_node_file_path(snippet.node_id)),
2813
- "candidate_role": "ranked",
2814
- "query_shape": query_shape,
2815
- "file_class": _classify_path(_node_file_path(snippet.node_id) or ""),
2816
- "why_included": "selected",
2817
- },
2818
- )
2819
- meta = dict(base_meta)
2820
- if snippet.node_id in pivot_node_ids:
2821
- meta["packaging_role"] = "pivot"
2822
- elif snippet.node_id in skeleton_node_ids:
2823
- meta["packaging_role"] = "adjacent_support"
2824
- else:
2825
- meta["packaging_role"] = "full"
2826
-
2827
- snippets_out.append(
2828
- {
2829
- "node_id": snippet.node_id,
2830
- "score": snippet.score,
2831
- "content": snippet.content,
2832
- "attached_context": meta,
2833
- }
2834
- )
2835
-
2836
- return {
2837
- "query": payload.query,
2838
- "tokens": payload.total_tokens_estimate,
2839
- "snippets": snippets_out,
2840
- "fallback_search_used": fallback_search_used,
2841
- "fallback_reason": fallback_reason,
2842
- }
2843
-
2843
+ payload = build_context(
2844
+ query,
2845
+ packed_ranked,
2846
+ token_budget=budget,
2847
+ intent=intent,
2848
+ mandatory_node_ids=mandatory_node_ids,
2849
+ )
2850
+ pre_fallback_diagnostics = _context_retrieval_diagnostics(
2851
+ payload,
2852
+ query,
2853
+ intent,
2854
+ file_text,
2855
+ attached,
2856
+ budget=budget,
2857
+ )
2858
+ fallback_triggered = _confidence_gate_triggers(pre_fallback_diagnostics, strict_accuracy=strict_accuracy)
2859
+ fallback_stage = "none"
2860
+ fallback_reason = pre_fallback_diagnostics.get("fallback_reason")
2861
+ fallback_search_used = False
2862
+
2863
+ if fallback_triggered:
2864
+ fallback_limit = 6 if strict_accuracy else 4
2865
+ ranked, attached, fallback_priority_ids, fallback_reason, fallback_search_used = _apply_normal_search_fallback(
2866
+ ranked,
2867
+ file_text,
2868
+ query,
2869
+ intent,
2870
+ payload,
2871
+ attached,
2872
+ limit=fallback_limit,
2873
+ )
2874
+ if fallback_priority_ids:
2875
+ combined_priority_ids = support_priority_ids | fallback_priority_ids | linked_priority_ids | chain_priority_ids
2876
+ ranked, support_priority_ids = _collapse_support_query_snippets(ranked, query, intent, file_text)
2877
+ combined_priority_ids |= support_priority_ids
2878
+ ranked = _promote_priority_first(
2879
+ ranked,
2880
+ explicit_priority_ids,
2881
+ linked_priority_ids,
2882
+ chain_priority_ids,
2883
+ explicit_target_paths,
2884
+ )
2885
+ mandatory_node_ids = _mandatory_node_ids(
2886
+ ranked,
2887
+ query,
2888
+ intent,
2889
+ support_priority_ids=combined_priority_ids,
2890
+ explicit_priority_ids=explicit_priority_ids | linked_priority_ids | chain_priority_ids,
2891
+ )
2892
+ mandatory_node_ids = _trim_mandatory_node_ids_for_dense_queries(
2893
+ mandatory_node_ids,
2894
+ ranked,
2895
+ query,
2896
+ intent,
2897
+ explicit_priority_ids=explicit_priority_ids | linked_priority_ids | chain_priority_ids,
2898
+ )
2899
+ ranked = _mmr_diversify_ranked(
2900
+ ranked,
2901
+ attached,
2902
+ query_shape=query_shape,
2903
+ mandatory_node_ids=mandatory_node_ids,
2904
+ explicit_priority_ids=explicit_priority_ids,
2905
+ linked_priority_ids=linked_priority_ids,
2906
+ chain_priority_ids=chain_priority_ids,
2907
+ )
2908
+ ranked = _prune_non_musthave_candidates(
2909
+ ranked,
2910
+ attached,
2911
+ query=query,
2912
+ intent=intent,
2913
+ query_shape=query_shape,
2914
+ mandatory_node_ids=mandatory_node_ids,
2915
+ explicit_priority_ids=explicit_priority_ids,
2916
+ linked_priority_ids=linked_priority_ids,
2917
+ chain_priority_ids=chain_priority_ids,
2918
+ )
2919
+ ranked = _enforce_family_file_cap(
2920
+ ranked,
2921
+ query=query,
2922
+ intent=intent,
2923
+ query_shape=query_shape,
2924
+ mandatory_node_ids=mandatory_node_ids,
2925
+ explicit_priority_ids=explicit_priority_ids,
2926
+ linked_priority_ids=linked_priority_ids,
2927
+ chain_priority_ids=chain_priority_ids,
2928
+ )
2929
+ ranked = _order_for_packing(
2930
+ ranked,
2931
+ mandatory_node_ids=mandatory_node_ids,
2932
+ explicit_priority_ids=explicit_priority_ids,
2933
+ linked_priority_ids=linked_priority_ids,
2934
+ chain_priority_ids=chain_priority_ids,
2935
+ )
2936
+ pivot_node_ids, skeleton_node_ids, compact_node_ids = _packaging_sets(
2937
+ ranked,
2938
+ attached,
2939
+ explicit_priority_ids=explicit_priority_ids,
2940
+ linked_priority_ids=linked_priority_ids,
2941
+ chain_priority_ids=chain_priority_ids,
2942
+ mandatory_node_ids=mandatory_node_ids,
2943
+ )
2944
+ packed_ranked = _apply_packaging(
2945
+ ranked,
2946
+ pivot_node_ids,
2947
+ skeleton_node_ids,
2948
+ compact_node_ids,
2949
+ query=query,
2950
+ intent=intent,
2951
+ query_shape=query_shape,
2952
+ mandatory_node_ids=mandatory_node_ids,
2953
+ )
2954
+ payload = build_context(
2955
+ query,
2956
+ packed_ranked,
2957
+ token_budget=budget,
2958
+ intent=intent,
2959
+ mandatory_node_ids=mandatory_node_ids,
2960
+ )
2961
+ fallback_stage = _accuracy_fallback_stage(
2962
+ pre_fallback_diagnostics,
2963
+ search_used=bool(fallback_search_used),
2964
+ strict_accuracy=strict_accuracy,
2965
+ )
2966
+ if not fallback_reason:
2967
+ fallback_reason = pre_fallback_diagnostics.get("fallback_reason")
2968
+ if not fallback_reason and fallback_triggered:
2969
+ fallback_reason = "confidence_gate"
2970
+
2971
+ diagnostics = _context_retrieval_diagnostics(
2972
+ payload,
2973
+ query,
2974
+ intent,
2975
+ file_text,
2976
+ attached,
2977
+ budget=budget,
2978
+ )
2979
+ if fallback_stage == "none" and fallback_triggered:
2980
+ fallback_stage = "confidence_gate"
2981
+
2982
+ snippets_out: list[dict[str, object]] = []
2983
+ for snippet in payload.snippets:
2984
+ base_meta = attached.get(
2985
+ snippet.node_id,
2986
+ {
2987
+ "channels": [],
2988
+ "family": _candidate_family(_node_file_path(snippet.node_id)),
2989
+ "file_role": _file_role(_node_file_path(snippet.node_id)),
2990
+ "candidate_role": "ranked",
2991
+ "query_shape": query_shape,
2992
+ "file_class": _classify_path(_node_file_path(snippet.node_id) or ""),
2993
+ "why_included": "selected",
2994
+ },
2995
+ )
2996
+ meta = dict(base_meta)
2997
+ if snippet.node_id in pivot_node_ids:
2998
+ meta["packaging_role"] = "pivot"
2999
+ elif snippet.node_id in skeleton_node_ids:
3000
+ meta["packaging_role"] = "adjacent_support"
3001
+ else:
3002
+ meta["packaging_role"] = "full"
3003
+
3004
+ snippets_out.append(
3005
+ {
3006
+ "node_id": snippet.node_id,
3007
+ "score": snippet.score,
3008
+ "content": snippet.content,
3009
+ "attached_context": meta,
3010
+ }
3011
+ )
3012
+
3013
+ return {
3014
+ "query": payload.query,
3015
+ "tokens": payload.total_tokens_estimate,
3016
+ "snippets": snippets_out,
3017
+ "fallback_search_used": fallback_search_used,
3018
+ "fallback_reason": fallback_reason,
3019
+ "retrieval_fallback_stage": fallback_stage,
3020
+ "retrieval_accuracy_gate_triggered": fallback_triggered,
3021
+ "retrieval_confidence": diagnostics.get("confidence", 0.0),
3022
+ "retrieval_confidence_pre_fallback": pre_fallback_diagnostics.get("confidence", 0.0),
3023
+ "retrieval_confidence_band": diagnostics.get("confidence_band", "low"),
3024
+ "retrieval_coverage": {
3025
+ "selected_count": diagnostics.get("selected_count", 0),
3026
+ "code_count": diagnostics.get("code_count", 0),
3027
+ "family_count": diagnostics.get("family_count", 0),
3028
+ "strong_file_count": diagnostics.get("strong_file_count", 0),
3029
+ "coverage_ratio": diagnostics.get("coverage_ratio", 0.0),
3030
+ "explicit_target_count": diagnostics.get("explicit_target_count", 0),
3031
+ "explicit_target_hits": diagnostics.get("explicit_target_hits", 0),
3032
+ "explicit_target_misses": diagnostics.get("explicit_target_misses", 0),
3033
+ "explicit_coverage_ratio": diagnostics.get("explicit_coverage_ratio", 0.0),
3034
+ "referenced_missing_count": diagnostics.get("referenced_missing_count", 0),
3035
+ },
3036
+ "retrieval_score_stats": {
3037
+ "score_top": diagnostics.get("score_top", 0.0),
3038
+ "score_second": diagnostics.get("score_second", 0.0),
3039
+ "score_gap": diagnostics.get("score_gap", 0.0),
3040
+ "score_ratio": diagnostics.get("score_ratio", 0.0),
3041
+ },
3042
+ "retrieval_diagnostics": diagnostics,
3043
+ }
3044
+
2844
3045
  def _adaptive_companion_candidates(
2845
3046
  payload: dict,
2846
3047
  file_text: dict[str, str],
@@ -3066,6 +3267,11 @@ def run_context_adaptive(
3066
3267
 
3067
3268
 
3068
3269
 
3270
+
3271
+
3272
+
3273
+
3274
+
3069
3275
 
3070
3276
 
3071
3277