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,92 @@
|
|
|
1
|
+
"""Tests for the enhanced embedding pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from semantic_code_intelligence.embeddings.enhanced import (
|
|
8
|
+
preprocess_code_for_embedding,
|
|
9
|
+
prepare_semantic_texts,
|
|
10
|
+
)
|
|
11
|
+
from semantic_code_intelligence.indexing.semantic_chunker import SemanticChunk
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# Preprocessing
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
class TestPreprocessCodeForEmbedding:
|
|
19
|
+
def test_strips_trailing_whitespace(self):
|
|
20
|
+
text = "def foo(): \n pass \n"
|
|
21
|
+
result = preprocess_code_for_embedding(text)
|
|
22
|
+
for line in result.splitlines():
|
|
23
|
+
assert line == line.rstrip()
|
|
24
|
+
|
|
25
|
+
def test_collapses_blank_lines(self):
|
|
26
|
+
text = "a\n\n\n\n\nb\n"
|
|
27
|
+
result = preprocess_code_for_embedding(text)
|
|
28
|
+
assert "\n\n\n" not in result
|
|
29
|
+
|
|
30
|
+
def test_prepends_semantic_label(self):
|
|
31
|
+
result = preprocess_code_for_embedding("x = 1", "[python] function foo")
|
|
32
|
+
assert result.startswith("[python] function foo\n")
|
|
33
|
+
|
|
34
|
+
def test_empty_label(self):
|
|
35
|
+
result = preprocess_code_for_embedding("x = 1", "")
|
|
36
|
+
assert not result.startswith("\n")
|
|
37
|
+
|
|
38
|
+
def test_empty_content(self):
|
|
39
|
+
result = preprocess_code_for_embedding("")
|
|
40
|
+
assert result == ""
|
|
41
|
+
|
|
42
|
+
def test_preserves_meaningful_content(self):
|
|
43
|
+
code = "def hello():\n return 'world'"
|
|
44
|
+
result = preprocess_code_for_embedding(code)
|
|
45
|
+
assert "def hello():" in result
|
|
46
|
+
assert "return 'world'" in result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Batch preparation
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
class TestPrepareSemanticTexts:
|
|
54
|
+
def test_empty_list(self):
|
|
55
|
+
assert prepare_semantic_texts([]) == []
|
|
56
|
+
|
|
57
|
+
def test_single_chunk(self):
|
|
58
|
+
chunk = SemanticChunk(
|
|
59
|
+
file_path="t.py", content="def foo(): pass",
|
|
60
|
+
start_line=1, end_line=1, chunk_index=0,
|
|
61
|
+
language="python", symbol_name="foo",
|
|
62
|
+
symbol_kind="function",
|
|
63
|
+
semantic_label="[python] function foo",
|
|
64
|
+
)
|
|
65
|
+
texts = prepare_semantic_texts([chunk])
|
|
66
|
+
assert len(texts) == 1
|
|
67
|
+
assert "[python] function foo" in texts[0]
|
|
68
|
+
|
|
69
|
+
def test_multiple_chunks(self):
|
|
70
|
+
chunks = [
|
|
71
|
+
SemanticChunk(
|
|
72
|
+
file_path="t.py", content=f"line{i}",
|
|
73
|
+
start_line=i, end_line=i, chunk_index=i,
|
|
74
|
+
language="python",
|
|
75
|
+
)
|
|
76
|
+
for i in range(3)
|
|
77
|
+
]
|
|
78
|
+
texts = prepare_semantic_texts(chunks)
|
|
79
|
+
assert len(texts) == 3
|
|
80
|
+
|
|
81
|
+
def test_preserves_content_order(self):
|
|
82
|
+
chunks = [
|
|
83
|
+
SemanticChunk(
|
|
84
|
+
file_path="t.py", content=f"content_{i}",
|
|
85
|
+
start_line=i, end_line=i, chunk_index=i,
|
|
86
|
+
language="python",
|
|
87
|
+
)
|
|
88
|
+
for i in range(3)
|
|
89
|
+
]
|
|
90
|
+
texts = prepare_semantic_texts(chunks)
|
|
91
|
+
for i, text in enumerate(texts):
|
|
92
|
+
assert f"content_{i}" in text
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Tests for the hash store."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from semantic_code_intelligence.storage.hash_store import HashStore
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestHashStore:
|
|
13
|
+
"""Tests for HashStore operations."""
|
|
14
|
+
|
|
15
|
+
def test_empty_store(self):
|
|
16
|
+
store = HashStore()
|
|
17
|
+
assert store.count == 0
|
|
18
|
+
assert store.get("file.py") is None
|
|
19
|
+
|
|
20
|
+
def test_set_and_get(self):
|
|
21
|
+
store = HashStore()
|
|
22
|
+
store.set("file.py", "abc123")
|
|
23
|
+
assert store.get("file.py") == "abc123"
|
|
24
|
+
|
|
25
|
+
def test_has_changed_new_file(self):
|
|
26
|
+
store = HashStore()
|
|
27
|
+
assert store.has_changed("file.py", "hash1") is True
|
|
28
|
+
|
|
29
|
+
def test_has_changed_same_hash(self):
|
|
30
|
+
store = HashStore()
|
|
31
|
+
store.set("file.py", "hash1")
|
|
32
|
+
assert store.has_changed("file.py", "hash1") is False
|
|
33
|
+
|
|
34
|
+
def test_has_changed_different_hash(self):
|
|
35
|
+
store = HashStore()
|
|
36
|
+
store.set("file.py", "hash1")
|
|
37
|
+
assert store.has_changed("file.py", "hash2") is True
|
|
38
|
+
|
|
39
|
+
def test_remove(self):
|
|
40
|
+
store = HashStore()
|
|
41
|
+
store.set("file.py", "hash1")
|
|
42
|
+
store.remove("file.py")
|
|
43
|
+
assert store.get("file.py") is None
|
|
44
|
+
assert store.count == 0
|
|
45
|
+
|
|
46
|
+
def test_remove_nonexistent_no_error(self):
|
|
47
|
+
store = HashStore()
|
|
48
|
+
store.remove("nonexistent.py") # should not raise
|
|
49
|
+
|
|
50
|
+
def test_count(self):
|
|
51
|
+
store = HashStore()
|
|
52
|
+
store.set("a.py", "h1")
|
|
53
|
+
store.set("b.py", "h2")
|
|
54
|
+
assert store.count == 2
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestHashStorePersistence:
|
|
58
|
+
"""Tests for save/load."""
|
|
59
|
+
|
|
60
|
+
def test_save_creates_file(self, tmp_path: Path):
|
|
61
|
+
store = HashStore()
|
|
62
|
+
store.set("file.py", "hash1")
|
|
63
|
+
store.save(tmp_path)
|
|
64
|
+
assert (tmp_path / "file_hashes.json").exists()
|
|
65
|
+
|
|
66
|
+
def test_load_roundtrip(self, tmp_path: Path):
|
|
67
|
+
store = HashStore()
|
|
68
|
+
store.set("a.py", "h1")
|
|
69
|
+
store.set("b.py", "h2")
|
|
70
|
+
store.save(tmp_path)
|
|
71
|
+
|
|
72
|
+
loaded = HashStore.load(tmp_path)
|
|
73
|
+
assert loaded.count == 2
|
|
74
|
+
assert loaded.get("a.py") == "h1"
|
|
75
|
+
assert loaded.get("b.py") == "h2"
|
|
76
|
+
|
|
77
|
+
def test_load_nonexistent_returns_empty(self, tmp_path: Path):
|
|
78
|
+
loaded = HashStore.load(tmp_path / "nope")
|
|
79
|
+
assert loaded.count == 0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Tests for utils/logging module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from semantic_code_intelligence.utils.logging import (
|
|
8
|
+
console,
|
|
9
|
+
error_console,
|
|
10
|
+
get_logger,
|
|
11
|
+
setup_logging,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestSetupLogging:
|
|
16
|
+
"""Tests for logging setup."""
|
|
17
|
+
|
|
18
|
+
def test_setup_logging_default(self):
|
|
19
|
+
logger = setup_logging(verbose=False)
|
|
20
|
+
assert logger.level == logging.INFO
|
|
21
|
+
|
|
22
|
+
def test_setup_logging_verbose(self):
|
|
23
|
+
logger = setup_logging(verbose=True)
|
|
24
|
+
assert logger.level == logging.DEBUG
|
|
25
|
+
|
|
26
|
+
def test_setup_returns_logger(self):
|
|
27
|
+
logger = setup_logging()
|
|
28
|
+
assert isinstance(logger, logging.Logger)
|
|
29
|
+
assert logger.name == "codexa"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestGetLogger:
|
|
33
|
+
"""Tests for logger retrieval."""
|
|
34
|
+
|
|
35
|
+
def test_get_root_logger(self):
|
|
36
|
+
logger = get_logger()
|
|
37
|
+
assert logger.name == "codexa"
|
|
38
|
+
|
|
39
|
+
def test_get_child_logger(self):
|
|
40
|
+
logger = get_logger("test")
|
|
41
|
+
assert logger.name == "codexa.test"
|
|
42
|
+
|
|
43
|
+
def test_get_nested_child_logger(self):
|
|
44
|
+
logger = get_logger("cli.init")
|
|
45
|
+
assert logger.name == "codexa.cli.init"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestConsoles:
|
|
49
|
+
"""Tests for console instances."""
|
|
50
|
+
|
|
51
|
+
def test_console_exists(self):
|
|
52
|
+
assert console is not None
|
|
53
|
+
|
|
54
|
+
def test_error_console_uses_stderr(self):
|
|
55
|
+
assert error_console.stderr is True
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Tests for the expanded CLI commands (explain, summary, watch, deps)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from click.testing import CliRunner
|
|
10
|
+
|
|
11
|
+
from semantic_code_intelligence.cli.main import cli
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SAMPLE_PYTHON = """\
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
def greet(name):
|
|
18
|
+
return f"Hello, {name}!"
|
|
19
|
+
|
|
20
|
+
class Service:
|
|
21
|
+
def __init__(self, url):
|
|
22
|
+
self.url = url
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def runner():
|
|
28
|
+
return CliRunner()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def project_dir(tmp_path):
|
|
33
|
+
"""Create a minimal initialized project."""
|
|
34
|
+
config_dir = tmp_path / ".codexa"
|
|
35
|
+
config_dir.mkdir()
|
|
36
|
+
(config_dir / "config.json").write_text("{}", encoding="utf-8")
|
|
37
|
+
(tmp_path / "main.py").write_text(SAMPLE_PYTHON, encoding="utf-8")
|
|
38
|
+
return tmp_path
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# explain command
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
class TestExplainCmd:
|
|
46
|
+
def test_explain_file(self, runner, project_dir):
|
|
47
|
+
main_py = str(project_dir / "main.py")
|
|
48
|
+
result = runner.invoke(cli, ["explain", ".", "--file", main_py, "-p", str(project_dir)])
|
|
49
|
+
assert result.exit_code == 0
|
|
50
|
+
|
|
51
|
+
def test_explain_symbol_in_file(self, runner, project_dir):
|
|
52
|
+
main_py = str(project_dir / "main.py")
|
|
53
|
+
result = runner.invoke(cli, ["explain", "greet", "--file", main_py, "-p", str(project_dir)])
|
|
54
|
+
assert result.exit_code == 0
|
|
55
|
+
assert "greet" in result.output
|
|
56
|
+
|
|
57
|
+
def test_explain_symbol_not_found(self, runner, project_dir):
|
|
58
|
+
main_py = str(project_dir / "main.py")
|
|
59
|
+
result = runner.invoke(cli, ["explain", "nonexistent", "--file", main_py, "-p", str(project_dir)])
|
|
60
|
+
assert result.exit_code == 0
|
|
61
|
+
assert "not found" in result.output.lower()
|
|
62
|
+
|
|
63
|
+
def test_explain_json_mode(self, runner, project_dir):
|
|
64
|
+
main_py = str(project_dir / "main.py")
|
|
65
|
+
result = runner.invoke(cli, ["explain", ".", "--file", main_py, "--json", "-p", str(project_dir)])
|
|
66
|
+
assert result.exit_code == 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# summary command
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
class TestSummaryCmd:
|
|
74
|
+
def test_summary_basic(self, runner, project_dir):
|
|
75
|
+
result = runner.invoke(cli, ["summary", "-p", str(project_dir)])
|
|
76
|
+
assert result.exit_code == 0
|
|
77
|
+
|
|
78
|
+
def test_summary_json(self, runner, project_dir):
|
|
79
|
+
result = runner.invoke(cli, ["summary", "--json", "-p", str(project_dir)])
|
|
80
|
+
assert result.exit_code == 0
|
|
81
|
+
|
|
82
|
+
def test_summary_empty_project(self, runner, tmp_path):
|
|
83
|
+
config_dir = tmp_path / ".codexa"
|
|
84
|
+
config_dir.mkdir()
|
|
85
|
+
(config_dir / "config.json").write_text("{}", encoding="utf-8")
|
|
86
|
+
result = runner.invoke(cli, ["summary", "-p", str(tmp_path)])
|
|
87
|
+
assert result.exit_code == 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# deps command
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
class TestDepsCmd:
|
|
95
|
+
def test_deps_single_file(self, runner, project_dir):
|
|
96
|
+
result = runner.invoke(cli, ["deps", "main.py", "-p", str(project_dir)])
|
|
97
|
+
assert result.exit_code == 0
|
|
98
|
+
|
|
99
|
+
def test_deps_whole_project(self, runner, project_dir):
|
|
100
|
+
result = runner.invoke(cli, ["deps", ".", "-p", str(project_dir)])
|
|
101
|
+
assert result.exit_code == 0
|
|
102
|
+
|
|
103
|
+
def test_deps_json(self, runner, project_dir):
|
|
104
|
+
result = runner.invoke(cli, ["deps", ".", "--json", "-p", str(project_dir)])
|
|
105
|
+
assert result.exit_code == 0
|
|
106
|
+
|
|
107
|
+
def test_deps_nonexistent_file(self, runner, project_dir):
|
|
108
|
+
result = runner.invoke(cli, ["deps", "nope.py", "-p", str(project_dir)])
|
|
109
|
+
assert result.exit_code == 0
|
|
110
|
+
assert "not found" in result.output.lower()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# watch command
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
class TestWatchCmd:
|
|
118
|
+
def test_watch_no_init(self, runner, tmp_path):
|
|
119
|
+
result = runner.invoke(cli, ["watch", "-p", str(tmp_path)])
|
|
120
|
+
assert result.exit_code == 0
|
|
121
|
+
assert "not initialized" in result.output.lower()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# router verification
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
class TestRouter:
|
|
129
|
+
def test_all_commands_registered(self, runner):
|
|
130
|
+
result = runner.invoke(cli, ["--help"])
|
|
131
|
+
assert result.exit_code == 0
|
|
132
|
+
assert "explain" in result.output
|
|
133
|
+
assert "summary" in result.output
|
|
134
|
+
assert "watch" in result.output
|
|
135
|
+
assert "deps" in result.output
|
|
136
|
+
assert "init" in result.output
|
|
137
|
+
assert "index" in result.output
|
|
138
|
+
assert "search" in result.output
|