pycodesage 0.3.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (111) hide show
  1. {pycodesage-0.3.0 → pycodesage-0.3.1}/PKG-INFO +1 -1
  2. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/__init__.py +1 -1
  3. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/engine.py +330 -87
  4. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/query_expansion.py +13 -6
  5. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/commands/init.py +6 -4
  6. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/llm/embeddings.py +50 -9
  7. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/llm/provider.py +52 -6
  8. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/pattern_store.py +23 -8
  9. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/lance_store.py +21 -6
  10. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/manager.py +36 -9
  11. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/config.py +160 -44
  12. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/rate_limiter.py +31 -10
  13. {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/PKG-INFO +1 -1
  14. {pycodesage-0.3.0 → pycodesage-0.3.1}/pyproject.toml +1 -1
  15. {pycodesage-0.3.0 → pycodesage-0.3.1}/LICENSE +0 -0
  16. {pycodesage-0.3.0 → pycodesage-0.3.1}/README.md +0 -0
  17. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/__main__.py +0 -0
  18. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/__init__.py +0 -0
  19. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/commands.py +0 -0
  20. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/context.py +0 -0
  21. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/models.py +0 -0
  22. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/prompts.py +0 -0
  23. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/__init__.py +0 -0
  24. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/commands/__init__.py +0 -0
  25. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/commands/chat.py +0 -0
  26. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/commands/index.py +0 -0
  27. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/groups/__init__.py +0 -0
  28. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/groups/mcp.py +0 -0
  29. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/main.py +0 -0
  30. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/__init__.py +0 -0
  31. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/console.py +0 -0
  32. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/decorators.py +0 -0
  33. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/formatters.py +0 -0
  34. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/options.py +0 -0
  35. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/signals.py +0 -0
  36. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/__init__.py +0 -0
  37. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/confidence.py +0 -0
  38. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/context_provider.py +0 -0
  39. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/deep_analyzer.py +0 -0
  40. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/indexer.py +0 -0
  41. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/relationship_extractor.py +0 -0
  42. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/resource_manager.py +0 -0
  43. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/suggester.py +0 -0
  44. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/docs/__init__.py +0 -0
  45. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/docs/generator.py +0 -0
  46. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/docs/onboarding.py +0 -0
  47. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/hooks/__init__.py +0 -0
  48. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/hooks/installer.py +0 -0
  49. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/llm/__init__.py +0 -0
  50. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/llm/prompts.py +0 -0
  51. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/mcp/__init__.py +0 -0
  52. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/mcp/global_server.py +0 -0
  53. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/mcp/server.py +0 -0
  54. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/__init__.py +0 -0
  55. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/hooks.py +0 -0
  56. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/learning_engine.py +0 -0
  57. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/memory_graph.py +0 -0
  58. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/memory_manager.py +0 -0
  59. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/models.py +0 -0
  60. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/pattern_miner.py +0 -0
  61. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/preference_store.py +0 -0
  62. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/profile.py +0 -0
  63. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/schemas.py +0 -0
  64. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/style_analyzer.py +0 -0
  65. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/__init__.py +0 -0
  66. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/code_element.py +0 -0
  67. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/context.py +0 -0
  68. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/smell.py +0 -0
  69. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/suggestion.py +0 -0
  70. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/__init__.py +0 -0
  71. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/base.py +0 -0
  72. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/python_parser.py +0 -0
  73. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/registry.py +0 -0
  74. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/treesitter_parser.py +0 -0
  75. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/__init__.py +0 -0
  76. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/analyzer.py +0 -0
  77. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/diff.py +0 -0
  78. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/formatters.py +0 -0
  79. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/hybrid_analyzer.py +0 -0
  80. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/models.py +0 -0
  81. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/prompts.py +0 -0
  82. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/smells.py +0 -0
  83. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/__init__.py +0 -0
  84. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/formatters.py +0 -0
  85. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/models.py +0 -0
  86. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/__init__.py +0 -0
  87. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/config.py +0 -0
  88. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/crypto.py +0 -0
  89. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/deserialization.py +0 -0
  90. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/injection.py +0 -0
  91. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/secrets.py +0 -0
  92. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/xss.py +0 -0
  93. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/scanner.py +0 -0
  94. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/__init__.py +0 -0
  95. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/base.py +0 -0
  96. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/database.py +0 -0
  97. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/kuzu_store.py +0 -0
  98. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/vector_base.py +0 -0
  99. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/__init__.py +0 -0
  100. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/features.py +0 -0
  101. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/health.py +0 -0
  102. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/language_detector.py +0 -0
  103. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/logging.py +0 -0
  104. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/mixins.py +0 -0
  105. {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/retry.py +0 -0
  106. {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/SOURCES.txt +0 -0
  107. {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/dependency_links.txt +0 -0
  108. {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/entry_points.txt +0 -0
  109. {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/requires.txt +0 -0
  110. {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/top_level.txt +0 -0
  111. {pycodesage-0.3.0 → pycodesage-0.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycodesage
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Local-first CLI code intelligence tool with LangChain-powered RAG
5
5
  Author: Keshav Ashiya
6
6
  License: MIT
@@ -1,4 +1,4 @@
1
1
  """CodeSage: Local-first CLI code intelligence tool."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.1"
4
4
  __author__ = "Keshav Ashiya"
@@ -49,8 +49,8 @@ class _LLMStreamRequest:
49
49
  """
50
50
 
51
51
  messages: List[Dict[str, str]]
52
- prefix: str = "" # Text to show before LLM response
53
- suffix: str = "" # Text to append after LLM response
52
+ prefix: str = "" # Text to show before LLM response
53
+ suffix: str = "" # Text to append after LLM response
54
54
 
55
55
 
56
56
  class ChatEngine:
@@ -490,9 +490,7 @@ class ChatEngine:
490
490
  # Streaming Methods
491
491
  # =========================================================================
492
492
 
493
- def stream_message(
494
- self, message: str
495
- ) -> Generator[Tuple[str, str], None, None]:
493
+ def stream_message(self, message: str) -> Generator[Tuple[str, str], None, None]:
496
494
  """Stream a chat response with progressive context + LLM tokens.
497
495
 
498
496
  Yields:
@@ -564,8 +562,12 @@ class ChatEngine:
564
562
 
565
563
  # Commands that call LLM and should stream their response
566
564
  _LLM_COMMANDS = {
567
- ChatCommand.DEEP, ChatCommand.PLAN, ChatCommand.REVIEW,
568
- ChatCommand.SECURITY, ChatCommand.IMPACT, ChatCommand.PATTERNS,
565
+ ChatCommand.DEEP,
566
+ ChatCommand.PLAN,
567
+ ChatCommand.REVIEW,
568
+ ChatCommand.SECURITY,
569
+ ChatCommand.IMPACT,
570
+ ChatCommand.PATTERNS,
569
571
  ChatCommand.SIMILAR,
570
572
  }
571
573
 
@@ -785,34 +787,48 @@ class ChatEngine:
785
787
  return "Unknown setting. Use: `/context code on|off` or `/context limit <n>`"
786
788
 
787
789
  def _handle_search(self, query: Optional[str]) -> str:
788
- """Handle /search command with query expansion."""
790
+ """Handle /search command with query expansion and multiple strategies."""
789
791
  if not query:
790
792
  return "Please provide a search query: /search <query>"
791
793
 
792
794
  try:
793
- # Expand query for better search results
794
- expanded = self._query_expander.expand(query, self._conversation_context)
795
+ import re
795
796
 
796
- # Log expansion
797
- logger.info(
798
- f"Search query expanded: '{query}' -> {len(expanded.expanded_terms)} terms"
799
- )
797
+ # Try multiple query strategies like /deep does
798
+ search_queries = [query]
799
+
800
+ # 1. Try lowercase
801
+ if query.lower() != query:
802
+ search_queries.append(query.lower())
800
803
 
801
- # Perform multiple searches with expanded queries
804
+ # 2. Try space-separated (for camelCase/PascalCase)
805
+ spaced = re.sub(r"([a-z])([A-Z])", r"\1 \2", query)
806
+ if spaced != query:
807
+ search_queries.append(spaced.lower())
808
+
809
+ # 3. Try individual significant terms
810
+ terms = query.replace("_", " ").replace("-", " ").split()
811
+ if len(terms) > 1:
812
+ for term in terms:
813
+ if len(term) > 3:
814
+ search_queries.append(term.lower())
815
+
816
+ # Perform multiple searches
802
817
  all_results = []
803
818
  seen_files = set()
804
819
 
805
- # Search with original query
806
- results = self.context_builder.search_code(query, limit=5)
807
- for r in results:
808
- key = f"{r.get('file')}:{r.get('line')}"
809
- if key not in seen_files:
810
- seen_files.add(key)
811
- all_results.append(r)
820
+ # Search with all query variations
821
+ for sq in search_queries:
822
+ results = self.context_builder.search_code(sq, limit=5)
823
+ for r in results:
824
+ key = f"{r.get('file')}:{r.get('line')}"
825
+ if key not in seen_files:
826
+ seen_files.add(key)
827
+ all_results.append(r)
812
828
 
813
- # Search with expanded terms if confidence is high
829
+ # Also try query expansion for additional results
830
+ expanded = self._query_expander.expand(query, self._conversation_context)
814
831
  if expanded.confidence_score >= 0.5 and expanded.expanded_terms:
815
- # Build expanded query from top terms
816
832
  expanded_query = " ".join(expanded.expanded_terms[:5])
817
833
  expanded_results = self.context_builder.search_code(
818
834
  expanded_query, limit=3
@@ -823,6 +839,8 @@ class ChatEngine:
823
839
  seen_files.add(key)
824
840
  all_results.append(r)
825
841
 
842
+ # Re-sort by similarity (highest first)
843
+ all_results.sort(key=lambda x: x.get("similarity", 0), reverse=True)
826
844
  results = all_results[:8] # Limit total results
827
845
  except Exception as e:
828
846
  return f"Search failed: {e}"
@@ -861,24 +879,81 @@ class ChatEngine:
861
879
  return "Please provide a query: /deep <query>"
862
880
 
863
881
  try:
864
- # Expand query for better context retrieval
865
- expanded = self._query_expander.expand(query, self._conversation_context)
866
-
867
- # Check confidence and ambiguity
868
- if expanded.is_ambiguous and expanded.confidence_score < 0.6:
869
- return self._generate_clarification_prompt(expanded)
882
+ # Try multiple query strategies to find relevant code
883
+ search_queries = [query]
884
+
885
+ # Track best context (most relevant results)
886
+ best_context = None
887
+ best_context_has_exact_match = False
888
+
889
+ # Add variations that might help semantic search
890
+ # 1. Try lowercase
891
+ if query.lower() != query:
892
+ search_queries.append(query.lower())
893
+
894
+ # 2. Try space-separated (for camelCase/PascalCase)
895
+ import re
896
+
897
+ spaced = re.sub(r"([a-z])([A-Z])", r"\1 \2", query)
898
+ if spaced != query:
899
+ search_queries.append(spaced.lower())
900
+
901
+ # 3. Try key terms from query
902
+ terms = query.replace("_", " ").replace("-", " ").split()
903
+ if len(terms) > 1:
904
+ # Add individual significant terms
905
+ for term in terms:
906
+ if len(term) > 3: # Skip short terms
907
+ search_queries.append(term.lower())
908
+
909
+ # 4. Try expanded terms from query expander (but only if useful)
910
+ try:
911
+ expanded = self._query_expander.expand(
912
+ query, self._conversation_context
913
+ )
914
+ if expanded and expanded.expanded_terms:
915
+ for term in expanded.expanded_terms[:3]:
916
+ if term.lower() not in [q.lower() for q in search_queries]:
917
+ search_queries.append(term.lower())
918
+ except Exception:
919
+ pass # Don't fail if expansion fails
920
+
921
+ # Try each query and keep the best result
922
+ for sq in search_queries:
923
+ context = self.context_provider.get_implementation_context(
924
+ sq,
925
+ include_cross_project=self.config.features.cross_project_recommendations,
926
+ )
870
927
 
871
- # Log expansion for debugging
872
- logger.info(
873
- f"Query expanded: '{query}' -> intent={expanded.intent.name}, "
874
- f"terms={len(expanded.expanded_terms)}, confidence={expanded.confidence_score:.2f}"
875
- )
928
+ # Check if we found an exact name match
929
+ has_exact_match = any(
930
+ ref.name and query.lower() in ref.name.lower()
931
+ for ref in context.relevant_code
932
+ )
876
933
 
877
- # Use EXPANDED query for context provider
878
- context = self.context_provider.get_implementation_context(
879
- expanded.enhanced_query,
880
- include_cross_project=self.config.features.cross_project_recommendations,
881
- )
934
+ # Prefer context with exact match, otherwise prefer more results
935
+ if best_context is None:
936
+ best_context = context
937
+ best_context_has_exact_match = has_exact_match
938
+ elif has_exact_match and not best_context_has_exact_match:
939
+ # This context has exact match, use it
940
+ best_context = context
941
+ best_context_has_exact_match = True
942
+ elif not has_exact_match and not best_context_has_exact_match:
943
+ # Neither has exact match, prefer more results
944
+ if len(context.relevant_code) > len(best_context.relevant_code):
945
+ best_context = context
946
+
947
+ # Use best context found
948
+ context = best_context
949
+
950
+ # If nothing found, try a broader search
951
+ if context is None or not context.relevant_code:
952
+ # Try keyword-based search as fallback
953
+ context = self.context_provider.get_implementation_context(
954
+ f"token bucket {query}",
955
+ include_cross_project=False,
956
+ )
882
957
 
883
958
  # Store for follow-up
884
959
  self._last_deep_result = {
@@ -890,22 +965,26 @@ class ChatEngine:
890
965
  # Build rich context for LLM
891
966
  context_sections = []
892
967
 
893
- # Add relevant code
968
+ # Add relevant code with more detail
894
969
  if context.relevant_code:
895
970
  context_sections.append("## Relevant Code Found\n")
896
- for ref in context.relevant_code[:5]:
971
+ for ref in context.relevant_code[:10]: # Increased from 5 to 10
897
972
  snippet = (
898
973
  getattr(ref, "snippet", "")
899
974
  or getattr(ref, "content", "")
900
975
  or getattr(ref, "code", "")
901
976
  )
977
+ # Include full path for clarity
978
+ file_path = ref.file
902
979
  context_sections.append(
903
- f"**{ref.name or ref.element_type}** in `{ref.file}:{ref.line}` "
904
- f"(relevance: {ref.similarity:.0%})\n"
980
+ f"### {ref.name or ref.element_type}\n"
981
+ f"**Location:** `{file_path}:{ref.line}`\n"
982
+ f"**Relevance:** {ref.similarity:.0%}\n"
905
983
  )
906
984
  if snippet:
985
+ # Include more context (500 chars instead of 300)
907
986
  context_sections.append(
908
- f"```{self.config.language}\n{snippet[:300]}\n```\n"
987
+ f"```{self.config.language}\n{snippet[:500]}\n```\n"
909
988
  )
910
989
 
911
990
  # Add patterns
@@ -916,7 +995,7 @@ class ChatEngine:
916
995
  desc = p.get("description", "")
917
996
  conf = p.get("confidence_score", p.get("confidence", 0))
918
997
  context_sections.append(
919
- f"- **{name}** (confidence: {conf:.0%}): {desc[:100]}\n"
998
+ f"- **{name}** (confidence: {conf:.0%}): {desc[:150]}\n"
920
999
  )
921
1000
 
922
1001
  # Add implementation plan if available
@@ -944,27 +1023,40 @@ class ChatEngine:
944
1023
 
945
1024
  # Build prompt for LLM to generate human-readable response
946
1025
  context_str = "\n".join(context_sections)
947
- prompt = f"""You are CodeSage, a senior software architect analyzing a codebase inquiry.
948
1026
 
949
- The user asked: "{query}"
1027
+ # Check if we have meaningful context
1028
+ has_context = bool(context.relevant_code or context.patterns)
950
1029
 
951
- Here is the relevant context gathered from the codebase:
1030
+ # Get list of files in context for prompt
1031
+ files_in_context = (
1032
+ ", ".join(set(ref.file.split("/")[-1] for ref in context.relevant_code))
1033
+ if context.relevant_code
1034
+ else "the retrieved files"
1035
+ )
952
1036
 
953
- {context_str}
1037
+ prompt = f"""You are a senior code architect analyzing the codebase. Provide a detailed, technical analysis based ONLY on the code provided below.
1038
+
1039
+ The user asked: "{query}"
1040
+
1041
+ CODE RETRIEVED FROM CODEBASE:
1042
+ ---
1043
+ {context_str if has_context else "NO CODE FOUND. Search the codebase for relevant code."}
1044
+ ---
954
1045
 
955
- Please provide a thoughtful, conversational analysis that:
956
- 1. Directly addresses the user's question
957
- 2. Synthesizes the information naturally (don't just list bullet points)
958
- 3. Highlights key insights and patterns discovered
959
- 4. Suggests next steps or areas to explore
960
- 5. References specific files/functions naturally in your explanation
1046
+ Provide a comprehensive analysis that:
1047
+ 1. Explains what each code element does with specific details
1048
+ 2. Shows the full method signatures and their purposes
1049
+ 3. Explains relationships between components (imports, calls, inherits from)
1050
+ 4. Includes relevant file:line references for every claim
1051
+ 5. If asking about implementation, show the actual code that implements it
1052
+ 6. If the queried item doesn't exist in the code, state clearly: "No matching code found in the codebase"
961
1053
 
962
- Write in a friendly, professional tone as if explaining to a colleague. Avoid rigid section headers unless they truly help organize complex information."""
1054
+ Be specific and technical - this is for a developer who knows programming but needs to understand this specific codebase."""
963
1055
 
964
1056
  messages = [
965
1057
  {
966
1058
  "role": "system",
967
- "content": "You are a helpful senior developer analyzing code.",
1059
+ "content": "You are a precise code analysis assistant. NEVER invent code, methods, or files. Only discuss what's explicitly in the provided context. If something is not in the context, say so.",
968
1060
  },
969
1061
  {"role": "user", "content": prompt},
970
1062
  ]
@@ -1049,7 +1141,16 @@ Write in a friendly, professional tone as if explaining to a colleague. Avoid ri
1049
1141
 
1050
1142
  # Build prompt for LLM
1051
1143
  context_str = "".join(context_sections)
1052
- prompt = f"""You are CodeSage, an experienced pair programmer helping implement a feature.
1144
+
1145
+ # Check if we have actual relevant code (>60% similarity)
1146
+ has_relevant_code = (
1147
+ any(ref.similarity >= 0.6 for ref in context.relevant_code)
1148
+ if context.relevant_code
1149
+ else False
1150
+ )
1151
+
1152
+ if has_relevant_code:
1153
+ prompt = f"""You are CodeSage, an experienced pair programmer helping implement a feature.
1053
1154
 
1054
1155
  The user wants to: {task}
1055
1156
 
@@ -1066,6 +1167,20 @@ Please provide a clear, actionable implementation plan that:
1066
1167
  6. Maintains consistency with existing code patterns
1067
1168
 
1068
1169
  Write in a practical, developer-friendly tone. Be specific about what needs to change and where."""
1170
+ else:
1171
+ prompt = f"""You are CodeSage, an experienced pair programmer helping implement a feature.
1172
+
1173
+ The user wants to: {task}
1174
+
1175
+ IMPORTANT: No sufficiently relevant code was found in the codebase for this task (highest similarity was {max([ref.similarity for ref in context.relevant_code], default=0):.0%} which is below the 60% threshold for relevance).
1176
+ - Do NOT provide generic implementation guidance
1177
+ - Do NOT suggest specific files or functions that don't have high relevance
1178
+ - Do NOT make up code examples
1179
+
1180
+ Instead, respond with:
1181
+ 1. A brief acknowledgment that the codebase doesn't have relevant code for this task
1182
+ 2. Suggestions for what types of files might need to be created (e.g., 'you may need to create an auth module')
1183
+ 3. Keep it concise - maximum 3-4 sentences"""
1069
1184
 
1070
1185
  messages = [
1071
1186
  {
@@ -1112,7 +1227,9 @@ Write in a practical, developer-friendly tone. Be specific about what needs to c
1112
1227
  changes = analyzer.get_all_changes()
1113
1228
 
1114
1229
  if not changes:
1115
- return "No uncommitted changes found. Make some changes and try again."
1230
+ return (
1231
+ "No uncommitted changes found. Make some changes and try again."
1232
+ )
1116
1233
 
1117
1234
  # Run review
1118
1235
  result = analyzer.review_changes(
@@ -1127,9 +1244,15 @@ Write in a practical, developer-friendly tone. Be specific about what needs to c
1127
1244
 
1128
1245
  if result.issues:
1129
1246
  # Group by severity
1130
- critical = [i for i in result.issues if i.severity.name == "CRITICAL"]
1247
+ critical = [
1248
+ i for i in result.issues if i.severity.name == "CRITICAL"
1249
+ ]
1131
1250
  warning = [i for i in result.issues if i.severity.name == "WARNING"]
1132
- info = [i for i in result.issues if i.severity.name not in ("CRITICAL", "WARNING")]
1251
+ info = [
1252
+ i
1253
+ for i in result.issues
1254
+ if i.severity.name not in ("CRITICAL", "WARNING")
1255
+ ]
1133
1256
 
1134
1257
  if critical:
1135
1258
  review_context.append(f"\n### Critical ({len(critical)})\n")
@@ -1138,7 +1261,9 @@ Write in a practical, developer-friendly tone. Be specific about what needs to c
1138
1261
  if issue.file:
1139
1262
  review_context.append(f" `{issue.file}:{issue.line}`")
1140
1263
  if issue.suggestion:
1141
- review_context.append(f" Fix: {issue.suggestion[:100]}\n")
1264
+ review_context.append(
1265
+ f" Fix: {issue.suggestion[:100]}\n"
1266
+ )
1142
1267
 
1143
1268
  if warning:
1144
1269
  review_context.append(f"\n### Warnings ({len(warning)})\n")
@@ -1161,28 +1286,35 @@ Write in a practical, developer-friendly tone. Be specific about what needs to c
1161
1286
 
1162
1287
  # Build prompt for LLM to generate human-readable review
1163
1288
  context_str = "".join(review_context)
1164
- prompt = f"""You are CodeSage, a senior code reviewer providing feedback on code changes.
1165
1289
 
1166
- Review target: {review_target}
1290
+ # Check if we have actual review content
1291
+ has_content = (
1292
+ "No issues found" in context_str
1293
+ or "Summary:" in context_str
1294
+ or "Critical" in context_str
1295
+ or "Warning" in context_str
1296
+ or "Suggestion" in context_str
1297
+ )
1167
1298
 
1168
- Here are the findings from automated analysis:
1299
+ prompt = f"""You are a code reviewer. Summarize the review findings below.
1169
1300
 
1170
- {context_str}
1301
+ Review target: {review_target}
1171
1302
 
1172
- Please provide a constructive code review that:
1173
- 1. Starts with an overall assessment (approval, needs changes, or major concerns)
1174
- 2. Groups related issues logically (not by severity)
1175
- 3. Explains WHY each issue matters, not just what
1176
- 4. Provides specific, actionable suggestions for fixes
1177
- 5. Highlights any positive aspects or good patterns observed
1178
- 6. Prioritizes critical security and correctness issues
1303
+ FINDINGS:
1304
+ {context_str if has_content else "No review data available."}
1179
1305
 
1180
- Write in a helpful, professional tone. Be specific about file locations and line numbers."""
1306
+ RULES:
1307
+ 1. Only discuss what's explicitly in the findings above
1308
+ 2. Do NOT mention files that aren't shown in the findings
1309
+ 3. Do NOT create example code
1310
+ 4. If no actual issues found, state that clearly
1311
+
1312
+ Keep the response concise."""
1181
1313
 
1182
1314
  messages = [
1183
1315
  {
1184
1316
  "role": "system",
1185
- "content": "You are a helpful senior developer performing code reviews.",
1317
+ "content": "You are a code reviewer. Summarize findings accurately. Do not invent files or issues.",
1186
1318
  },
1187
1319
  {"role": "user", "content": prompt},
1188
1320
  ]
@@ -1232,15 +1364,21 @@ Write in a helpful, professional tone. Be specific about file locations and line
1232
1364
  f"\n### Critical Issues ({len(critical)})\n"
1233
1365
  )
1234
1366
  for f in critical[:5]:
1235
- security_context.append(f"- **{f.rule.id}**: {f.rule.message[:100]}")
1236
- security_context.append(f" Location: `{f.file}:{f.line_number}`\n")
1367
+ security_context.append(
1368
+ f"- **{f.rule.id}**: {f.rule.message[:100]}"
1369
+ )
1370
+ security_context.append(
1371
+ f" Location: `{f.file}:{f.line_number}`\n"
1372
+ )
1237
1373
 
1238
1374
  if high:
1239
1375
  security_context.append(
1240
1376
  f"\n### High Priority Issues ({len(high)})\n"
1241
1377
  )
1242
1378
  for f in high[:5]:
1243
- security_context.append(f"- **{f.rule.id}**: {f.rule.message[:80]}")
1379
+ security_context.append(
1380
+ f"- **{f.rule.id}**: {f.rule.message[:80]}"
1381
+ )
1244
1382
  security_context.append(f" `{f.file}:{f.line_number}`\n")
1245
1383
 
1246
1384
  if medium:
@@ -1248,7 +1386,9 @@ Write in a helpful, professional tone. Be specific about file locations and line
1248
1386
  f"\n### Medium Priority Issues ({len(medium)})\n"
1249
1387
  )
1250
1388
  for f in medium[:3]:
1251
- security_context.append(f"- {f.rule.id}: {f.rule.message[:60]}\n")
1389
+ security_context.append(
1390
+ f"- {f.rule.id}: {f.rule.message[:60]}\n"
1391
+ )
1252
1392
 
1253
1393
  if low:
1254
1394
  security_context.append(
@@ -1257,13 +1397,21 @@ Write in a helpful, professional tone. Be specific about file locations and line
1257
1397
 
1258
1398
  security_context.append(f"\n**Total findings: {len(finding_list)}**\n")
1259
1399
  else:
1400
+ scan_target = target if target else "Project-wide"
1260
1401
  security_context.append(
1261
- f"## Security Analysis: {target or 'Project'}\n\nNo security issues found!\n"
1402
+ f"## Security Analysis: {scan_target}\n\n"
1403
+ f"Security scan completed - no issues found.\n"
1404
+ f"Scanned: {scan_target}\n"
1262
1405
  )
1263
1406
 
1264
1407
  # Build prompt for LLM
1265
1408
  context_str = "".join(security_context)
1266
- prompt = f"""You are CodeSage, a security engineer reviewing code for vulnerabilities.
1409
+
1410
+ # Check if there are actual findings
1411
+ has_findings = finding_list and len(finding_list) > 0
1412
+
1413
+ if has_findings:
1414
+ prompt = f"""You are CodeSage, a security engineer reviewing code for vulnerabilities.
1267
1415
 
1268
1416
  Target: {target or "Project-wide scan"}
1269
1417
 
@@ -1280,6 +1428,16 @@ Please provide a security assessment that:
1280
1428
  6. Suggests preventive measures for future development
1281
1429
 
1282
1430
  Write in a clear, actionable tone. Focus on practical remediation."""
1431
+ else:
1432
+ prompt = f"""You are CodeSage, a security engineer reviewing code for vulnerabilities.
1433
+
1434
+ Target: {target or "Project-wide scan"}
1435
+
1436
+ Security scan results:
1437
+
1438
+ {context_str}
1439
+
1440
+ IMPORTANT: No security issues were found in this scan. Your response should ONLY confirm this finding - do NOT provide generic security advice, best practices, or recommendations. Keep it very brief (2-3 sentences max)."""
1283
1441
 
1284
1442
  messages = [
1285
1443
  {
@@ -1303,8 +1461,59 @@ Write in a clear, actionable tone. Focus on practical remediation."""
1303
1461
  return "Please provide an element: /impact <function or class name>"
1304
1462
 
1305
1463
  try:
1306
- # Search for the element
1307
- results = self.context_builder.search_code(element, limit=1)
1464
+ import re
1465
+ import sqlite3
1466
+
1467
+ # First, try to find exact name match in database
1468
+ db_path = str(self.config.storage.db_path)
1469
+ conn = sqlite3.connect(db_path)
1470
+ cursor = conn.execute(
1471
+ "SELECT name, file, type, line_start, code FROM code_elements WHERE name = ?",
1472
+ (element,),
1473
+ )
1474
+ exact_match = cursor.fetchone()
1475
+ conn.close()
1476
+
1477
+ if exact_match:
1478
+ # Use exact match
1479
+ results = [
1480
+ {
1481
+ "name": exact_match[0],
1482
+ "file": exact_match[1],
1483
+ "type": exact_match[2],
1484
+ "line": exact_match[3],
1485
+ "code": exact_match[4] or "",
1486
+ "similarity": 1.0,
1487
+ }
1488
+ ]
1489
+ else:
1490
+ # Try multiple query strategies to find the element
1491
+ search_queries = [element]
1492
+
1493
+ # 1. Try lowercase
1494
+ if element.lower() != element:
1495
+ search_queries.append(element.lower())
1496
+
1497
+ # 2. Try space-separated
1498
+ spaced = re.sub(r"([a-z])([A-Z])", r"\1 \2", element)
1499
+ if spaced != element:
1500
+ search_queries.append(spaced.lower())
1501
+
1502
+ # Search with multiple queries
1503
+ all_results = []
1504
+ seen = set()
1505
+ for sq in search_queries:
1506
+ results = self.context_builder.search_code(sq, limit=5)
1507
+ for r in results:
1508
+ key = f"{r.get('file')}:{r.get('line')}"
1509
+ if key not in seen:
1510
+ seen.add(key)
1511
+ all_results.append(r)
1512
+
1513
+ # Re-sort by similarity (highest first)
1514
+ all_results.sort(key=lambda x: x.get("similarity", 0), reverse=True)
1515
+ results = all_results[:1] if all_results else []
1516
+
1308
1517
  if not results:
1309
1518
  return f"Element not found: {element}"
1310
1519
 
@@ -1482,7 +1691,34 @@ Write in an informative tone that helps developers understand and adopt these pa
1482
1691
  return "Please provide an element: /similar <function or class name>"
1483
1692
 
1484
1693
  try:
1485
- results = self.context_builder.search_code(element, limit=10)
1694
+ import re
1695
+
1696
+ # Try multiple query strategies
1697
+ search_queries = [element]
1698
+
1699
+ # 1. Try lowercase
1700
+ if element.lower() != element:
1701
+ search_queries.append(element.lower())
1702
+
1703
+ # 2. Try space-separated
1704
+ spaced = re.sub(r"([a-z])([A-Z])", r"\1 \2", element)
1705
+ if spaced != element:
1706
+ search_queries.append(spaced.lower())
1707
+
1708
+ # Search with multiple queries and collect all results
1709
+ all_results = []
1710
+ seen = set()
1711
+ for sq in search_queries:
1712
+ results = self.context_builder.search_code(sq, limit=10)
1713
+ for r in results:
1714
+ key = f"{r.get('file')}:{r.get('line')}"
1715
+ if key not in seen:
1716
+ seen.add(key)
1717
+ all_results.append(r)
1718
+
1719
+ # Re-sort by similarity
1720
+ all_results.sort(key=lambda x: x.get("similarity", 0), reverse=True)
1721
+ results = all_results
1486
1722
 
1487
1723
  if not results:
1488
1724
  return f"No matches found for: {element}"
@@ -1490,6 +1726,9 @@ Write in an informative tone that helps developers understand and adopt these pa
1490
1726
  # Skip first if it's exact match
1491
1727
  similar = results[1:] if results[0].get("similarity", 0) > 0.95 else results
1492
1728
 
1729
+ # Filter to only show reasonably similar results (>50%)
1730
+ similar = [r for r in similar if r.get("similarity", 0) > 0.5]
1731
+
1493
1732
  if not similar:
1494
1733
  return f"No similar code found to: {element}"
1495
1734
 
@@ -1592,7 +1831,9 @@ Write in a practical, developer-friendly tone focused on code quality and mainta
1592
1831
  filename = f"chat_export_{timestamp}.md"
1593
1832
 
1594
1833
  # Check if there's any conversation to export
1595
- user_msgs = [m for m in self._session.messages if m.role in ("user", "assistant")]
1834
+ user_msgs = [
1835
+ m for m in self._session.messages if m.role in ("user", "assistant")
1836
+ ]
1596
1837
  if not user_msgs:
1597
1838
  return "No conversation to export yet. Start chatting first!"
1598
1839
 
@@ -1619,7 +1860,9 @@ Write in a practical, developer-friendly tone focused on code quality and mainta
1619
1860
  lines.append("---\n")
1620
1861
 
1621
1862
  output_path.write_text("\n".join(lines))
1622
- return f"Conversation exported ({len(user_msgs)} messages) to: `{output_path}`"
1863
+ return (
1864
+ f"Conversation exported ({len(user_msgs)} messages) to: `{output_path}`"
1865
+ )
1623
1866
 
1624
1867
  except Exception as e:
1625
1868
  return f"Export failed: {e}"