codexa 0.4.0__py3-none-any.whl
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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- semantic_code_intelligence/workspace/__init__.py +427 -0
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
"""Tests for Phase 16 — Advanced AI Workflows.
|
|
2
|
+
|
|
3
|
+
Covers: conversation sessions, session store, investigation chains,
|
|
4
|
+
cross-repo refactoring, streaming LLM, CLI commands, router, version, docs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from unittest.mock import patch
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
from click.testing import CliRunner
|
|
16
|
+
|
|
17
|
+
from semantic_code_intelligence.llm.provider import LLMMessage, LLMResponse, MessageRole
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# =========================================================================
|
|
21
|
+
# Conversation session tests
|
|
22
|
+
# =========================================================================
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestConversationSession:
|
|
26
|
+
"""Tests for ConversationSession data model."""
|
|
27
|
+
|
|
28
|
+
def test_create_session(self):
|
|
29
|
+
from semantic_code_intelligence.llm.conversation import ConversationSession
|
|
30
|
+
|
|
31
|
+
session = ConversationSession()
|
|
32
|
+
assert len(session.session_id) == 12
|
|
33
|
+
assert session.messages == []
|
|
34
|
+
assert session.turn_count == 0
|
|
35
|
+
|
|
36
|
+
def test_add_messages(self):
|
|
37
|
+
from semantic_code_intelligence.llm.conversation import ConversationSession
|
|
38
|
+
|
|
39
|
+
session = ConversationSession()
|
|
40
|
+
session.add_system("You are a helper.")
|
|
41
|
+
session.add_user("Hello")
|
|
42
|
+
session.add_assistant("Hi!")
|
|
43
|
+
|
|
44
|
+
assert len(session.messages) == 3
|
|
45
|
+
assert session.messages[0].role == MessageRole.SYSTEM
|
|
46
|
+
assert session.messages[1].role == MessageRole.USER
|
|
47
|
+
assert session.messages[2].role == MessageRole.ASSISTANT
|
|
48
|
+
assert session.turn_count == 2 # user + assistant
|
|
49
|
+
|
|
50
|
+
def test_last_message(self):
|
|
51
|
+
from semantic_code_intelligence.llm.conversation import ConversationSession
|
|
52
|
+
|
|
53
|
+
session = ConversationSession()
|
|
54
|
+
assert session.last_message is None
|
|
55
|
+
session.add_user("Hello")
|
|
56
|
+
assert session.last_message.content == "Hello"
|
|
57
|
+
|
|
58
|
+
def test_get_messages_for_llm_no_limit(self):
|
|
59
|
+
from semantic_code_intelligence.llm.conversation import ConversationSession
|
|
60
|
+
|
|
61
|
+
session = ConversationSession()
|
|
62
|
+
session.add_system("sys")
|
|
63
|
+
session.add_user("Q1")
|
|
64
|
+
session.add_assistant("A1")
|
|
65
|
+
session.add_user("Q2")
|
|
66
|
+
|
|
67
|
+
msgs = session.get_messages_for_llm()
|
|
68
|
+
assert len(msgs) == 4
|
|
69
|
+
assert msgs[0].role == MessageRole.SYSTEM
|
|
70
|
+
|
|
71
|
+
def test_get_messages_for_llm_with_limit(self):
|
|
72
|
+
from semantic_code_intelligence.llm.conversation import ConversationSession
|
|
73
|
+
|
|
74
|
+
session = ConversationSession()
|
|
75
|
+
session.add_system("sys")
|
|
76
|
+
for i in range(10):
|
|
77
|
+
session.add_user(f"Q{i}")
|
|
78
|
+
session.add_assistant(f"A{i}")
|
|
79
|
+
|
|
80
|
+
msgs = session.get_messages_for_llm(max_turns=2)
|
|
81
|
+
# 1 system + 4 recent messages (2 turns × 2)
|
|
82
|
+
assert len(msgs) == 5
|
|
83
|
+
assert msgs[0].role == MessageRole.SYSTEM
|
|
84
|
+
|
|
85
|
+
def test_serialization_roundtrip(self):
|
|
86
|
+
from semantic_code_intelligence.llm.conversation import ConversationSession
|
|
87
|
+
|
|
88
|
+
session = ConversationSession(title="Test")
|
|
89
|
+
session.add_system("sys")
|
|
90
|
+
session.add_user("Hello")
|
|
91
|
+
session.add_assistant("Hi!")
|
|
92
|
+
|
|
93
|
+
data = session.to_dict()
|
|
94
|
+
restored = ConversationSession.from_dict(data)
|
|
95
|
+
|
|
96
|
+
assert restored.session_id == session.session_id
|
|
97
|
+
assert restored.title == "Test"
|
|
98
|
+
assert len(restored.messages) == 3
|
|
99
|
+
assert restored.messages[1].content == "Hello"
|
|
100
|
+
|
|
101
|
+
def test_title_setting(self):
|
|
102
|
+
from semantic_code_intelligence.llm.conversation import ConversationSession
|
|
103
|
+
|
|
104
|
+
session = ConversationSession(title="My Chat")
|
|
105
|
+
assert session.title == "My Chat"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# =========================================================================
|
|
109
|
+
# Session store tests
|
|
110
|
+
# =========================================================================
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestSessionStore:
|
|
114
|
+
"""Tests for persistent SessionStore."""
|
|
115
|
+
|
|
116
|
+
def test_create_store(self, tmp_path):
|
|
117
|
+
from semantic_code_intelligence.llm.conversation import SessionStore
|
|
118
|
+
|
|
119
|
+
store = SessionStore(tmp_path)
|
|
120
|
+
assert (tmp_path / ".codexa" / "sessions").is_dir()
|
|
121
|
+
|
|
122
|
+
def test_save_and_load(self, tmp_path):
|
|
123
|
+
from semantic_code_intelligence.llm.conversation import (
|
|
124
|
+
ConversationSession,
|
|
125
|
+
SessionStore,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
store = SessionStore(tmp_path)
|
|
129
|
+
session = ConversationSession(title="Test Chat")
|
|
130
|
+
session.add_user("Hello")
|
|
131
|
+
session.add_assistant("Hi!")
|
|
132
|
+
|
|
133
|
+
store.save(session)
|
|
134
|
+
loaded = store.load(session.session_id)
|
|
135
|
+
|
|
136
|
+
assert loaded is not None
|
|
137
|
+
assert loaded.session_id == session.session_id
|
|
138
|
+
assert loaded.title == "Test Chat"
|
|
139
|
+
assert len(loaded.messages) == 2
|
|
140
|
+
|
|
141
|
+
def test_load_nonexistent(self, tmp_path):
|
|
142
|
+
from semantic_code_intelligence.llm.conversation import SessionStore
|
|
143
|
+
|
|
144
|
+
store = SessionStore(tmp_path)
|
|
145
|
+
assert store.load("nonexistent") is None
|
|
146
|
+
|
|
147
|
+
def test_list_sessions(self, tmp_path):
|
|
148
|
+
from semantic_code_intelligence.llm.conversation import (
|
|
149
|
+
ConversationSession,
|
|
150
|
+
SessionStore,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
store = SessionStore(tmp_path)
|
|
154
|
+
s1 = ConversationSession(title="Chat 1")
|
|
155
|
+
s1.add_user("Hello")
|
|
156
|
+
s2 = ConversationSession(title="Chat 2")
|
|
157
|
+
s2.add_user("Hey")
|
|
158
|
+
|
|
159
|
+
store.save(s1)
|
|
160
|
+
store.save(s2)
|
|
161
|
+
|
|
162
|
+
sessions = store.list_sessions()
|
|
163
|
+
assert len(sessions) == 2
|
|
164
|
+
ids = {s["session_id"] for s in sessions}
|
|
165
|
+
assert s1.session_id in ids
|
|
166
|
+
assert s2.session_id in ids
|
|
167
|
+
|
|
168
|
+
def test_delete_session(self, tmp_path):
|
|
169
|
+
from semantic_code_intelligence.llm.conversation import (
|
|
170
|
+
ConversationSession,
|
|
171
|
+
SessionStore,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
store = SessionStore(tmp_path)
|
|
175
|
+
session = ConversationSession()
|
|
176
|
+
store.save(session)
|
|
177
|
+
assert store.delete(session.session_id) is True
|
|
178
|
+
assert store.load(session.session_id) is None
|
|
179
|
+
assert store.delete(session.session_id) is False
|
|
180
|
+
|
|
181
|
+
def test_get_or_create_new(self, tmp_path):
|
|
182
|
+
from semantic_code_intelligence.llm.conversation import SessionStore
|
|
183
|
+
|
|
184
|
+
store = SessionStore(tmp_path)
|
|
185
|
+
session = store.get_or_create()
|
|
186
|
+
assert session is not None
|
|
187
|
+
assert len(session.messages) == 0
|
|
188
|
+
|
|
189
|
+
def test_get_or_create_existing(self, tmp_path):
|
|
190
|
+
from semantic_code_intelligence.llm.conversation import (
|
|
191
|
+
ConversationSession,
|
|
192
|
+
SessionStore,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
store = SessionStore(tmp_path)
|
|
196
|
+
original = ConversationSession(title="Existing")
|
|
197
|
+
original.add_user("Test")
|
|
198
|
+
store.save(original)
|
|
199
|
+
|
|
200
|
+
resumed = store.get_or_create(original.session_id)
|
|
201
|
+
assert resumed.title == "Existing"
|
|
202
|
+
assert len(resumed.messages) == 1
|
|
203
|
+
|
|
204
|
+
def test_path_traversal_prevention(self, tmp_path):
|
|
205
|
+
from semantic_code_intelligence.llm.conversation import SessionStore
|
|
206
|
+
|
|
207
|
+
store = SessionStore(tmp_path)
|
|
208
|
+
# Attempt path traversal — should be sanitised
|
|
209
|
+
path = store._session_path("../../../etc/passwd")
|
|
210
|
+
assert ".." not in str(path.name)
|
|
211
|
+
assert "passwd" in str(path.name)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# =========================================================================
|
|
215
|
+
# Investigation chain tests
|
|
216
|
+
# =========================================================================
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class TestInvestigationChain:
|
|
220
|
+
"""Tests for autonomous investigation chains."""
|
|
221
|
+
|
|
222
|
+
def test_simple_conclude(self, tmp_path):
|
|
223
|
+
from semantic_code_intelligence.llm.investigation import InvestigationChain
|
|
224
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
225
|
+
|
|
226
|
+
provider = MockProvider()
|
|
227
|
+
provider.enqueue_response(json.dumps({
|
|
228
|
+
"thought": "I can answer directly.",
|
|
229
|
+
"action": "conclude",
|
|
230
|
+
"action_input": "The answer is 42.",
|
|
231
|
+
}))
|
|
232
|
+
|
|
233
|
+
chain = InvestigationChain(provider, tmp_path, max_steps=3)
|
|
234
|
+
result = chain.investigate("What is the answer?")
|
|
235
|
+
|
|
236
|
+
assert result.conclusion == "The answer is 42."
|
|
237
|
+
assert result.total_steps == 1
|
|
238
|
+
assert result.chain_id != ""
|
|
239
|
+
|
|
240
|
+
def test_search_then_conclude(self, tmp_path):
|
|
241
|
+
from semantic_code_intelligence.llm.investigation import InvestigationChain
|
|
242
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
243
|
+
|
|
244
|
+
provider = MockProvider()
|
|
245
|
+
# Step 1: search
|
|
246
|
+
provider.enqueue_response(json.dumps({
|
|
247
|
+
"thought": "Need to search for context.",
|
|
248
|
+
"action": "search",
|
|
249
|
+
"action_input": "authentication logic",
|
|
250
|
+
}))
|
|
251
|
+
# Step 2: conclude
|
|
252
|
+
provider.enqueue_response(json.dumps({
|
|
253
|
+
"thought": "Found it.",
|
|
254
|
+
"action": "conclude",
|
|
255
|
+
"action_input": "Auth logic is in auth.py",
|
|
256
|
+
}))
|
|
257
|
+
|
|
258
|
+
chain = InvestigationChain(provider, tmp_path, max_steps=5)
|
|
259
|
+
result = chain.investigate("Where is authentication?")
|
|
260
|
+
|
|
261
|
+
assert result.total_steps == 2
|
|
262
|
+
assert "auth.py" in result.conclusion
|
|
263
|
+
|
|
264
|
+
def test_max_steps_forces_conclusion(self, tmp_path):
|
|
265
|
+
from semantic_code_intelligence.llm.investigation import InvestigationChain
|
|
266
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
267
|
+
|
|
268
|
+
provider = MockProvider()
|
|
269
|
+
# Always want to search — never conclude
|
|
270
|
+
for _ in range(5):
|
|
271
|
+
provider.enqueue_response(json.dumps({
|
|
272
|
+
"thought": "Keep searching.",
|
|
273
|
+
"action": "search",
|
|
274
|
+
"action_input": "something",
|
|
275
|
+
}))
|
|
276
|
+
# Forced conclusion response
|
|
277
|
+
provider.enqueue_response("Forced final answer.")
|
|
278
|
+
|
|
279
|
+
chain = InvestigationChain(provider, tmp_path, max_steps=3)
|
|
280
|
+
result = chain.investigate("Tell me everything")
|
|
281
|
+
|
|
282
|
+
# Should have 3 search steps, then forced conclusion
|
|
283
|
+
assert result.total_steps == 3
|
|
284
|
+
|
|
285
|
+
def test_result_to_dict(self, tmp_path):
|
|
286
|
+
from semantic_code_intelligence.llm.investigation import InvestigationResult
|
|
287
|
+
|
|
288
|
+
result = InvestigationResult(
|
|
289
|
+
question="Why?",
|
|
290
|
+
conclusion="Because.",
|
|
291
|
+
chain_id="abc",
|
|
292
|
+
total_steps=2,
|
|
293
|
+
steps=[{"step": 1, "action": "search"}],
|
|
294
|
+
)
|
|
295
|
+
d = result.to_dict()
|
|
296
|
+
assert d["question"] == "Why?"
|
|
297
|
+
assert d["total_steps"] == 2
|
|
298
|
+
|
|
299
|
+
def test_parse_fallback(self, tmp_path):
|
|
300
|
+
from semantic_code_intelligence.llm.investigation import InvestigationChain
|
|
301
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
302
|
+
|
|
303
|
+
provider = MockProvider()
|
|
304
|
+
# Non-JSON response — should fallback to conclude
|
|
305
|
+
provider.enqueue_response("Just a plain text answer.")
|
|
306
|
+
|
|
307
|
+
chain = InvestigationChain(provider, tmp_path, max_steps=3)
|
|
308
|
+
result = chain.investigate("Question?")
|
|
309
|
+
|
|
310
|
+
assert result.conclusion == "Just a plain text answer."
|
|
311
|
+
assert result.total_steps == 1
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# =========================================================================
|
|
315
|
+
# Cross-repo refactoring tests
|
|
316
|
+
# =========================================================================
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class TestCrossRefactor:
|
|
320
|
+
"""Tests for cross-repo refactoring analysis."""
|
|
321
|
+
|
|
322
|
+
def test_empty_workspace(self, tmp_path):
|
|
323
|
+
from semantic_code_intelligence.llm.cross_refactor import analyze_cross_repo
|
|
324
|
+
|
|
325
|
+
result = analyze_cross_repo(tmp_path)
|
|
326
|
+
assert result.repos_analyzed == []
|
|
327
|
+
assert result.total_symbols == 0
|
|
328
|
+
assert result.matches == []
|
|
329
|
+
|
|
330
|
+
def test_result_to_dict(self):
|
|
331
|
+
from semantic_code_intelligence.llm.cross_refactor import (
|
|
332
|
+
CrossRefactorResult,
|
|
333
|
+
CrossRepoMatch,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
match = CrossRepoMatch(
|
|
337
|
+
repo_a="backend",
|
|
338
|
+
symbol_a="validate",
|
|
339
|
+
file_a="auth.py",
|
|
340
|
+
repo_b="frontend",
|
|
341
|
+
symbol_b="validate",
|
|
342
|
+
file_b="auth.ts",
|
|
343
|
+
similarity_note="Jaccard: 0.85",
|
|
344
|
+
)
|
|
345
|
+
result = CrossRefactorResult(
|
|
346
|
+
repos_analyzed=["backend", "frontend"],
|
|
347
|
+
total_symbols=10,
|
|
348
|
+
matches=[match],
|
|
349
|
+
)
|
|
350
|
+
d = result.to_dict()
|
|
351
|
+
assert d["repos_analyzed"] == ["backend", "frontend"]
|
|
352
|
+
assert len(d["matches"]) == 1
|
|
353
|
+
assert d["matches"][0]["symbol_a"] == "validate"
|
|
354
|
+
|
|
355
|
+
def test_cross_repo_match_to_dict(self):
|
|
356
|
+
from semantic_code_intelligence.llm.cross_refactor import CrossRepoMatch
|
|
357
|
+
|
|
358
|
+
m = CrossRepoMatch(
|
|
359
|
+
repo_a="a", symbol_a="foo", file_a="a.py",
|
|
360
|
+
repo_b="b", symbol_b="bar", file_b="b.py",
|
|
361
|
+
)
|
|
362
|
+
d = m.to_dict()
|
|
363
|
+
assert d["repo_a"] == "a"
|
|
364
|
+
assert d["file_b"] == "b.py"
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class TestCrossRepoDuplicates:
|
|
368
|
+
"""Tests for cross-repo duplicate detection internals."""
|
|
369
|
+
|
|
370
|
+
def test_find_cross_duplicates_same_repo_excluded(self):
|
|
371
|
+
from semantic_code_intelligence.llm.cross_refactor import _find_cross_duplicates
|
|
372
|
+
|
|
373
|
+
body = "def f(x):\n y = x + 1\n z = y * 2\n return z\n # pad\n"
|
|
374
|
+
repo_symbols = {
|
|
375
|
+
"repoA": [
|
|
376
|
+
{"name": "f1", "kind": "function", "file": "a.py", "lines": 5, "body": body},
|
|
377
|
+
{"name": "f2", "kind": "function", "file": "b.py", "lines": 5, "body": body},
|
|
378
|
+
],
|
|
379
|
+
}
|
|
380
|
+
matches = _find_cross_duplicates(repo_symbols)
|
|
381
|
+
assert len(matches) == 0 # Same repo — excluded
|
|
382
|
+
|
|
383
|
+
def test_find_cross_duplicates_across_repos(self):
|
|
384
|
+
from semantic_code_intelligence.llm.cross_refactor import _find_cross_duplicates
|
|
385
|
+
|
|
386
|
+
body = "def f(x):\n y = x + 1\n z = y * 2\n return z\n # extra\n"
|
|
387
|
+
repo_symbols = {
|
|
388
|
+
"repoA": [{"name": "compute", "kind": "function", "file": "a.py", "lines": 5, "body": body}],
|
|
389
|
+
"repoB": [{"name": "calc", "kind": "function", "file": "b.py", "lines": 5, "body": body}],
|
|
390
|
+
}
|
|
391
|
+
matches = _find_cross_duplicates(repo_symbols, threshold=0.5)
|
|
392
|
+
assert len(matches) == 1
|
|
393
|
+
assert matches[0].repo_a != matches[0].repo_b
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# =========================================================================
|
|
397
|
+
# Streaming LLM tests
|
|
398
|
+
# =========================================================================
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class TestStreaming:
|
|
402
|
+
"""Tests for streaming LLM support."""
|
|
403
|
+
|
|
404
|
+
def test_stream_mock(self):
|
|
405
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
406
|
+
from semantic_code_intelligence.llm.streaming import stream_chat, StreamEvent
|
|
407
|
+
|
|
408
|
+
provider = MockProvider(default_response="Hello world test")
|
|
409
|
+
messages = [LLMMessage(role=MessageRole.USER, content="Hi")]
|
|
410
|
+
|
|
411
|
+
events = list(stream_chat(provider, messages))
|
|
412
|
+
kinds = [e.kind for e in events]
|
|
413
|
+
|
|
414
|
+
assert "start" in kinds
|
|
415
|
+
assert "token" in kinds
|
|
416
|
+
assert "done" in kinds
|
|
417
|
+
|
|
418
|
+
# Accumulate tokens
|
|
419
|
+
text = "".join(e.content for e in events if e.kind == "token")
|
|
420
|
+
assert text == "Hello world test"
|
|
421
|
+
|
|
422
|
+
def test_stream_mock_with_custom_response(self):
|
|
423
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
424
|
+
from semantic_code_intelligence.llm.streaming import stream_chat
|
|
425
|
+
|
|
426
|
+
provider = MockProvider()
|
|
427
|
+
provider.enqueue_response("Custom streaming response")
|
|
428
|
+
messages = [LLMMessage(role=MessageRole.USER, content="Test")]
|
|
429
|
+
|
|
430
|
+
events = list(stream_chat(provider, messages))
|
|
431
|
+
text = "".join(e.content for e in events if e.kind == "token")
|
|
432
|
+
assert text == "Custom streaming response"
|
|
433
|
+
|
|
434
|
+
def test_stream_event_to_dict(self):
|
|
435
|
+
from semantic_code_intelligence.llm.streaming import StreamEvent
|
|
436
|
+
|
|
437
|
+
event = StreamEvent(kind="token", content="hello", metadata={"pos": 1})
|
|
438
|
+
d = event.to_dict()
|
|
439
|
+
assert d["kind"] == "token"
|
|
440
|
+
assert d["content"] == "hello"
|
|
441
|
+
|
|
442
|
+
def test_stream_event_to_sse(self):
|
|
443
|
+
from semantic_code_intelligence.llm.streaming import StreamEvent
|
|
444
|
+
|
|
445
|
+
event = StreamEvent(kind="token", content="hi")
|
|
446
|
+
sse = event.to_sse()
|
|
447
|
+
assert sse.startswith("data: ")
|
|
448
|
+
assert sse.endswith("\n\n")
|
|
449
|
+
payload = json.loads(sse[len("data: "):])
|
|
450
|
+
assert payload["kind"] == "token"
|
|
451
|
+
|
|
452
|
+
def test_stream_with_plugin_manager(self):
|
|
453
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
454
|
+
from semantic_code_intelligence.llm.streaming import stream_chat
|
|
455
|
+
from semantic_code_intelligence.plugins import PluginManager
|
|
456
|
+
|
|
457
|
+
provider = MockProvider(default_response="Token test")
|
|
458
|
+
pm = PluginManager()
|
|
459
|
+
messages = [LLMMessage(role=MessageRole.USER, content="Test")]
|
|
460
|
+
|
|
461
|
+
events = list(stream_chat(provider, messages, plugin_manager=pm))
|
|
462
|
+
assert any(e.kind == "token" for e in events)
|
|
463
|
+
|
|
464
|
+
def test_fallback_non_standard_provider(self):
|
|
465
|
+
"""Test that a non-standard provider falls back to single-token emit."""
|
|
466
|
+
from semantic_code_intelligence.llm.streaming import stream_chat
|
|
467
|
+
|
|
468
|
+
class CustomProvider:
|
|
469
|
+
name = "custom"
|
|
470
|
+
|
|
471
|
+
def chat(self, messages, **kwargs):
|
|
472
|
+
return LLMResponse(content="Custom response", model="custom", provider="custom")
|
|
473
|
+
|
|
474
|
+
provider = CustomProvider()
|
|
475
|
+
messages = [LLMMessage(role=MessageRole.USER, content="Test")]
|
|
476
|
+
|
|
477
|
+
events = list(stream_chat(provider, messages))
|
|
478
|
+
kinds = [e.kind for e in events]
|
|
479
|
+
assert "start" in kinds
|
|
480
|
+
assert "token" in kinds
|
|
481
|
+
assert "done" in kinds
|
|
482
|
+
assert events[1].content == "Custom response"
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# =========================================================================
|
|
486
|
+
# CLI command tests
|
|
487
|
+
# =========================================================================
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class TestChatCLI:
|
|
491
|
+
"""Tests for the `codexa chat` command."""
|
|
492
|
+
|
|
493
|
+
@pytest.fixture
|
|
494
|
+
def runner(self):
|
|
495
|
+
return CliRunner()
|
|
496
|
+
|
|
497
|
+
def _extract_json(self, output: str) -> dict:
|
|
498
|
+
"""Extract JSON object from CLI output, skipping any log noise."""
|
|
499
|
+
# Find the first '{' and parse from there
|
|
500
|
+
start = output.index("{")
|
|
501
|
+
return json.loads(output[start:])
|
|
502
|
+
|
|
503
|
+
def test_help(self, runner):
|
|
504
|
+
from semantic_code_intelligence.cli.commands.chat_cmd import chat_cmd
|
|
505
|
+
|
|
506
|
+
result = runner.invoke(chat_cmd, ["--help"])
|
|
507
|
+
assert result.exit_code == 0
|
|
508
|
+
assert "chat" in result.output.lower() or "conversation" in result.output.lower()
|
|
509
|
+
|
|
510
|
+
def test_has_session_option(self, runner):
|
|
511
|
+
from semantic_code_intelligence.cli.commands.chat_cmd import chat_cmd
|
|
512
|
+
|
|
513
|
+
result = runner.invoke(chat_cmd, ["--help"])
|
|
514
|
+
assert "--session" in result.output
|
|
515
|
+
|
|
516
|
+
def test_has_list_sessions(self, runner):
|
|
517
|
+
from semantic_code_intelligence.cli.commands.chat_cmd import chat_cmd
|
|
518
|
+
|
|
519
|
+
result = runner.invoke(chat_cmd, ["--help"])
|
|
520
|
+
assert "--list-sessions" in result.output
|
|
521
|
+
|
|
522
|
+
def test_has_max_turns(self, runner):
|
|
523
|
+
from semantic_code_intelligence.cli.commands.chat_cmd import chat_cmd
|
|
524
|
+
|
|
525
|
+
result = runner.invoke(chat_cmd, ["--help"])
|
|
526
|
+
assert "--max-turns" in result.output
|
|
527
|
+
|
|
528
|
+
def test_json_output(self, runner, tmp_path):
|
|
529
|
+
from semantic_code_intelligence.cli.commands.chat_cmd import chat_cmd
|
|
530
|
+
|
|
531
|
+
result = runner.invoke(chat_cmd, [
|
|
532
|
+
"Hello", "--json", "--path", str(tmp_path)
|
|
533
|
+
], obj={"pipe": False})
|
|
534
|
+
assert result.exit_code == 0
|
|
535
|
+
data = self._extract_json(result.output)
|
|
536
|
+
assert "session_id" in data
|
|
537
|
+
assert "answer" in data
|
|
538
|
+
|
|
539
|
+
def test_pipe_mode(self, runner, tmp_path):
|
|
540
|
+
from semantic_code_intelligence.cli.commands.chat_cmd import chat_cmd
|
|
541
|
+
|
|
542
|
+
result = runner.invoke(chat_cmd, [
|
|
543
|
+
"Hello", "--pipe", "--path", str(tmp_path)
|
|
544
|
+
], obj={"pipe": False})
|
|
545
|
+
assert result.exit_code == 0
|
|
546
|
+
assert len(result.output.strip()) > 0
|
|
547
|
+
|
|
548
|
+
def test_list_sessions_json(self, runner, tmp_path):
|
|
549
|
+
from semantic_code_intelligence.cli.commands.chat_cmd import chat_cmd
|
|
550
|
+
|
|
551
|
+
result = runner.invoke(chat_cmd, [
|
|
552
|
+
"x", "--list-sessions", "--json", "--path", str(tmp_path)
|
|
553
|
+
], obj={"pipe": False})
|
|
554
|
+
assert result.exit_code == 0
|
|
555
|
+
data = json.loads(result.output)
|
|
556
|
+
assert isinstance(data, list)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
class TestInvestigateCLI:
|
|
560
|
+
"""Tests for the `codexa investigate` command."""
|
|
561
|
+
|
|
562
|
+
@pytest.fixture
|
|
563
|
+
def runner(self):
|
|
564
|
+
return CliRunner()
|
|
565
|
+
|
|
566
|
+
def test_help(self, runner):
|
|
567
|
+
from semantic_code_intelligence.cli.commands.investigate_cmd import investigate_cmd
|
|
568
|
+
|
|
569
|
+
result = runner.invoke(investigate_cmd, ["--help"])
|
|
570
|
+
assert result.exit_code == 0
|
|
571
|
+
assert "investigate" in result.output.lower() or "investigation" in result.output.lower()
|
|
572
|
+
|
|
573
|
+
def test_has_max_steps(self, runner):
|
|
574
|
+
from semantic_code_intelligence.cli.commands.investigate_cmd import investigate_cmd
|
|
575
|
+
|
|
576
|
+
result = runner.invoke(investigate_cmd, ["--help"])
|
|
577
|
+
assert "--max-steps" in result.output
|
|
578
|
+
|
|
579
|
+
def test_json_output(self, runner, tmp_path):
|
|
580
|
+
from semantic_code_intelligence.cli.commands.investigate_cmd import investigate_cmd
|
|
581
|
+
|
|
582
|
+
result = runner.invoke(investigate_cmd, [
|
|
583
|
+
"What is this?", "--json", "--path", str(tmp_path)
|
|
584
|
+
], obj={"pipe": False})
|
|
585
|
+
assert result.exit_code == 0
|
|
586
|
+
data = json.loads(result.output)
|
|
587
|
+
assert "question" in data
|
|
588
|
+
assert "conclusion" in data
|
|
589
|
+
assert "steps" in data
|
|
590
|
+
|
|
591
|
+
def test_pipe_mode(self, runner, tmp_path):
|
|
592
|
+
from semantic_code_intelligence.cli.commands.investigate_cmd import investigate_cmd
|
|
593
|
+
|
|
594
|
+
result = runner.invoke(investigate_cmd, [
|
|
595
|
+
"What is this?", "--pipe", "--path", str(tmp_path)
|
|
596
|
+
], obj={"pipe": False})
|
|
597
|
+
assert result.exit_code == 0
|
|
598
|
+
assert "Conclusion:" in result.output
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
class TestCrossRefactorCLI:
|
|
602
|
+
"""Tests for the `codexa cross-refactor` command."""
|
|
603
|
+
|
|
604
|
+
@pytest.fixture
|
|
605
|
+
def runner(self):
|
|
606
|
+
return CliRunner()
|
|
607
|
+
|
|
608
|
+
def test_help(self, runner):
|
|
609
|
+
from semantic_code_intelligence.cli.commands.cross_refactor_cmd import cross_refactor_cmd
|
|
610
|
+
|
|
611
|
+
result = runner.invoke(cross_refactor_cmd, ["--help"])
|
|
612
|
+
assert result.exit_code == 0
|
|
613
|
+
assert "cross-refactor" in result.output.lower() or "refactor" in result.output.lower()
|
|
614
|
+
|
|
615
|
+
def test_has_threshold(self, runner):
|
|
616
|
+
from semantic_code_intelligence.cli.commands.cross_refactor_cmd import cross_refactor_cmd
|
|
617
|
+
|
|
618
|
+
result = runner.invoke(cross_refactor_cmd, ["--help"])
|
|
619
|
+
assert "--threshold" in result.output
|
|
620
|
+
|
|
621
|
+
def test_json_empty_workspace(self, runner, tmp_path):
|
|
622
|
+
from semantic_code_intelligence.cli.commands.cross_refactor_cmd import cross_refactor_cmd
|
|
623
|
+
|
|
624
|
+
result = runner.invoke(cross_refactor_cmd, [
|
|
625
|
+
"--json", "--path", str(tmp_path)
|
|
626
|
+
], obj={"pipe": False})
|
|
627
|
+
assert result.exit_code == 0
|
|
628
|
+
data = json.loads(result.output)
|
|
629
|
+
assert data["repos_analyzed"] == []
|
|
630
|
+
|
|
631
|
+
def test_pipe_mode(self, runner, tmp_path):
|
|
632
|
+
from semantic_code_intelligence.cli.commands.cross_refactor_cmd import cross_refactor_cmd
|
|
633
|
+
|
|
634
|
+
result = runner.invoke(cross_refactor_cmd, [
|
|
635
|
+
"--pipe", "--path", str(tmp_path)
|
|
636
|
+
], obj={"pipe": False})
|
|
637
|
+
assert result.exit_code == 0
|
|
638
|
+
assert "Repos:" in result.output
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
# =========================================================================
|
|
642
|
+
# Router, version, and module structure tests
|
|
643
|
+
# =========================================================================
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
class TestRouterPhase16:
|
|
647
|
+
"""Tests for CLI router registration."""
|
|
648
|
+
|
|
649
|
+
def test_register_commands_count(self):
|
|
650
|
+
import click
|
|
651
|
+
from semantic_code_intelligence.cli.router import register_commands
|
|
652
|
+
|
|
653
|
+
group = click.Group("test")
|
|
654
|
+
register_commands(group)
|
|
655
|
+
assert len(group.commands) == 39
|
|
656
|
+
|
|
657
|
+
def test_chat_command_registered(self):
|
|
658
|
+
from semantic_code_intelligence.cli.main import cli
|
|
659
|
+
|
|
660
|
+
assert "chat" in cli.commands
|
|
661
|
+
|
|
662
|
+
def test_investigate_command_registered(self):
|
|
663
|
+
from semantic_code_intelligence.cli.main import cli
|
|
664
|
+
|
|
665
|
+
assert "investigate" in cli.commands
|
|
666
|
+
|
|
667
|
+
def test_cross_refactor_command_registered(self):
|
|
668
|
+
from semantic_code_intelligence.cli.main import cli
|
|
669
|
+
|
|
670
|
+
assert "cross-refactor" in cli.commands
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
class TestVersionBump:
|
|
674
|
+
"""Test version is 0.19.0."""
|
|
675
|
+
|
|
676
|
+
def test_version_is_016(self):
|
|
677
|
+
from semantic_code_intelligence import __version__
|
|
678
|
+
|
|
679
|
+
assert __version__ == "0.4.0"
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
class TestPhase16ModuleStructure:
|
|
683
|
+
"""Tests for module import structure."""
|
|
684
|
+
|
|
685
|
+
def test_import_conversation(self):
|
|
686
|
+
from semantic_code_intelligence.llm.conversation import (
|
|
687
|
+
ConversationSession,
|
|
688
|
+
SessionStore,
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
def test_import_investigation(self):
|
|
692
|
+
from semantic_code_intelligence.llm.investigation import (
|
|
693
|
+
InvestigationChain,
|
|
694
|
+
InvestigationResult,
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
def test_import_streaming(self):
|
|
698
|
+
from semantic_code_intelligence.llm.streaming import (
|
|
699
|
+
stream_chat,
|
|
700
|
+
StreamEvent,
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
def test_import_cross_refactor(self):
|
|
704
|
+
from semantic_code_intelligence.llm.cross_refactor import (
|
|
705
|
+
analyze_cross_repo,
|
|
706
|
+
CrossRefactorResult,
|
|
707
|
+
CrossRepoMatch,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
def test_llm_package_exports(self):
|
|
711
|
+
from semantic_code_intelligence.llm import (
|
|
712
|
+
ConversationSession,
|
|
713
|
+
SessionStore,
|
|
714
|
+
InvestigationChain,
|
|
715
|
+
InvestigationResult,
|
|
716
|
+
stream_chat,
|
|
717
|
+
StreamEvent,
|
|
718
|
+
analyze_cross_repo,
|
|
719
|
+
CrossRefactorResult,
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
class TestDocsGenerator:
|
|
724
|
+
"""Tests for AI workflows doc generation."""
|
|
725
|
+
|
|
726
|
+
def test_generate_ai_workflows_reference(self):
|
|
727
|
+
from semantic_code_intelligence.docs import generate_ai_workflows_reference
|
|
728
|
+
|
|
729
|
+
md = generate_ai_workflows_reference()
|
|
730
|
+
assert "AI Workflows" in md
|
|
731
|
+
assert "codexa chat" in md
|
|
732
|
+
assert "codexa investigate" in md
|
|
733
|
+
assert "codexa cross-refactor" in md
|
|
734
|
+
assert "stream_chat" in md
|
|
735
|
+
assert "ON_STREAM" in md
|
|
736
|
+
|
|
737
|
+
def test_generate_all_docs_includes_ai(self, tmp_path):
|
|
738
|
+
from semantic_code_intelligence.docs import generate_all_docs
|
|
739
|
+
|
|
740
|
+
generated = generate_all_docs(tmp_path)
|
|
741
|
+
assert "AI_WORKFLOWS.md" in generated
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
# =========================================================================
|
|
745
|
+
# Backward compatibility tests
|
|
746
|
+
# =========================================================================
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
class TestBackwardCompatibility:
|
|
750
|
+
"""Ensure Phase 14 and Phase 15 modules still work."""
|
|
751
|
+
|
|
752
|
+
def test_ci_module_imports(self):
|
|
753
|
+
from semantic_code_intelligence.ci.quality import analyze_project, QualityReport
|
|
754
|
+
from semantic_code_intelligence.ci.pr import generate_pr_report, PRReport
|
|
755
|
+
from semantic_code_intelligence.ci.templates import get_template
|
|
756
|
+
from semantic_code_intelligence.ci.hooks import run_precommit_check
|
|
757
|
+
|
|
758
|
+
def test_web_module_imports(self):
|
|
759
|
+
from semantic_code_intelligence.web.api import APIHandler
|
|
760
|
+
from semantic_code_intelligence.web.ui import page_search
|
|
761
|
+
from semantic_code_intelligence.web.visualize import render_call_graph
|
|
762
|
+
|
|
763
|
+
def test_reasoning_engine_intact(self):
|
|
764
|
+
from semantic_code_intelligence.llm.reasoning import (
|
|
765
|
+
ReasoningEngine,
|
|
766
|
+
AskResult,
|
|
767
|
+
ReviewResult,
|
|
768
|
+
RefactorResult,
|
|
769
|
+
SuggestResult,
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
def test_plugin_hooks_intact(self):
|
|
773
|
+
from semantic_code_intelligence.plugins import PluginHook
|
|
774
|
+
|
|
775
|
+
assert PluginHook.ON_STREAM.value == "on_stream"
|
|
776
|
+
assert PluginHook.CUSTOM_VALIDATION.value == "custom_validation"
|
|
777
|
+
|
|
778
|
+
def test_safety_validator_intact(self):
|
|
779
|
+
from semantic_code_intelligence.llm.safety import SafetyValidator
|
|
780
|
+
|
|
781
|
+
v = SafetyValidator()
|
|
782
|
+
assert v.validate("x = 1\n").safe is True
|
|
783
|
+
|
|
784
|
+
def test_session_memory_intact(self):
|
|
785
|
+
from semantic_code_intelligence.context.memory import SessionMemory, ReasoningStep
|
|
786
|
+
|
|
787
|
+
mem = SessionMemory()
|
|
788
|
+
mem.start_chain("test")
|
|
789
|
+
mem.add_step("test", "search", "query", "results")
|
|
790
|
+
chain = mem.get_chain("test")
|
|
791
|
+
assert len(chain) == 1
|
|
792
|
+
assert chain[0].action == "search"
|