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.
Files changed (189) hide show
  1. codexa-0.4.0.dist-info/METADATA +650 -0
  2. codexa-0.4.0.dist-info/RECORD +189 -0
  3. codexa-0.4.0.dist-info/WHEEL +5 -0
  4. codexa-0.4.0.dist-info/entry_points.txt +2 -0
  5. codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
  6. codexa-0.4.0.dist-info/top_level.txt +1 -0
  7. semantic_code_intelligence/__init__.py +5 -0
  8. semantic_code_intelligence/analysis/__init__.py +21 -0
  9. semantic_code_intelligence/analysis/ai_features.py +351 -0
  10. semantic_code_intelligence/bridge/__init__.py +28 -0
  11. semantic_code_intelligence/bridge/context_provider.py +245 -0
  12. semantic_code_intelligence/bridge/protocol.py +167 -0
  13. semantic_code_intelligence/bridge/server.py +348 -0
  14. semantic_code_intelligence/bridge/vscode.py +271 -0
  15. semantic_code_intelligence/ci/__init__.py +13 -0
  16. semantic_code_intelligence/ci/hooks.py +98 -0
  17. semantic_code_intelligence/ci/hotspots.py +272 -0
  18. semantic_code_intelligence/ci/impact.py +246 -0
  19. semantic_code_intelligence/ci/metrics.py +591 -0
  20. semantic_code_intelligence/ci/pr.py +412 -0
  21. semantic_code_intelligence/ci/quality.py +557 -0
  22. semantic_code_intelligence/ci/templates.py +164 -0
  23. semantic_code_intelligence/ci/trace.py +224 -0
  24. semantic_code_intelligence/cli/__init__.py +0 -0
  25. semantic_code_intelligence/cli/commands/__init__.py +0 -0
  26. semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
  27. semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
  28. semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
  29. semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
  30. semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
  31. semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
  32. semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
  33. semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
  34. semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
  35. semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
  36. semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
  37. semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
  38. semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
  39. semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
  40. semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
  41. semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
  42. semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
  43. semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
  44. semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
  45. semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
  46. semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
  47. semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
  48. semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
  49. semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
  50. semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
  51. semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
  52. semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
  53. semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
  54. semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
  55. semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
  56. semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
  57. semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
  58. semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
  59. semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
  60. semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
  61. semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
  62. semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
  63. semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
  64. semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
  65. semantic_code_intelligence/cli/main.py +65 -0
  66. semantic_code_intelligence/cli/router.py +92 -0
  67. semantic_code_intelligence/config/__init__.py +0 -0
  68. semantic_code_intelligence/config/settings.py +260 -0
  69. semantic_code_intelligence/context/__init__.py +19 -0
  70. semantic_code_intelligence/context/engine.py +429 -0
  71. semantic_code_intelligence/context/memory.py +253 -0
  72. semantic_code_intelligence/daemon/__init__.py +1 -0
  73. semantic_code_intelligence/daemon/watcher.py +515 -0
  74. semantic_code_intelligence/docs/__init__.py +1080 -0
  75. semantic_code_intelligence/embeddings/__init__.py +0 -0
  76. semantic_code_intelligence/embeddings/enhanced.py +131 -0
  77. semantic_code_intelligence/embeddings/generator.py +149 -0
  78. semantic_code_intelligence/embeddings/model_registry.py +100 -0
  79. semantic_code_intelligence/evolution/__init__.py +1 -0
  80. semantic_code_intelligence/evolution/budget_guard.py +111 -0
  81. semantic_code_intelligence/evolution/commit_manager.py +88 -0
  82. semantic_code_intelligence/evolution/context_builder.py +131 -0
  83. semantic_code_intelligence/evolution/engine.py +249 -0
  84. semantic_code_intelligence/evolution/patch_generator.py +229 -0
  85. semantic_code_intelligence/evolution/task_selector.py +214 -0
  86. semantic_code_intelligence/evolution/test_runner.py +111 -0
  87. semantic_code_intelligence/indexing/__init__.py +0 -0
  88. semantic_code_intelligence/indexing/chunker.py +174 -0
  89. semantic_code_intelligence/indexing/parallel.py +86 -0
  90. semantic_code_intelligence/indexing/scanner.py +146 -0
  91. semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
  92. semantic_code_intelligence/llm/__init__.py +62 -0
  93. semantic_code_intelligence/llm/cache.py +219 -0
  94. semantic_code_intelligence/llm/cached_provider.py +145 -0
  95. semantic_code_intelligence/llm/conversation.py +190 -0
  96. semantic_code_intelligence/llm/cross_refactor.py +272 -0
  97. semantic_code_intelligence/llm/investigation.py +274 -0
  98. semantic_code_intelligence/llm/mock_provider.py +77 -0
  99. semantic_code_intelligence/llm/ollama_provider.py +122 -0
  100. semantic_code_intelligence/llm/openai_provider.py +100 -0
  101. semantic_code_intelligence/llm/provider.py +92 -0
  102. semantic_code_intelligence/llm/rate_limiter.py +164 -0
  103. semantic_code_intelligence/llm/reasoning.py +438 -0
  104. semantic_code_intelligence/llm/safety.py +110 -0
  105. semantic_code_intelligence/llm/streaming.py +251 -0
  106. semantic_code_intelligence/lsp/__init__.py +609 -0
  107. semantic_code_intelligence/mcp/__init__.py +393 -0
  108. semantic_code_intelligence/parsing/__init__.py +19 -0
  109. semantic_code_intelligence/parsing/parser.py +375 -0
  110. semantic_code_intelligence/plugins/__init__.py +255 -0
  111. semantic_code_intelligence/plugins/examples/__init__.py +1 -0
  112. semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
  113. semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
  114. semantic_code_intelligence/scalability/__init__.py +205 -0
  115. semantic_code_intelligence/search/__init__.py +0 -0
  116. semantic_code_intelligence/search/formatter.py +123 -0
  117. semantic_code_intelligence/search/grep.py +361 -0
  118. semantic_code_intelligence/search/hybrid_search.py +170 -0
  119. semantic_code_intelligence/search/keyword_search.py +311 -0
  120. semantic_code_intelligence/search/section_expander.py +103 -0
  121. semantic_code_intelligence/services/__init__.py +0 -0
  122. semantic_code_intelligence/services/indexing_service.py +630 -0
  123. semantic_code_intelligence/services/search_service.py +269 -0
  124. semantic_code_intelligence/storage/__init__.py +0 -0
  125. semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
  126. semantic_code_intelligence/storage/hash_store.py +66 -0
  127. semantic_code_intelligence/storage/index_manifest.py +85 -0
  128. semantic_code_intelligence/storage/index_stats.py +138 -0
  129. semantic_code_intelligence/storage/query_history.py +160 -0
  130. semantic_code_intelligence/storage/symbol_registry.py +209 -0
  131. semantic_code_intelligence/storage/vector_store.py +297 -0
  132. semantic_code_intelligence/tests/__init__.py +0 -0
  133. semantic_code_intelligence/tests/test_ai_features.py +351 -0
  134. semantic_code_intelligence/tests/test_chunker.py +119 -0
  135. semantic_code_intelligence/tests/test_cli.py +188 -0
  136. semantic_code_intelligence/tests/test_config.py +154 -0
  137. semantic_code_intelligence/tests/test_context.py +381 -0
  138. semantic_code_intelligence/tests/test_embeddings.py +73 -0
  139. semantic_code_intelligence/tests/test_endtoend.py +1142 -0
  140. semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
  141. semantic_code_intelligence/tests/test_hash_store.py +79 -0
  142. semantic_code_intelligence/tests/test_logging.py +55 -0
  143. semantic_code_intelligence/tests/test_new_cli.py +138 -0
  144. semantic_code_intelligence/tests/test_parser.py +495 -0
  145. semantic_code_intelligence/tests/test_phase10.py +355 -0
  146. semantic_code_intelligence/tests/test_phase11.py +593 -0
  147. semantic_code_intelligence/tests/test_phase12.py +375 -0
  148. semantic_code_intelligence/tests/test_phase13.py +663 -0
  149. semantic_code_intelligence/tests/test_phase14.py +568 -0
  150. semantic_code_intelligence/tests/test_phase15.py +814 -0
  151. semantic_code_intelligence/tests/test_phase16.py +792 -0
  152. semantic_code_intelligence/tests/test_phase17.py +815 -0
  153. semantic_code_intelligence/tests/test_phase18.py +934 -0
  154. semantic_code_intelligence/tests/test_phase19.py +986 -0
  155. semantic_code_intelligence/tests/test_phase20.py +2753 -0
  156. semantic_code_intelligence/tests/test_phase20b.py +2058 -0
  157. semantic_code_intelligence/tests/test_phase20c.py +962 -0
  158. semantic_code_intelligence/tests/test_phase21.py +428 -0
  159. semantic_code_intelligence/tests/test_phase22.py +799 -0
  160. semantic_code_intelligence/tests/test_phase23.py +783 -0
  161. semantic_code_intelligence/tests/test_phase24.py +715 -0
  162. semantic_code_intelligence/tests/test_phase25.py +496 -0
  163. semantic_code_intelligence/tests/test_phase26.py +251 -0
  164. semantic_code_intelligence/tests/test_phase27.py +531 -0
  165. semantic_code_intelligence/tests/test_phase8.py +592 -0
  166. semantic_code_intelligence/tests/test_phase9.py +643 -0
  167. semantic_code_intelligence/tests/test_plugins.py +293 -0
  168. semantic_code_intelligence/tests/test_priority_features.py +727 -0
  169. semantic_code_intelligence/tests/test_router.py +41 -0
  170. semantic_code_intelligence/tests/test_scalability.py +138 -0
  171. semantic_code_intelligence/tests/test_scanner.py +125 -0
  172. semantic_code_intelligence/tests/test_search.py +160 -0
  173. semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
  174. semantic_code_intelligence/tests/test_tools.py +182 -0
  175. semantic_code_intelligence/tests/test_vector_store.py +151 -0
  176. semantic_code_intelligence/tests/test_watcher.py +211 -0
  177. semantic_code_intelligence/tools/__init__.py +442 -0
  178. semantic_code_intelligence/tools/executor.py +232 -0
  179. semantic_code_intelligence/tools/protocol.py +200 -0
  180. semantic_code_intelligence/tui/__init__.py +454 -0
  181. semantic_code_intelligence/utils/__init__.py +0 -0
  182. semantic_code_intelligence/utils/logging.py +112 -0
  183. semantic_code_intelligence/version.py +3 -0
  184. semantic_code_intelligence/web/__init__.py +11 -0
  185. semantic_code_intelligence/web/api.py +289 -0
  186. semantic_code_intelligence/web/server.py +397 -0
  187. semantic_code_intelligence/web/ui.py +659 -0
  188. semantic_code_intelligence/web/visualize.py +226 -0
  189. 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