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.
- {pycodesage-0.3.0 → pycodesage-0.3.1}/PKG-INFO +1 -1
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/__init__.py +1 -1
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/engine.py +330 -87
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/query_expansion.py +13 -6
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/commands/init.py +6 -4
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/llm/embeddings.py +50 -9
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/llm/provider.py +52 -6
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/pattern_store.py +23 -8
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/lance_store.py +21 -6
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/manager.py +36 -9
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/config.py +160 -44
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/rate_limiter.py +31 -10
- {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/PKG-INFO +1 -1
- {pycodesage-0.3.0 → pycodesage-0.3.1}/pyproject.toml +1 -1
- {pycodesage-0.3.0 → pycodesage-0.3.1}/LICENSE +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/README.md +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/__main__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/commands.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/context.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/models.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/chat/prompts.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/commands/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/commands/chat.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/commands/index.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/groups/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/groups/mcp.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/main.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/console.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/decorators.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/formatters.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/options.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/cli/utils/signals.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/confidence.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/context_provider.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/deep_analyzer.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/indexer.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/relationship_extractor.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/resource_manager.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/core/suggester.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/docs/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/docs/generator.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/docs/onboarding.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/hooks/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/hooks/installer.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/llm/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/llm/prompts.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/mcp/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/mcp/global_server.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/mcp/server.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/hooks.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/learning_engine.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/memory_graph.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/memory_manager.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/models.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/pattern_miner.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/preference_store.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/profile.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/schemas.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/memory/style_analyzer.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/code_element.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/context.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/smell.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/models/suggestion.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/base.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/python_parser.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/registry.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/parsers/treesitter_parser.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/analyzer.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/diff.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/formatters.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/hybrid_analyzer.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/models.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/prompts.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/review/smells.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/formatters.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/models.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/config.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/crypto.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/deserialization.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/injection.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/secrets.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/rules/xss.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/security/scanner.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/base.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/database.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/kuzu_store.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/storage/vector_base.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/__init__.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/features.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/health.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/language_detector.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/logging.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/mixins.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/codesage/utils/retry.py +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/SOURCES.txt +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/dependency_links.txt +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/entry_points.txt +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/requires.txt +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/pycodesage.egg-info/top_level.txt +0 -0
- {pycodesage-0.3.0 → pycodesage-0.3.1}/setup.cfg +0 -0
|
@@ -49,8 +49,8 @@ class _LLMStreamRequest:
|
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
51
|
messages: List[Dict[str, str]]
|
|
52
|
-
prefix: str = ""
|
|
53
|
-
suffix: str = ""
|
|
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,
|
|
568
|
-
ChatCommand.
|
|
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
|
-
|
|
794
|
-
expanded = self._query_expander.expand(query, self._conversation_context)
|
|
795
|
+
import re
|
|
795
796
|
|
|
796
|
-
#
|
|
797
|
-
|
|
798
|
-
|
|
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
|
-
#
|
|
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
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
seen_files
|
|
811
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
#
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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[:
|
|
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"
|
|
904
|
-
f"
|
|
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[:
|
|
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[:
|
|
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
|
-
|
|
1027
|
+
# Check if we have meaningful context
|
|
1028
|
+
has_context = bool(context.relevant_code or context.patterns)
|
|
950
1029
|
|
|
951
|
-
|
|
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
|
-
|
|
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
|
-
|
|
956
|
-
1.
|
|
957
|
-
2.
|
|
958
|
-
3.
|
|
959
|
-
4.
|
|
960
|
-
5.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 = [
|
|
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 = [
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1299
|
+
prompt = f"""You are a code reviewer. Summarize the review findings below.
|
|
1169
1300
|
|
|
1170
|
-
{
|
|
1301
|
+
Review target: {review_target}
|
|
1171
1302
|
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
1236
|
-
|
|
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(
|
|
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(
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
1307
|
-
|
|
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
|
-
|
|
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 = [
|
|
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
|
|
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}"
|